diff --git a/build-common.xml b/build-common.xml index abab979b12c..1b9018dd4e8 100644 --- a/build-common.xml +++ b/build-common.xml @@ -289,6 +289,7 @@ + @@ -312,60 +313,10 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + @@ -374,11 +325,11 @@ Test Report: ${test.dir}/report/index.html - - - - - + + + + + ${test.src.dir} not found. Will not run unit tests @@ -435,6 +386,9 @@ + + + diff --git a/client/src/java/com/zimbra/client/ZCalDataSource.java b/client/src/java/com/zimbra/client/ZCalDataSource.java index 40571f7f769..8a5404ff0bf 100644 --- a/client/src/java/com/zimbra/client/ZCalDataSource.java +++ b/client/src/java/com/zimbra/client/ZCalDataSource.java @@ -18,17 +18,18 @@ import org.json.JSONException; -import com.zimbra.soap.admin.type.DataSourceType; import com.zimbra.common.soap.Element; import com.zimbra.common.soap.MailConstants; -import com.zimbra.common.util.SystemUtil; +import com.zimbra.soap.admin.type.DataSourceType; +import com.zimbra.soap.mail.type.CalDataSourceNameOrId; +import com.zimbra.soap.mail.type.DataSourceNameOrId; +import com.zimbra.soap.mail.type.MailCalDataSource; import com.zimbra.soap.type.CalDataSource; +import com.zimbra.soap.type.DataSource; import com.zimbra.soap.type.DataSources; -public class ZCalDataSource implements ZDataSource, ToZJSONObject { +public class ZCalDataSource extends ZDataSource implements ToZJSONObject { - private CalDataSource data; - public ZCalDataSource(String name, String folderId, boolean enabled) { data = DataSources.newCalDataSource(); data.setName(name); @@ -40,14 +41,7 @@ public ZCalDataSource(CalDataSource data) { this.data = DataSources.newCalDataSource(data); } - public String getId() { - return data.getId(); - } - - public String getName() { - return data.getName(); - } - + @Override public DataSourceType getType() { return DataSourceType.cal; } @@ -55,11 +49,8 @@ public DataSourceType getType() { public String getFolderId() { return data.getFolderId(); } - - public boolean isEnabled() { - return SystemUtil.coalesce(data.isEnabled(), Boolean.FALSE); - } + @Deprecated public Element toElement(Element parent) { Element src = parent.addElement(MailConstants.E_DS_CAL); src.addAttribute(MailConstants.A_ID, data.getId()); @@ -69,12 +60,29 @@ public Element toElement(Element parent) { return src; } + @Deprecated public Element toIdElement(Element parent) { Element src = parent.addElement(MailConstants.E_DS_CAL); src.addAttribute(MailConstants.A_ID, getId()); return src; } + @Override + public DataSource toJaxb() { + MailCalDataSource jaxbObject = new MailCalDataSource(); + jaxbObject.setId(data.getId()); + jaxbObject.setName(data.getName()); + jaxbObject.setFolderId(data.getFolderId()); + jaxbObject.setEnabled(data.isEnabled()); + return jaxbObject; + } + + @Override + public DataSourceNameOrId toJaxbNameOrId() { + CalDataSourceNameOrId jaxbObject = CalDataSourceNameOrId.createForId(data.getId()); + return jaxbObject; + } + public ZJSONObject toZJSONObject() throws JSONException { ZJSONObject zjo = new ZJSONObject(); zjo.put("id", data.getId()); diff --git a/client/src/java/com/zimbra/client/ZDataSource.java b/client/src/java/com/zimbra/client/ZDataSource.java index 86e4da2c0df..dc50ae4d3e8 100644 --- a/client/src/java/com/zimbra/client/ZDataSource.java +++ b/client/src/java/com/zimbra/client/ZDataSource.java @@ -17,15 +17,121 @@ package com.zimbra.client; +import org.json.JSONException; + +import com.zimbra.common.util.SystemUtil; import com.zimbra.soap.admin.type.DataSourceType; -import com.zimbra.common.soap.Element; +import com.zimbra.soap.mail.type.DataSourceNameOrId; +import com.zimbra.soap.mail.type.MailDataSource; +import com.zimbra.soap.mail.type.MailUnknownDataSource; +import com.zimbra.soap.type.DataSource; +import com.zimbra.soap.type.DataSources; + +public class ZDataSource implements ToZJSONObject { + protected DataSource data; + public static String SOURCE_HOST_YAHOO = "yahoo.com"; + + public ZDataSource() { + data = DataSources.newDataSource(); + data.setEnabled(false); + } + + public ZDataSource(String name, boolean enabled) { + data = DataSources.newDataSource(); + data.setName(name); + data.setEnabled(enabled); + } + + public ZDataSource(String name, boolean enabled, Iterable attributes) { + data = DataSources.newDataSource(); + data.setAttributes(attributes); + data.setName(name); + data.setEnabled(enabled); + } + + public ZDataSource(DataSource data) { + this.data = DataSources.newDataSource(data); + } + + public DataSource toJaxb() { + MailUnknownDataSource jaxbObject = new MailUnknownDataSource(); + jaxbObject.setId(data.getId()); + jaxbObject.setName(data.getName()); + jaxbObject.setEnabled(data.isEnabled()); + jaxbObject.setFolderId(data.getFolderId()); + jaxbObject.setRefreshToken(data.getRefreshToken()); + jaxbObject.setRefreshTokenUrl(data.getRefreshTokenUrl()); + jaxbObject.setImportOnly(data.isImportOnly()); + jaxbObject.setImportClass(data.getImportClass()); + jaxbObject.setHost(data.getHost()); + return jaxbObject; + } + + public DataSourceNameOrId toJaxbNameOrId() { + DataSourceNameOrId jaxbObject = DataSourceNameOrId.createForId(data.getId()); + return jaxbObject; + } -public interface ZDataSource { - - public Element toElement(Element parent); - public Element toIdElement(Element parent); + @Override + public ZJSONObject toZJSONObject() throws JSONException { + ZJSONObject zjo = new ZJSONObject(); + zjo.put("id", data.getId()); + zjo.put("name", data.getName()); + zjo.put("enabled", data.isEnabled()); + zjo.put("folderId", data.getFolderId()); + zjo.put("refreshToken", data.getRefreshToken()); + zjo.put("refreshTokenUrl", data.getRefreshTokenUrl()); + zjo.put("importOnly", data.isImportOnly()); + zjo.put("importClass", data.getImportClass()); + zjo.put("host", data.getHost()); + return zjo; + } - public DataSourceType getType(); - public String getName(); - public String getId(); + public String getName() { + return data.getName(); + } + public void setName(String name) { + data.setName(name); + } + public DataSourceType getType() { + return DataSourceType.unknown; + } + public String getId() { + return data.getId(); + } + public void setId(String id) { + data.setId(id); + } + public String getFolderId() { return data.getFolderId(); } + public void setFolderId(String folderid) { + data.setFolderId(folderid); + } + public void setRefreshToken(String val) { + data.setRefreshToken(val); + } + public void setRefreshTokenURL(String val) { + data.setRefreshTokenUrl(val); + } + public String getRefreshToken() { + return data.getRefreshToken(); + } + public String getRefreshTokenUrl() { + return data.getRefreshTokenUrl(); + } + public String getImportClass() { + return data.getImportClass(); + } + public void setImportClass(String importClass) { + data.setImportClass(importClass); + } + public String getHost() { + return data.getHost(); + } + public void setHost(String host) { + data.setHost(host); + } + public void setEnabled(boolean enabled) { data.setEnabled(enabled); } + public boolean isEnabled() { + return SystemUtil.coalesce(data.isEnabled(), Boolean.FALSE); + } } diff --git a/client/src/java/com/zimbra/client/ZGetInfoResult.java b/client/src/java/com/zimbra/client/ZGetInfoResult.java index 342e1b932ee..9d2f71963b7 100644 --- a/client/src/java/com/zimbra/client/ZGetInfoResult.java +++ b/client/src/java/com/zimbra/client/ZGetInfoResult.java @@ -106,6 +106,8 @@ public List getDataSources() { newList.add(new ZCalDataSource((CalDataSource) ds)); } else if (ds instanceof RssDataSource) { newList.add(new ZRssDataSource((RssDataSource) ds)); + } else { + newList.add(new ZDataSource(ds)); } } return newList; diff --git a/client/src/java/com/zimbra/client/ZImapDataSource.java b/client/src/java/com/zimbra/client/ZImapDataSource.java index 2c32dd3ca12..ccd503a163c 100644 --- a/client/src/java/com/zimbra/client/ZImapDataSource.java +++ b/client/src/java/com/zimbra/client/ZImapDataSource.java @@ -18,18 +18,20 @@ import org.json.JSONException; -import com.zimbra.soap.admin.type.DataSourceType; import com.zimbra.common.service.ServiceException; import com.zimbra.common.soap.Element; import com.zimbra.common.soap.MailConstants; import com.zimbra.common.util.SystemUtil; +import com.zimbra.soap.admin.type.DataSourceType; +import com.zimbra.soap.mail.type.DataSourceNameOrId; +import com.zimbra.soap.mail.type.ImapDataSourceNameOrId; +import com.zimbra.soap.mail.type.MailImapDataSource; +import com.zimbra.soap.type.DataSource; import com.zimbra.soap.type.DataSource.ConnectionType; import com.zimbra.soap.type.DataSources; import com.zimbra.soap.type.ImapDataSource; -public class ZImapDataSource implements ZDataSource, ToZJSONObject { - - private ImapDataSource data; +public class ZImapDataSource extends ZDataSource implements ToZJSONObject { public ZImapDataSource(ImapDataSource data) { this.data = DataSources.newImapDataSource(data); @@ -63,6 +65,7 @@ public ZImapDataSource(String name, boolean enabled, String host, int port, this(name,enabled,host,port,username,password,folderid,connectionType,false); } + @Deprecated public Element toElement(Element parent) { Element src = parent.addElement(MailConstants.E_DS_IMAP); src.addAttribute(MailConstants.A_ID, data.getId()); @@ -84,16 +87,30 @@ public Element toIdElement(Element parent) { return src; } - public DataSourceType getType() { return DataSourceType.imap; } - - public String getId() { return data.getId(); } + @Override + public DataSource toJaxb() { + MailImapDataSource jaxbObject = new MailImapDataSource(); + jaxbObject.setId(data.getId()); + jaxbObject.setName(data.getName()); + jaxbObject.setHost(data.getHost()); + jaxbObject.setPort(data.getPort()); + jaxbObject.setUsername(data.getUsername()); + jaxbObject.setPassword(data.getPassword()); + jaxbObject.setFolderId(data.getFolderId()); + jaxbObject.setConnectionType(data.getConnectionType()); + jaxbObject.setImportOnly(data.isImportOnly()); + jaxbObject.setEnabled(data.isEnabled()); + return jaxbObject; + } - public String getName() { return data.getName(); } - public void setName(String name) { data.setName(name); } + @Override + public DataSourceNameOrId toJaxbNameOrId() { + ImapDataSourceNameOrId jaxbObject = ImapDataSourceNameOrId.createForId(data.getId()); + return jaxbObject; + } - public boolean isEnabled() { return SystemUtil.coalesce(data.isEnabled(), Boolean.FALSE); } - - public void setEnabled(boolean enabled) { data.setEnabled(enabled); } + @Override + public DataSourceType getType() { return DataSourceType.imap; } public String getHost() { return data.getHost(); } public void setHost(String host) { data.setHost(host); } @@ -103,7 +120,7 @@ public Element toIdElement(Element parent) { public String getUsername() { return data.getUsername(); } public void setUsername(String username) { data.setUsername(username); } - + public String getFolderId() { return data.getFolderId(); } public void setFolderId(String folderid) { data.setFolderId(folderid); } diff --git a/client/src/java/com/zimbra/client/ZMailbox.java b/client/src/java/com/zimbra/client/ZMailbox.java index 05f357f6199..b23f75d4872 100644 --- a/client/src/java/com/zimbra/client/ZMailbox.java +++ b/client/src/java/com/zimbra/client/ZMailbox.java @@ -161,8 +161,11 @@ import com.zimbra.soap.mail.message.CheckSpellingRequest; import com.zimbra.soap.mail.message.CheckSpellingResponse; import com.zimbra.soap.mail.message.CreateContactRequest; +import com.zimbra.soap.mail.message.CreateDataSourceRequest; +import com.zimbra.soap.mail.message.CreateDataSourceResponse; import com.zimbra.soap.mail.message.CreateSearchFolderRequest; import com.zimbra.soap.mail.message.CreateSearchFolderResponse; +import com.zimbra.soap.mail.message.DeleteDataSourceRequest; import com.zimbra.soap.mail.message.GetAppointmentRequest; import com.zimbra.soap.mail.message.GetAppointmentResponse; import com.zimbra.soap.mail.message.GetDataSourcesRequest; @@ -183,11 +186,13 @@ import com.zimbra.soap.mail.message.IMAPCopyResponse; import com.zimbra.soap.mail.message.ImportContactsRequest; import com.zimbra.soap.mail.message.ImportContactsResponse; +import com.zimbra.soap.mail.message.ImportDataRequest; import com.zimbra.soap.mail.message.ItemActionRequest; import com.zimbra.soap.mail.message.ItemActionResponse; import com.zimbra.soap.mail.message.ListIMAPSubscriptionsRequest; import com.zimbra.soap.mail.message.ListIMAPSubscriptionsResponse; import com.zimbra.soap.mail.message.ModifyContactRequest; +import com.zimbra.soap.mail.message.ModifyDataSourceRequest; import com.zimbra.soap.mail.message.ModifyFilterRulesRequest; import com.zimbra.soap.mail.message.ModifyOutgoingFilterRulesRequest; import com.zimbra.soap.mail.message.OpenIMAPFolderRequest; @@ -196,6 +201,8 @@ import com.zimbra.soap.mail.message.RecordIMAPSessionResponse; import com.zimbra.soap.mail.message.ResetRecentMessageCountRequest; import com.zimbra.soap.mail.message.SaveIMAPSubscriptionsRequest; +import com.zimbra.soap.mail.message.TestDataSourceRequest; +import com.zimbra.soap.mail.message.TestDataSourceResponse; import com.zimbra.soap.mail.type.ActionResult; import com.zimbra.soap.mail.type.ActionSelector; import com.zimbra.soap.mail.type.ContactSpec; @@ -210,6 +217,7 @@ import com.zimbra.soap.mail.type.NewContactAttr; import com.zimbra.soap.mail.type.NewContactGroupMember; import com.zimbra.soap.mail.type.NewSearchFolderSpec; +import com.zimbra.soap.mail.type.TestDataSource; import com.zimbra.soap.type.AccountSelector; import com.zimbra.soap.type.AccountWithModifications; import com.zimbra.soap.type.CalDataSource; @@ -234,11 +242,10 @@ public class ZMailbox implements ToZJSONObject, MailboxStore { private static final int CALENDAR_FOLDER_ALL = -1; /* Generally set "accountId" explicitly when using another user's auth token, as don't have GetAccountInfo - * capability which is what is normally used to get the account ID. Choosing not to populate accountId - * for other use cases to avoid increasing memory footprint. + * capability which is what is normally used to get the account ID. Note that not always set. */ private String accountId = null; - /* As above, generally set "name" explicitly only when using another user's auth token */ + /* As above, generally set "name" explicitly when using another user's auth token */ private String name = null; private String authName = null; private static final Pattern sAttachmentId = Pattern.compile("\\d+,'.*','(.*)'"); @@ -742,8 +749,10 @@ private void initPreAuth(Options options) { private void initTargetAccount(String key, AccountBy by) { if (AccountBy.id.equals(by)) { mTransport.setTargetAcctId(key); + accountId = key; } else if (AccountBy.name.equals(by)) { mTransport.setTargetAcctName(key); + name = key; } } @@ -4665,9 +4674,11 @@ public void modifyIdentity(ZIdentity identity) throws ServiceException { * @return the new data source id */ public String createDataSource(ZDataSource source) throws ServiceException { - Element req = newRequestElement(MailConstants.CREATE_DATA_SOURCE_REQUEST); - source.toElement(req); - return invoke(req).listElements().get(0).getAttribute(MailConstants.A_ID); + CreateDataSourceRequest req = new CreateDataSourceRequest(); + DataSource jaxbObj = source.toJaxb(); + req.setDataSource(jaxbObj); + CreateDataSourceResponse resp = (CreateDataSourceResponse)invokeJaxb(req); + return resp.getDataSource().getId(); } /** @@ -4676,20 +4687,22 @@ public String createDataSource(ZDataSource source) throws ServiceException { * @return null on success, or the error string on failure */ public String testDataSource(ZDataSource source) throws ServiceException { - Element req = newRequestElement(MailConstants.TEST_DATA_SOURCE_REQUEST); - source.toElement(req); - Element resp = invoke(req); - List children = resp.listElements(); - if (children.size() == 0) { - return MailConstants.TEST_DATA_SOURCE_RESPONSE + " has no child elements"; - } - Element dsEl = children.get(0); - boolean success = dsEl.getAttributeBool(MailConstants.A_DS_SUCCESS, false); - if (!success) { - return resp.getAttribute(MailConstants.A_DS_ERROR, "error"); - } else { - return null; + TestDataSourceRequest req = new TestDataSourceRequest(); + DataSource jaxbObj = source.toJaxb(); + req.setDataSource(jaxbObj); + TestDataSourceResponse resp = (TestDataSourceResponse)invokeJaxb(req); + List dataSources = resp.getDataSources(); + int success = 0; + if(dataSources.size() > 0 && dataSources.get(0) != null) { + TestDataSource ds = dataSources.get(0); + success = ds.getSuccess(); + if(success < 1) { + return ds.getError(); + } else { + return null; + } } + return null; } public List getAllDataSources() throws ServiceException { @@ -4704,6 +4717,8 @@ public List getAllDataSources() throws ServiceException { result.add(new ZCalDataSource((CalDataSource) ds)); } else if (ds instanceof RssDataSource) { result.add(new ZRssDataSource((RssDataSource) ds)); + } else { + result.add(new ZDataSource(ds)); } } return result; @@ -4716,7 +4731,21 @@ public List getAllDataSources() throws ServiceException { */ public ZDataSource getDataSourceById(String id) throws ServiceException { for (ZDataSource ds : getAllDataSources()) { - if (ds.getId().equals(id)) { + if (ds.getId() != null && ds.getId().equals(id)) { + return ds; + } + } + return null; + } + + /** + * Gets a data source by name. + * @return the data source, or null if no data source with the + * given name exists + */ + public ZDataSource getDataSourceByName(String name) throws ServiceException { + for (ZDataSource ds : getAllDataSources()) { + if (ds.getName() != null && ds.getName().equals(name)) { return ds; } } @@ -4724,15 +4753,15 @@ public ZDataSource getDataSourceById(String id) throws ServiceException { } public void modifyDataSource(ZDataSource source) throws ServiceException { - Element req = newRequestElement(MailConstants.MODIFY_DATA_SOURCE_REQUEST); - source.toElement(req); - invoke(req); + ModifyDataSourceRequest req = new ModifyDataSourceRequest(); + req.setDataSource(source.toJaxb()); + invokeJaxb(req); } public void deleteDataSource(ZDataSource source) throws ServiceException { - Element req = newRequestElement(MailConstants.DELETE_DATA_SOURCE_REQUEST); - source.toIdElement(req); - invoke(req); + DeleteDataSourceRequest req = new DeleteDataSourceRequest(); + req.addDataSource(source.toJaxbNameOrId()); + invokeJaxb(req); } public ZFilterRules getIncomingFilterRules() throws ServiceException { @@ -4786,11 +4815,11 @@ public void deleteDataSource(Key.DataSourceBy by, String key) throws ServiceExce } public void importData(List sources) throws ServiceException { - Element req = newRequestElement(MailConstants.IMPORT_DATA_REQUEST); + ImportDataRequest req = new ImportDataRequest(); for (ZDataSource src : sources) { - src.toIdElement(req); + req.addDataSource(src.toJaxbNameOrId()); } - invoke(req); + invokeJaxb(req); } public static class ZImportStatus { @@ -6109,6 +6138,12 @@ public Pair getFolderByPathLongestMatch(String baseFolderItemId break; } folder = subfolder; + if (folder instanceof ZMountpoint) { + if ((i + 1) < segments.length) { + unmatched = StringUtil.join("/", segments, i + 1, segments.length - (i + 1)); + } + break; + } } return new Pair(folder, unmatched); } @@ -6126,19 +6161,13 @@ public Pair getFolderByPathLongestMatch(String baseFolderItemId @Override public ExistingParentFolderStoreAndUnmatchedPart getParentFolderStoreAndUnmatchedPart(OpContext octxt, String path) throws ServiceException { - // Could have based this on getFolderByPathLongestMatch(ZFolder.ID_USER_ROOT, path); - // but that looks less efficient - for deep nesting, creating lots of ZFolders and throwing them away... - // This code borrowed (and moved) from ImapPath.getReferent() if (Strings.isNullOrEmpty(path)) { return new ExistingParentFolderStoreAndUnmatchedPart(getFolderById(ZFolder.ID_USER_ROOT), ""); } try { - for (int index = path.length(); index != -1; index = path.lastIndexOf('/', index - 1)) { - ZFolder zfolder = getFolderByPath(path.substring(0, index)); - if (zfolder != null) { - String subpathRemote = path.substring(Math.min(path.length(), index + 1)); - return new ExistingParentFolderStoreAndUnmatchedPart(zfolder, subpathRemote); - } + Pair pair = getFolderByPathLongestMatch(ZFolder.ID_USER_ROOT, path); + if (pair != null) { + return new ExistingParentFolderStoreAndUnmatchedPart(pair.getFirst(), pair.getSecond()); } } catch (ServiceException e) {} return new ExistingParentFolderStoreAndUnmatchedPart(getFolderById(ZFolder.ID_USER_ROOT), path); diff --git a/client/src/java/com/zimbra/client/ZPop3DataSource.java b/client/src/java/com/zimbra/client/ZPop3DataSource.java index 0201926639a..4ea0e2e70be 100644 --- a/client/src/java/com/zimbra/client/ZPop3DataSource.java +++ b/client/src/java/com/zimbra/client/ZPop3DataSource.java @@ -19,19 +19,21 @@ import org.json.JSONException; -import com.zimbra.soap.admin.type.DataSourceType; import com.zimbra.common.soap.Element; import com.zimbra.common.soap.MailConstants; import com.zimbra.common.util.SystemUtil; import com.zimbra.common.util.ZimbraLog; import com.zimbra.soap.account.type.AccountPop3DataSource; +import com.zimbra.soap.admin.type.DataSourceType; +import com.zimbra.soap.mail.type.DataSourceNameOrId; +import com.zimbra.soap.mail.type.MailPop3DataSource; +import com.zimbra.soap.mail.type.Pop3DataSourceNameOrId; +import com.zimbra.soap.type.DataSource; import com.zimbra.soap.type.DataSource.ConnectionType; import com.zimbra.soap.type.DataSources; import com.zimbra.soap.type.Pop3DataSource; -public class ZPop3DataSource implements ZDataSource, ToZJSONObject { - - private Pop3DataSource data; +public class ZPop3DataSource extends ZDataSource implements ToZJSONObject { public ZPop3DataSource(Pop3DataSource data) { this.data = DataSources.newPop3DataSource(data); @@ -52,6 +54,7 @@ public ZPop3DataSource(String name, boolean enabled, String host, int port, setLeaveOnServer(leaveOnServer); } + @Deprecated public Element toElement(Element parent) { Element src = parent.addElement(MailConstants.E_DS_POP3); src.addAttribute(MailConstants.A_ID, data.getId()); @@ -63,26 +66,44 @@ public Element toElement(Element parent) { src.addAttribute(MailConstants.A_DS_PASSWORD, data.getPassword()); src.addAttribute(MailConstants.A_FOLDER, data.getFolderId()); src.addAttribute(MailConstants.A_DS_CONNECTION_TYPE, data.getConnectionType().name()); - src.addAttribute(MailConstants.A_DS_LEAVE_ON_SERVER, data.isLeaveOnServer()); + src.addAttribute(MailConstants.A_DS_LEAVE_ON_SERVER, leaveOnServer()); ZimbraLog.test.info("XXX bburtin: " + src.prettyPrint()); return src; } + @Deprecated public Element toIdElement(Element parent) { Element src = parent.addElement(MailConstants.E_DS_POP3); src.addAttribute(MailConstants.A_ID, getId()); return src; } - public DataSourceType getType() { return DataSourceType.pop3; } - - public String getId() { return data.getId(); } - - public String getName() { return data.getName(); } - public void setName(String name) { data.setName(name); } + @Override + public DataSource toJaxb() { + MailPop3DataSource jaxbObject = new MailPop3DataSource(); + jaxbObject.setId(data.getId()); + jaxbObject.setName(data.getName()); + jaxbObject.setHost(data.getHost()); + jaxbObject.setPort(data.getPort()); + jaxbObject.setUsername(data.getUsername()); + jaxbObject.setPassword(data.getPassword()); + jaxbObject.setFolderId(data.getFolderId()); + jaxbObject.setConnectionType(data.getConnectionType()); + jaxbObject.setLeaveOnServer(leaveOnServer()); + jaxbObject.setEnabled(data.isEnabled()); + return jaxbObject; + } - public boolean isEnabled() { return SystemUtil.coalesce(data.isEnabled(), Boolean.FALSE); } - public void setEnabled(boolean enabled) { data.setEnabled(enabled); } + @Override + public DataSourceNameOrId toJaxbNameOrId() { + Pop3DataSourceNameOrId jaxbObject = Pop3DataSourceNameOrId.createForId(data.getId()); + return jaxbObject; + } + private Pop3DataSource getData() { + return ((Pop3DataSource)data); + } + @Override + public DataSourceType getType() { return DataSourceType.pop3; } public String getHost() { return data.getHost(); } public void setHost(String host) { data.setHost(host); } @@ -106,11 +127,11 @@ public void setConnectionType(ConnectionType connectionType) { } public boolean leaveOnServer() { - Boolean val = data.isLeaveOnServer(); + Boolean val = getData().isLeaveOnServer(); return (val == null ? true : val); } - public void setLeaveOnServer(boolean leaveOnServer) { data.setLeaveOnServer(leaveOnServer); } + public void setLeaveOnServer(boolean leaveOnServer) { getData().setLeaveOnServer(leaveOnServer); } public ZJSONObject toZJSONObject() throws JSONException { ZJSONObject zjo = new ZJSONObject(); @@ -122,7 +143,7 @@ public ZJSONObject toZJSONObject() throws JSONException { zjo.put("username", data.getUsername()); zjo.put("folderId", data.getFolderId()); zjo.put("connectionType", data.getConnectionType().toString()); - zjo.put("leaveOnServer", data.isLeaveOnServer()); + zjo.put("leaveOnServer", leaveOnServer()); return zjo; } diff --git a/client/src/java/com/zimbra/client/ZRssDataSource.java b/client/src/java/com/zimbra/client/ZRssDataSource.java index 0ee008d2009..d54c18ede36 100644 --- a/client/src/java/com/zimbra/client/ZRssDataSource.java +++ b/client/src/java/com/zimbra/client/ZRssDataSource.java @@ -18,18 +18,19 @@ import org.json.JSONException; -import com.zimbra.soap.admin.type.DataSourceType; import com.zimbra.common.soap.Element; import com.zimbra.common.soap.MailConstants; -import com.zimbra.common.util.SystemUtil; +import com.zimbra.soap.admin.type.DataSourceType; +import com.zimbra.soap.mail.type.DataSourceNameOrId; +import com.zimbra.soap.mail.type.MailRssDataSource; +import com.zimbra.soap.mail.type.RssDataSourceNameOrId; +import com.zimbra.soap.type.DataSource; import com.zimbra.soap.type.DataSources; import com.zimbra.soap.type.RssDataSource; -public class ZRssDataSource implements ZDataSource, ToZJSONObject { +public class ZRssDataSource extends ZDataSource implements ToZJSONObject { - private RssDataSource data; - public ZRssDataSource(String name, String folderId, boolean enabled) { data = DataSources.newRssDataSource(); data.setName(name); @@ -40,15 +41,8 @@ public ZRssDataSource(String name, String folderId, boolean enabled) { public ZRssDataSource(RssDataSource data) { this.data = DataSources.newRssDataSource(data); } - - public String getId() { - return data.getId(); - } - - public String getName() { - return data.getName(); - } + @Override public DataSourceType getType() { return DataSourceType.rss; } @@ -57,10 +51,7 @@ public String getFolderId() { return data.getFolderId(); } - public boolean isEnabled() { - return SystemUtil.coalesce(data.isEnabled(), Boolean.FALSE); - } - + @Deprecated public Element toElement(Element parent) { Element src = parent.addElement(MailConstants.E_DS_RSS); src.addAttribute(MailConstants.A_ID, data.getId()); @@ -70,12 +61,29 @@ public Element toElement(Element parent) { return src; } + @Deprecated public Element toIdElement(Element parent) { Element src = parent.addElement(MailConstants.E_DS_RSS); src.addAttribute(MailConstants.A_ID, getId()); return src; } + @Override + public DataSource toJaxb() { + MailRssDataSource jaxbObject = new MailRssDataSource(); + jaxbObject.setId(data.getId()); + jaxbObject.setName(data.getName()); + jaxbObject.setFolderId(data.getFolderId()); + jaxbObject.setEnabled(data.isEnabled()); + return jaxbObject; + } + + @Override + public DataSourceNameOrId toJaxbNameOrId() { + RssDataSourceNameOrId jaxbObject = RssDataSourceNameOrId.createForId(data.getId()); + return jaxbObject; + } + public ZJSONObject toZJSONObject() throws JSONException { ZJSONObject zjo = new ZJSONObject(); zjo.put("id", data.getId()); diff --git a/common/ivy.xml b/common/ivy.xml index df9a82c14a5..36236c123ec 100644 --- a/common/ivy.xml +++ b/common/ivy.xml @@ -8,6 +8,7 @@ + diff --git a/common/src/java-test/com/zimbra/common/calendar/Ical4JTest.java b/common/src/java-test/com/zimbra/common/calendar/Ical4JTest.java new file mode 100644 index 00000000000..98a62ea191c --- /dev/null +++ b/common/src/java-test/com/zimbra/common/calendar/Ical4JTest.java @@ -0,0 +1,170 @@ +/* + * ***** BEGIN LICENSE BLOCK ***** + * Zimbra Collaboration Suite Server + * Copyright (C) 2017 Synacor, Inc. + * + * This program is free software: you can redistribute it and/or modify it under + * the terms of the GNU General Public License as published by the Free Software Foundation, + * version 2 of the License. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * You should have received a copy of the GNU General Public License along with this program. + * If not, see . + * ***** END LICENSE BLOCK ***** + */ + +package com.zimbra.common.calendar; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.util.List; + +import org.junit.Assert; +import org.junit.Test; + +import com.google.common.base.Charsets; +import com.zimbra.common.calendar.ZCalendar.ICalTok; +import com.zimbra.common.calendar.ZCalendar.ZComponent; +import com.zimbra.common.calendar.ZCalendar.ZParameter; +import com.zimbra.common.calendar.ZCalendar.ZProperty; +import com.zimbra.common.calendar.ZCalendar.ZVCalendar; +import com.zimbra.common.service.ServiceException; + +import net.fortuna.ical4j.data.ParserException; +import net.fortuna.ical4j.util.Uris; + +/** + * Intended to test features that Zimbra has chosen to add to ical4j in the past, to ensure that future + * integrations still function correctly. + * + */ +public class Ical4JTest { + + public static final String emptyCN = + "BEGIN:VCALENDAR\r\n" + + "VERSION:2.0\r\n" + + "PRODID:Microsoft Exchange Server 2007\r\n" + + "BEGIN:VEVENT\r\n" + + "ORGANIZER;CN=:MAILTO:test1@invalid.dom\r\n" + + "ATTENDEE;ROLE=REQ-PARTICIPANT;PARTSTAT=NEEDS-ACTION;RSVP=TRUE;CN=:MAILTO:te\r\n" + + " st2@invalid.dom\r\n" + + "SUMMARY;LANGUAGE=en-US:Testing Empty CNs\r\n" + + "UID:U4\r\n" + + "DTSTAMP:20070228T183803Z\r\n" + + "DTSTART;VALUE=DATE:20051018\r\n" + + "DTEND;VALUE=DATE:20051019\r\n" + + "END:VEVENT\r\n" + + "END:VCALENDAR\r\n"; + + public static final String multiVcalendar = + "BEGIN:VCALENDAR\r\n" + + "VERSION:2.0\r\n" + + "PRODID:Oracle/Oracle Calendar Server 9.0.4.2.8\r\n" + + "BEGIN:VEVENT\r\n" + + "UID:U1\r\n" + + "DTSTAMP:20070228T183803Z\r\n" + + "DTSTART;VALUE=DATE:20051018\r\n" + + "DTEND;VALUE=DATE:20051019\r\n" + + "SUMMARY:event in cal1\r\n" + + "END:VEVENT\r\n" + + "END:VCALENDAR\r\n" + + "BEGIN:VCALENDAR\r\n" + + "VERSION:2.0\r\n" + + "PRODID:Oracle/Oracle Calendar Server 9.0.4.2.8\r\n" + + "BEGIN:VEVENT\r\n" + + "UID:U2\r\n" + + "DTSTAMP:20070228T183800Z\r\n" + + "DTSTART;VALUE=DATE:20051019\r\n" + + "DTEND;VALUE=DATE:20051020\r\n" + + "SUMMARY:event in cal2\r\n" + + "END:VEVENT\r\n" + + "END:VCALENDAR\r\n"; + + public static final String wrappedWithTab = + "BEGIN:VCALENDAR\r\n" + + "VERSION:2.0\r\n" + + "PRODID:-//Microsoft Corporation//Outlook 12.0 MIMEDIR//EN\r\n" + + "BEGIN:VEVENT\r\n" + + "DESCRIPTION:When: Friday\\, March 30\\, 2007 8:00 AM-8:30 AM (GMT-07:00) Moun\r\n" + + "\ttain Time (US & Canada).\\nWhere: Certified Service\\, Inc. \\n\\n*~*~*~*~*~*~\r\n" + + "\t*~*~*~*\\n\\nGood morning Ray\\, \\n\\nFriday works for me. Just let me know w\r\n" + + "\that time. \\n\\nThanks\\, \\n\\r\\n\r\n" + + "UID:U1\r\n" + + "DTSTAMP:20070228T183803Z\r\n" + + "DTSTART;VALUE=DATE:20051018\r\n" + + "DTEND;VALUE=DATE:20051019\r\n" + + "SUMMARY:mySumm\r\n" + + "END:VEVENT\r\n" + + "END:VCALENDAR\r\n"; + + /** + * Uri-encoding was causing problems with java.net.URL building (specifically we would end up + * with URL's where the SchemeSpecificPart was "MAILTO%3Afoo%40bar.com") + */ + @Test + public void testUrisEncodeDecode() { + String mailtourl = "mailto:foobar@example.net"; + String decoded = Uris.decode(mailtourl); + Assert.assertEquals("Result of Decode", mailtourl, decoded); + String encoded = Uris.encode(mailtourl); + /** + * This was failing on baseline ical4j-0.9.16 with: + * junit.framework.ComparisonFailure: Result of Encode expected: + * but was: + */ + Assert.assertEquals("Result of Encode", mailtourl, encoded); + mailtourl = "rubbishfoobar@example.net"; + decoded = Uris.decode(mailtourl); + Assert.assertEquals("Result of Decode", mailtourl, decoded); + encoded = Uris.encode(mailtourl); + Assert.assertEquals("Result of Encode", mailtourl, encoded); + } + + @Test + public void testMultiVCALENDAR() throws IOException, ParserException, ServiceException { + List zvcals = doParse(multiVcalendar); + Assert.assertNotNull("List of ZVCalendar", zvcals); + Assert.assertEquals("Number of cals", 2, zvcals.size()); + } + + @Test + public void testTabWrappedLine() throws IOException, ParserException, ServiceException { + List zvcals = doParse(wrappedWithTab); + Assert.assertNotNull("List of ZVCalendar", zvcals); + Assert.assertEquals("Number of cals", 1, zvcals.size()); + } + + /** + * Bug 50398 - handling of empty CN parameter + * An empty CN parameter should not affect the value of a property. With unpatched ical4j-0.9.16 it did. + * It is acceptable to either drop the CN parameter or leave it as the empty string + */ + @Test + public void testEmptyCN() throws IOException, ParserException, ServiceException { + List zvcals = doParse(emptyCN); + Assert.assertNotNull("List of ZVCalendar", zvcals); + Assert.assertEquals("Number of cals", 1, zvcals.size()); + ZVCalendar zvcal = zvcals.get(0); + ZComponent vevent = zvcal.getComponent(ICalTok.VEVENT); + Assert.assertNotNull("VEVENT", vevent); + ZProperty orgProp = vevent.getProperty(ICalTok.ORGANIZER); + // With unpatched ical4j-0.9.16 value is ":test1@invalid.dom" + Assert.assertEquals("ORGANIZER value", "MAILTO:test1@invalid.dom", orgProp.getValue()); + ZParameter orgCNparam = orgProp.getParameter(ICalTok.CN); + Assert.assertNotNull("ORGANIZER CN parameter", orgCNparam); + String cnValue = orgCNparam.getValue(); + if (cnValue != null) { + // Note that with ical4j-0.9.16-patched, the value is null + Assert.assertEquals("ORGANIZER CN param value", "", cnValue); + } + } + + public static List doParse(String ical) + throws IOException, ParserException, ServiceException { + ByteArrayInputStream bais = new ByteArrayInputStream (ical.getBytes(Charsets.UTF_8)); + List zvcals = ZCalendar.ZCalendarBuilder.buildMulti(bais, Charsets.UTF_8.name()); + return zvcals; + } +} diff --git a/common/src/java-test/com/zimbra/common/zmime/ZMimeBodyPartTest.java b/common/src/java-test/com/zimbra/common/zmime/ZMimeBodyPartTest.java index 626049af987..75fbf361a46 100644 --- a/common/src/java-test/com/zimbra/common/zmime/ZMimeBodyPartTest.java +++ b/common/src/java-test/com/zimbra/common/zmime/ZMimeBodyPartTest.java @@ -1,7 +1,7 @@ /* * ***** BEGIN LICENSE BLOCK ***** * Zimbra Collaboration Suite Server - * Copyright (C) 2011, 2012, 2013, 2014, 2016 Synacor, Inc. + * Copyright (C) 2011, 2012, 2013, 2014, 2016, 2017 Synacor, Inc. * * This program is free software: you can redistribute it and/or modify it under * the terms of the GNU General Public License as published by the Free Software Foundation, @@ -16,6 +16,7 @@ */ package com.zimbra.common.zmime; +import org.apache.commons.lang.StringUtils; import org.junit.Assert; import org.junit.BeforeClass; import org.junit.Test; @@ -84,10 +85,9 @@ public void encoding() throws Exception { testEncodingSelection("single NUL", "cyb\u0000le illus.", ZTransferEncoding.QUOTED_PRINTABLE); testEncodingSelection("single control char", "ESCAPE ME \u001B", ZTransferEncoding.SEVEN_BIT); - StringBuilder sb = new StringBuilder(1000); - for (int i = 0; i < 1000; i++) { - sb.append("X"); - } - testEncodingSelection("line too long", sb.toString(), ZTransferEncoding.QUOTED_PRINTABLE); + testEncodingSelection("line too long (ascii)", StringUtils.leftPad("", 1000, "X"), ZTransferEncoding.QUOTED_PRINTABLE); + testEncodingSelection("line too long (JIS)", new String(StringUtils.leftPad("", 1000, "あ").getBytes("ISO-2022-JP")), ZTransferEncoding.QUOTED_PRINTABLE); + testEncodingSelection("line too long (utf-8)", new String(StringUtils.leftPad("", 1000, "あ").getBytes("UTF-8")), ZTransferEncoding.BASE64); + testEncodingSelection("line too long (shift-jis)", new String(StringUtils.leftPad("", 1000, "あ").getBytes("Shift-JIS")), ZTransferEncoding.BASE64); } } diff --git a/common/src/java/com/zimbra/common/account/ZAttrProvisioning.java b/common/src/java/com/zimbra/common/account/ZAttrProvisioning.java index e1a91011f84..3ff02825f13 100644 --- a/common/src/java/com/zimbra/common/account/ZAttrProvisioning.java +++ b/common/src/java/com/zimbra/common/account/ZAttrProvisioning.java @@ -440,6 +440,26 @@ public static DomainType fromString(String s) throws ServiceException { public boolean isAlias() { return this == alias;} } + public static enum FeatureAddressVerificationStatus { + verified("verified"), + pending("pending"), + failed("failed"), + expired("expired"); + private String mValue; + private FeatureAddressVerificationStatus(String value) { mValue = value; } + public String toString() { return mValue; } + public static FeatureAddressVerificationStatus fromString(String s) throws ServiceException { + for (FeatureAddressVerificationStatus value : values()) { + if (value.mValue.equals(s)) return value; + } + throw ServiceException.INVALID_REQUEST("invalid value: "+s+", valid values: "+ Arrays.asList(values()), null); + } + public boolean isVerified() { return this == verified;} + public boolean isPending() { return this == pending;} + public boolean isFailed() { return this == failed;} + public boolean isExpired() { return this == expired;} + } + public static enum FeatureSocialFiltersEnabled { SocialCast("SocialCast"), LinkedIn("LinkedIn"), @@ -6092,6 +6112,42 @@ public static TwoFactorAuthSecretEncoding fromString(String s) throws ServiceExc @ZAttr(id=1244) public static final String A_zimbraExternalUserMailAddress = "zimbraExternalUserMailAddress"; + /** + * RFC822 email address under verification for an account + * + * @since ZCS 8.8.5 + */ + @ZAttr(id=2128) + public static final String A_zimbraFeatureAddressUnderVerification = "zimbraFeatureAddressUnderVerification"; + + /** + * Enable end-user email address verification + * + * @since ZCS 8.8.5 + */ + @ZAttr(id=2126) + public static final String A_zimbraFeatureAddressVerificationEnabled = "zimbraFeatureAddressVerificationEnabled"; + + /** + * Expiry time for end-user email address verification. Must be in valid + * duration format: {digits}{time-unit}. digits: 0-9, time-unit: + * [hmsd]|ms. h - hours, m - minutes, s - seconds, d - days, ms - + * milliseconds. If time unit is not specified, the default is + * s(seconds). + * + * @since ZCS 8.8.5 + */ + @ZAttr(id=2127) + public static final String A_zimbraFeatureAddressVerificationExpiry = "zimbraFeatureAddressVerificationExpiry"; + + /** + * End-user email address verification status + * + * @since ZCS 8.8.5 + */ + @ZAttr(id=2129) + public static final String A_zimbraFeatureAddressVerificationStatus = "zimbraFeatureAddressVerificationStatus"; + /** * whether email features and tabs are enabled in the web client if * accessed from the admin console @@ -6232,6 +6288,30 @@ public static TwoFactorAuthSecretEncoding fromString(String s) throws ServiceExc @ZAttr(id=806) public static final String A_zimbraFeatureConfirmationPageEnabled = "zimbraFeatureConfirmationPageEnabled"; + /** + * Sleep time between subsequent contact backups. 0 means that contact + * backup is disabled. . Must be in valid duration format: + * {digits}{time-unit}. digits: 0-9, time-unit: [hmsd]|ms. h - hours, m - + * minutes, s - seconds, d - days, ms - milliseconds. If time unit is not + * specified, the default is s(seconds). + * + * @since ZCS 8.8.5 + */ + @ZAttr(id=2124) + public static final String A_zimbraFeatureContactBackupFrequency = "zimbraFeatureContactBackupFrequency"; + + /** + * Duration for which the backups should be preserved. . Must be in valid + * duration format: {digits}{time-unit}. digits: 0-9, time-unit: + * [hmsd]|ms. h - hours, m - minutes, s - seconds, d - days, ms - + * milliseconds. If time unit is not specified, the default is + * s(seconds). + * + * @since ZCS 8.8.5 + */ + @ZAttr(id=2125) + public static final String A_zimbraFeatureContactBackupLifeTime = "zimbraFeatureContactBackupLifeTime"; + /** * whether detailed contact search UI is enabled * @@ -6531,6 +6611,14 @@ public static TwoFactorAuthSecretEncoding fromString(String s) throws ServiceExc @ZAttr(id=1127) public static final String A_zimbraFeatureMAPIConnectorEnabled = "zimbraFeatureMAPIConnectorEnabled"; + /** + * Mark messages sent to a forwarding address as read + * + * @since ZCS 8.8.5 + */ + @ZAttr(id=2123) + public static final String A_zimbraFeatureMarkMailForwardedAsRead = "zimbraFeatureMarkMailForwardedAsRead"; + /** * Whether to enable Zimbra Mobile Gateway feature * @@ -11485,13 +11573,23 @@ public static TwoFactorAuthSecretEncoding fromString(String s) throws ServiceExc public static final String A_zimbraNetworkActivation = "zimbraNetworkActivation"; /** - * Whether to enable old zimbra network admin module. + * Deprecated since: 8.8.5. This attribute has been renamed to + * zimbraNetworkAdminNGEnabled. Orig desc: Whether to enable old zimbra + * network admin module. * * @since ZCS 8.8.2 */ @ZAttr(id=2119) public static final String A_zimbraNetworkAdminEnabled = "zimbraNetworkAdminEnabled"; + /** + * Whether to enable zimbra network new generation admin module. + * + * @since ZCS 8.8.5 + */ + @ZAttr(id=2130) + public static final String A_zimbraNetworkAdminNGEnabled = "zimbraNetworkAdminNGEnabled"; + /** * Contents of a signed Zimbra license key - an XML string. */ diff --git a/common/src/java/com/zimbra/common/httpclient/ZimbraHttpClientManager.java b/common/src/java/com/zimbra/common/httpclient/ZimbraHttpClientManager.java index 6aabf20684f..b55a48634b3 100644 --- a/common/src/java/com/zimbra/common/httpclient/ZimbraHttpClientManager.java +++ b/common/src/java/com/zimbra/common/httpclient/ZimbraHttpClientManager.java @@ -30,12 +30,14 @@ public class ZimbraHttpClientManager { protected static ZimbraHttpClientManager instance; - final CloseableHttpAsyncClient internalAsyncClient; - final CloseableHttpClient internalClient; - final PoolingHttpClientConnectionManager internalConnectionMgr; - final RequestConfig internalRequestConfig; - + private final CloseableHttpAsyncClient internalAsyncClient; + private final CloseableHttpClient internalClient; + private final CloseableHttpClient externalClient; public ZimbraHttpClientManager() { + PoolingHttpClientConnectionManager internalConnectionMgr; + PoolingHttpClientConnectionManager externallConnectionMgr; + RequestConfig internalRequestConfig; + RequestConfig externalRequestConfig; SSLContext sslcontext = null; try { sslcontext = SSLContexts.custom().loadTrustMaterial(CustomTrustManager.loadKeyStore(), new TrustSelfSignedStrategy()).build(); @@ -68,6 +70,21 @@ public ZimbraHttpClientManager() { .setTcpNoDelay(LC.httpclient_internal_connmgr_tcp_nodelay.booleanValue()) .build()) .build(); + externallConnectionMgr = new PoolingHttpClientConnectionManager(); + externallConnectionMgr.setDefaultMaxPerRoute(LC.httpclient_external_connmgr_max_host_connections.intValue()); + externallConnectionMgr.setMaxTotal(LC.httpclient_external_connmgr_max_total_connections.intValue()); + externalRequestConfig = RequestConfig.custom(). + setConnectTimeout(LC.httpclient_external_connmgr_connection_timeout.intValue()) + .setSocketTimeout(LC.httpclient_external_connmgr_so_timeout.intValue()) + .setStaleConnectionCheckEnabled(LC.httpclient_external_connmgr_stale_connection_check.booleanValue()) + .build(); + externalClient = HttpClientBuilder.create() + .setConnectionManager(externallConnectionMgr) + .setDefaultRequestConfig(externalRequestConfig) + .setDefaultSocketConfig(SocketConfig.custom() + .setTcpNoDelay(LC.httpclient_external_connmgr_tcp_nodelay.booleanValue()) + .build()) + .build(); } public static synchronized ZimbraHttpClientManager getInstance() { @@ -91,6 +108,10 @@ public CloseableHttpClient getInternalHttpClient() { return internalClient; } + public CloseableHttpClient getExternalHttpClient() { + return externalClient; + } + /** * orderly shutdown the client */ @@ -98,5 +119,6 @@ public CloseableHttpClient getInternalHttpClient() { public void shutDown() throws IOException { internalAsyncClient.close(); internalClient.close(); + externalClient.close(); } } diff --git a/common/src/java/com/zimbra/common/service/ServiceException.java b/common/src/java/com/zimbra/common/service/ServiceException.java index 9c56cdd0846..e95574e50e9 100644 --- a/common/src/java/com/zimbra/common/service/ServiceException.java +++ b/common/src/java/com/zimbra/common/service/ServiceException.java @@ -432,7 +432,7 @@ public static ServiceException OPERATION_DENIED(String message) { return new ServiceException("operation denied: "+message, OPERATION_DENIED, SENDERS_FAULT); } - public static ServiceException NETWORK_MODULES_NG_ENABLED() { - return new ServiceException("ZimbraNetworkModulesNGEnabled is true", ZIMBRA_NETWORK_MODULES_NG_ENABLED, RECEIVERS_FAULT); + public static ServiceException NETWORK_MODULES_NG_ENABLED(String str) { + return new ServiceException("ZimbraNetworkModulesNG: "+ str + " is not enabled.", ZIMBRA_NETWORK_MODULES_NG_ENABLED, RECEIVERS_FAULT); } } diff --git a/common/src/java/com/zimbra/common/soap/AccountConstants.java b/common/src/java/com/zimbra/common/soap/AccountConstants.java index f37415d3278..74260381da4 100644 --- a/common/src/java/com/zimbra/common/soap/AccountConstants.java +++ b/common/src/java/com/zimbra/common/soap/AccountConstants.java @@ -558,4 +558,11 @@ public class AccountConstants { public static final String A_CONSUMER_APP_NAME = "appName"; public static final String A_APPROVED_ON = "approvedOn"; public static final String A_CONSUMER_DEVICE = "device"; + + //ext user prov URL metadata constants + public static final String P_ACCOUNT_ID = "aid"; + public static final String P_FOLDER_ID = "fid"; + public static final String P_LINK_EXPIRY = "exp"; + public static final String P_EMAIL = "email"; + public static final String P_ADDRESS_VERIFICATION = "address-verification"; } diff --git a/common/src/java/com/zimbra/common/soap/MailConstants.java b/common/src/java/com/zimbra/common/soap/MailConstants.java index 86788339883..82f6bd6379b 100644 --- a/common/src/java/com/zimbra/common/soap/MailConstants.java +++ b/common/src/java/com/zimbra/common/soap/MailConstants.java @@ -133,6 +133,10 @@ private MailConstants() { public static final String E_MODIFY_CONTACT_RESPONSE = "ModifyContactResponse"; public static final String E_GET_CONTACTS_REQUEST = "GetContactsRequest"; public static final String E_GET_CONTACTS_RESPONSE = "GetContactsResponse"; + public static final String E_GET_CONTACT_BACKUP_LIST_REQUEST = "GetContactBackupListRequest"; + public static final String E_GET_CONTACT_BACKUP_LIST_RESPONSE = "GetContactBackupListResponse"; + public static final String E_BACKUPS = "backups"; + public static final String E_BACKUP = "backup"; public static final String E_IMPORT_CONTACTS_REQUEST = "ImportContactsRequest"; public static final String E_IMPORT_CONTACTS_RESPONSE = "ImportContactsResponse"; public static final String E_EXPORT_CONTACTS_REQUEST = "ExportContactsRequest"; @@ -395,6 +399,8 @@ private MailConstants() { public static final QName EXPORT_CONTACTS_RESPONSE = QName.get(E_EXPORT_CONTACTS_RESPONSE, NAMESPACE); public static final QName CONTACT_ACTION_REQUEST = QName.get(E_CONTACT_ACTION_REQUEST, NAMESPACE); public static final QName CONTACT_ACTION_RESPONSE = QName.get(E_CONTACT_ACTION_RESPONSE, NAMESPACE); + public static final QName GET_CONTACT_BACKUP_LIST_REQUEST = QName.get(E_GET_CONTACT_BACKUP_LIST_REQUEST, NAMESPACE); + public static final QName GET_CONTACT_BACKUP_LIST_RESPONSE = QName.get(E_GET_CONTACT_BACKUP_LIST_RESPONSE, NAMESPACE); // notes public static final QName CREATE_NOTE_REQUEST = QName.get(E_CREATE_NOTE_REQUEST, NAMESPACE); public static final QName CREATE_NOTE_RESPONSE = QName.get(E_CREATE_NOTE_RESPONSE, NAMESPACE); @@ -679,6 +685,7 @@ private MailConstants() { public static final String A_STRING_COMPARISON = "stringComparison"; public static final String A_VALUE_COMPARISON = "valueComparison"; public static final String A_COUNT_COMPARISON = "countComparison"; + public static final String A_VALUE_COMPARISON_COMPARATOR = "valueComparisonComparator"; public static final String A_CASE_SENSITIVE = "caseSensitive"; public static final String A_NUMBER_COMPARISON = "numberComparison"; public static final String A_DATE_COMPARISON = "dateComparison"; @@ -1332,4 +1339,13 @@ private MailConstants() { public static final String OP_INHERIT = "inherit"; public static final String OP_MUTE = "mute"; public static final String OP_RESET_IMAP_UID = "resetimapuid"; + + // Contacts API + public static final String E_RESTORE_CONTACTS_REQUEST = "RestoreContactsRequest"; + public static final String E_RESTORE_CONTACTS_RESPONSE = "RestoreContactsResponse"; + public static final QName RESTORE_CONTACTS_REQUEST = QName.get(E_RESTORE_CONTACTS_REQUEST, NAMESPACE); + public static final QName RESTORE_CONTACTS_RESPONSE = QName.get(E_RESTORE_CONTACTS_RESPONSE, NAMESPACE); + public static final String A_CONTACTS_BACKUP_FILE_NAME = "contactsBackupFileName"; + public static final String A_CONTACTS_RESTORE_RESOLVE = "resolve"; + public static final String A_CONTACTS_BACKUP_FOLDER_NAME = "ContactsBackup"; } diff --git a/common/src/java/com/zimbra/common/util/L10nUtil.java b/common/src/java/com/zimbra/common/util/L10nUtil.java index a05b79bebde..25d4684e88f 100644 --- a/common/src/java/com/zimbra/common/util/L10nUtil.java +++ b/common/src/java/com/zimbra/common/util/L10nUtil.java @@ -274,8 +274,12 @@ public static enum MsgKey { //sieve seiveRejectMDNSubject, - seiveRejectMDNErrorMsg + seiveRejectMDNErrorMsg, + //forwarding email address verification + verifyEmailSubject, + verifyEmailBodyText, + verifyEmailBodyHtml // add other messages in the future... } diff --git a/common/src/java/com/zimbra/common/zmime/ZMimeBodyPart.java b/common/src/java/com/zimbra/common/zmime/ZMimeBodyPart.java index 5d96890e652..37dcb69f844 100644 --- a/common/src/java/com/zimbra/common/zmime/ZMimeBodyPart.java +++ b/common/src/java/com/zimbra/common/zmime/ZMimeBodyPart.java @@ -1,7 +1,7 @@ /* * ***** BEGIN LICENSE BLOCK ***** * Zimbra Collaboration Suite Server - * Copyright (C) 2011, 2012, 2013, 2014, 2016 Synacor, Inc. + * Copyright (C) 2011, 2012, 2013, 2014, 2016, 2017 Synacor, Inc. * * This program is free software: you can redistribute it and/or modify it under * the terms of the GNU General Public License as published by the Free Software Foundation, @@ -412,7 +412,7 @@ String getEncoding(boolean isAttachment, boolean isText) { } else if (toolong == 0) { return "8bit"; //section 6.2 of RFC2045 } else { - return "binary"; + return "base64"; } } } diff --git a/soap/src/java/com/zimbra/soap/JaxbUtil.java b/soap/src/java/com/zimbra/soap/JaxbUtil.java index bef012f65fd..2adf23b8403 100644 --- a/soap/src/java/com/zimbra/soap/JaxbUtil.java +++ b/soap/src/java/com/zimbra/soap/JaxbUtil.java @@ -297,6 +297,8 @@ public final class JaxbUtil { com.zimbra.soap.mail.message.GetCalendarItemSummariesResponse.class, com.zimbra.soap.mail.message.GetCommentsRequest.class, com.zimbra.soap.mail.message.GetCommentsResponse.class, + com.zimbra.soap.mail.message.GetContactBackupListRequest.class, + com.zimbra.soap.mail.message.GetContactBackupListResponse.class, com.zimbra.soap.mail.message.GetContactsRequest.class, com.zimbra.soap.mail.message.GetContactsResponse.class, com.zimbra.soap.mail.message.GetConvRequest.class, @@ -1112,7 +1114,11 @@ public final class JaxbUtil { com.zimbra.soap.mail.message.GetLastItemIdInMailboxRequest.class, com.zimbra.soap.mail.message.GetLastItemIdInMailboxResponse.class, com.zimbra.soap.mail.message.BeginTrackingIMAPRequest.class, - com.zimbra.soap.mail.message.BeginTrackingIMAPResponse.class + com.zimbra.soap.mail.message.BeginTrackingIMAPResponse.class, + + //Contacts API + com.zimbra.soap.mail.message.RestoreContactsRequest.class, + com.zimbra.soap.mail.message.RestoreContactsResponse.class }; try { diff --git a/soap/src/java/com/zimbra/soap/account/type/AccountDataSource.java b/soap/src/java/com/zimbra/soap/account/type/AccountDataSource.java index 0dec69dbbc7..21634e6acdd 100644 --- a/soap/src/java/com/zimbra/soap/account/type/AccountDataSource.java +++ b/soap/src/java/com/zimbra/soap/account/type/AccountDataSource.java @@ -38,8 +38,7 @@ @XmlAccessorType(XmlAccessType.NONE) @XmlType(propOrder = {"lastError", "attributes"}) @XmlRootElement -public class AccountDataSource -implements DataSource { +public class AccountDataSource implements DataSource { /** * @zm-api-field-tag data-source-id @@ -209,6 +208,19 @@ public class AccountDataSource @XmlElement(name=MailConstants.E_ATTRIBUTE /* a */, required=false) private List attributes = Lists.newArrayList(); + /** + * @zm-api-field-tag data-source-refreshToken + * @zm-api-field-description refresh token for refreshing data source oauth token + */ + @XmlAttribute(name = MailConstants.A_DS_REFRESH_TOKEN /* refreshToken */, required = false) + private String refreshToken; + + /** + * @zm-api-field-tag data-source-refreshTokenUrl + * @zm-api-field-description refreshTokenUrl for refreshing data source oauth token + */ + @XmlAttribute(name = MailConstants.A_DS_REFRESH_TOKEN_URL /* refreshTokenUrl */, required = false) + private String refreshTokenUrl; public AccountDataSource() { } @@ -241,6 +253,8 @@ public void copy(DataSource from) { importClass = from.getImportClass(); failingSince = from.getFailingSince(); lastError = from.getLastError(); + refreshToken = from.getRefreshToken(); + refreshTokenUrl = from.getRefreshTokenUrl(); setAttributes(from.getAttributes()); } @@ -347,12 +361,18 @@ public void addAttribute(String attribute) { public List getAttributes() { return Collections.unmodifiableList(attributes); } - + @Override + public void setRefreshToken(String refreshToken) { this.refreshToken = refreshToken; } + @Override + public String getRefreshToken() { return refreshToken; } + @Override + public void setRefreshTokenUrl(String refreshTokenUrl) { this.refreshTokenUrl = refreshTokenUrl; } + @Override + public String getRefreshTokenUrl() { return refreshTokenUrl; } @Override public ConnectionType getConnectionType() { return AdsConnectionType.ACT_TO_CT.apply(adsConnectionType); } - @Override public void setConnectionType(ConnectionType connectionType) { this.adsConnectionType = AdsConnectionType.CT_TO_ACT.apply(connectionType); @@ -382,6 +402,8 @@ public Objects.ToStringHelper addToStringInfo( .add("importClass", importClass) .add("failingSince", failingSince) .add("lastError", lastError) + .add("refreshToken", refreshToken) + .add("refreshTokenUrl", refreshTokenUrl) .add("attributes", attributes); } diff --git a/soap/src/java/com/zimbra/soap/account/type/AccountUnknownDataSource.java b/soap/src/java/com/zimbra/soap/account/type/AccountUnknownDataSource.java index 28f3a81627c..ef6d5d700b1 100644 --- a/soap/src/java/com/zimbra/soap/account/type/AccountUnknownDataSource.java +++ b/soap/src/java/com/zimbra/soap/account/type/AccountUnknownDataSource.java @@ -20,11 +20,14 @@ import javax.xml.bind.annotation.XmlAccessType; import javax.xml.bind.annotation.XmlAccessorType; +import com.zimbra.soap.type.DataSource; @XmlAccessorType(XmlAccessType.NONE) public class AccountUnknownDataSource extends AccountDataSource { + public AccountUnknownDataSource(DataSource from) { + copy(from); + } public AccountUnknownDataSource() { } - } diff --git a/soap/src/java/com/zimbra/soap/admin/type/DataSourceType.java b/soap/src/java/com/zimbra/soap/admin/type/DataSourceType.java index a4564a5f323..8f26dd6271c 100644 --- a/soap/src/java/com/zimbra/soap/admin/type/DataSourceType.java +++ b/soap/src/java/com/zimbra/soap/admin/type/DataSourceType.java @@ -26,7 +26,7 @@ @XmlEnum public enum DataSourceType { // case must match protocol - pop3, imap, caldav, contacts, yab, rss, cal, gal, xsync, tagmap; + pop3, imap, caldav, contacts, yab, rss, cal, gal, xsync, tagmap, unknown; public static DataSourceType fromString(String s) throws ServiceException { try { diff --git a/soap/src/java/com/zimbra/soap/mail/message/CreateDataSourceRequest.java b/soap/src/java/com/zimbra/soap/mail/message/CreateDataSourceRequest.java index a3b40fbc9dc..a9add74c6d8 100644 --- a/soap/src/java/com/zimbra/soap/mail/message/CreateDataSourceRequest.java +++ b/soap/src/java/com/zimbra/soap/mail/message/CreateDataSourceRequest.java @@ -17,13 +17,13 @@ package com.zimbra.soap.mail.message; -import com.google.common.base.Objects; import javax.xml.bind.annotation.XmlAccessType; import javax.xml.bind.annotation.XmlAccessorType; import javax.xml.bind.annotation.XmlElement; import javax.xml.bind.annotation.XmlElements; import javax.xml.bind.annotation.XmlRootElement; +import com.google.common.base.Objects; import com.zimbra.common.soap.MailConstants; import com.zimbra.soap.mail.type.MailCalDataSource; import com.zimbra.soap.mail.type.MailCaldavDataSource; diff --git a/soap/src/java/com/zimbra/soap/mail/message/CreateDataSourceResponse.java b/soap/src/java/com/zimbra/soap/mail/message/CreateDataSourceResponse.java index ca84c3cf187..70f62db9287 100644 --- a/soap/src/java/com/zimbra/soap/mail/message/CreateDataSourceResponse.java +++ b/soap/src/java/com/zimbra/soap/mail/message/CreateDataSourceResponse.java @@ -18,6 +18,7 @@ package com.zimbra.soap.mail.message; import com.google.common.base.Objects; + import javax.xml.bind.annotation.XmlAccessType; import javax.xml.bind.annotation.XmlAccessorType; import javax.xml.bind.annotation.XmlElement; diff --git a/soap/src/java/com/zimbra/soap/mail/message/GetContactBackupListRequest.java b/soap/src/java/com/zimbra/soap/mail/message/GetContactBackupListRequest.java new file mode 100644 index 00000000000..99c4c1666d9 --- /dev/null +++ b/soap/src/java/com/zimbra/soap/mail/message/GetContactBackupListRequest.java @@ -0,0 +1,32 @@ +/* + * ***** BEGIN LICENSE BLOCK ***** + * Zimbra Collaboration Suite Server + * Copyright (C) 2017 Synacor, Inc. + * + * This program is free software: you can redistribute it and/or modify it under + * the terms of the GNU General Public License as published by the Free Software Foundation, + * version 2 of the License. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * You should have received a copy of the GNU General Public License along with this program. + * If not, see . + * ***** END LICENSE BLOCK ***** + */ + +package com.zimbra.soap.mail.message; + +import javax.xml.bind.annotation.XmlRootElement; + +import com.zimbra.common.soap.MailConstants; + +/** + * @zm-api-command-auth-required true + * @zm-api-command-admin-auth-required false + * @zm-api-command-description Get list of available contact backups + */ +@XmlRootElement(name=MailConstants.E_GET_CONTACT_BACKUP_LIST_REQUEST) +public class GetContactBackupListRequest { + // empty request +} diff --git a/soap/src/java/com/zimbra/soap/mail/message/GetContactBackupListResponse.java b/soap/src/java/com/zimbra/soap/mail/message/GetContactBackupListResponse.java new file mode 100644 index 00000000000..4e7cc78a9e2 --- /dev/null +++ b/soap/src/java/com/zimbra/soap/mail/message/GetContactBackupListResponse.java @@ -0,0 +1,97 @@ +/* + * ***** BEGIN LICENSE BLOCK ***** + * Zimbra Collaboration Suite Server + * Copyright (C) 2017 Synacor, Inc. + * + * This program is free software: you can redistribute it and/or modify it under + * the terms of the GNU General Public License as published by the Free Software Foundation, + * version 2 of the License. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * You should have received a copy of the GNU General Public License along with this program. + * If not, see . + * ***** END LICENSE BLOCK ***** + */ + +package com.zimbra.soap.mail.message; + +import java.util.ArrayList; +import java.util.List; + +import javax.xml.bind.annotation.XmlAccessType; +import javax.xml.bind.annotation.XmlAccessorType; +import javax.xml.bind.annotation.XmlElement; +import javax.xml.bind.annotation.XmlElementWrapper; +import javax.xml.bind.annotation.XmlRootElement; + +import com.google.common.base.Objects; +import com.zimbra.common.soap.MailConstants; +import com.zimbra.common.util.StringUtil; +import com.zimbra.soap.json.jackson.annotate.ZimbraJsonArrayForWrapper; + +/** + * api to get list of available contact backups + */ +@XmlAccessorType(XmlAccessType.NONE) +@XmlRootElement(name = MailConstants.E_GET_CONTACT_BACKUP_LIST_RESPONSE) +public class GetContactBackupListResponse { + @ZimbraJsonArrayForWrapper + @XmlElementWrapper(name=MailConstants.E_BACKUPS /* backups */, required=false) + @XmlElement(name = MailConstants.E_BACKUP /* backup */, required = false) + private List backup; + + public GetContactBackupListResponse() { + this(null); + } + + /** + * @param backup + */ + public GetContactBackupListResponse(List backup) { + setBackup(backup); + } + + /** + * @return the backup + */ + public List getBackup() { + return backup; + } + + /** + * @param backup the backup to set + */ + public void setBackup(List backup) { + if (backup == null || backup.isEmpty()) { + this.backup = null; + } else { + this.backup = backup; + } + } + + /** + * @param backup the backup to add in the backup + */ + public void addBackup(String backup) { + if (!StringUtil.isNullOrEmpty(backup)) { + if (this.backup == null) { + this.backup = new ArrayList(); + } + this.backup.add(backup); + } + } + + public Objects.ToStringHelper addToStringInfo(Objects.ToStringHelper helper) { + return helper + .add("backup", backup); + } + + @Override + public String toString() { + return addToStringInfo(Objects.toStringHelper(this)).toString(); + } + + +} diff --git a/soap/src/java/com/zimbra/soap/mail/message/GetDataSourcesResponse.java b/soap/src/java/com/zimbra/soap/mail/message/GetDataSourcesResponse.java index 1d69ae015d7..971c866d43b 100644 --- a/soap/src/java/com/zimbra/soap/mail/message/GetDataSourcesResponse.java +++ b/soap/src/java/com/zimbra/soap/mail/message/GetDataSourcesResponse.java @@ -17,10 +17,6 @@ package com.zimbra.soap.mail.message; -import com.google.common.base.Objects; -import com.google.common.collect.Iterables; -import com.google.common.collect.Lists; - import java.util.Collections; import java.util.List; @@ -30,6 +26,9 @@ import javax.xml.bind.annotation.XmlElements; import javax.xml.bind.annotation.XmlRootElement; +import com.google.common.base.Objects; +import com.google.common.collect.Iterables; +import com.google.common.collect.Lists; import com.zimbra.common.soap.MailConstants; import com.zimbra.soap.mail.type.MailCalDataSource; import com.zimbra.soap.mail.type.MailCaldavDataSource; diff --git a/soap/src/java/com/zimbra/soap/mail/message/ImportDataRequest.java b/soap/src/java/com/zimbra/soap/mail/message/ImportDataRequest.java index 9fd8ea3e2ea..f4b1a8af5d6 100644 --- a/soap/src/java/com/zimbra/soap/mail/message/ImportDataRequest.java +++ b/soap/src/java/com/zimbra/soap/mail/message/ImportDataRequest.java @@ -21,7 +21,6 @@ import com.google.common.collect.Iterables; import com.google.common.collect.Lists; -import java.util.Collections; import java.util.List; import javax.xml.bind.annotation.XmlAccessType; diff --git a/soap/src/java/com/zimbra/soap/mail/message/ModifyDataSourceRequest.java b/soap/src/java/com/zimbra/soap/mail/message/ModifyDataSourceRequest.java index db9ef185902..d9d15ce4887 100644 --- a/soap/src/java/com/zimbra/soap/mail/message/ModifyDataSourceRequest.java +++ b/soap/src/java/com/zimbra/soap/mail/message/ModifyDataSourceRequest.java @@ -17,13 +17,13 @@ package com.zimbra.soap.mail.message; -import com.google.common.base.Objects; import javax.xml.bind.annotation.XmlAccessType; import javax.xml.bind.annotation.XmlAccessorType; import javax.xml.bind.annotation.XmlElement; import javax.xml.bind.annotation.XmlElements; import javax.xml.bind.annotation.XmlRootElement; +import com.google.common.base.Objects; import com.zimbra.common.soap.MailConstants; import com.zimbra.soap.mail.type.MailCalDataSource; import com.zimbra.soap.mail.type.MailCaldavDataSource; diff --git a/soap/src/java/com/zimbra/soap/mail/message/RestoreContactsRequest.java b/soap/src/java/com/zimbra/soap/mail/message/RestoreContactsRequest.java new file mode 100644 index 00000000000..c8c8d6ddf6e --- /dev/null +++ b/soap/src/java/com/zimbra/soap/mail/message/RestoreContactsRequest.java @@ -0,0 +1,97 @@ +/* + * ***** BEGIN LICENSE BLOCK ***** + * Zimbra Collaboration Suite Server + * Copyright (C) 2017 Synacor, Inc. + * + * This program is free software: you can redistribute it and/or modify it under + * the terms of the GNU General Public License as published by the Free Software Foundation, + * version 2 of the License. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * You should have received a copy of the GNU General Public License along with this program. + * If not, see . + * ***** END LICENSE BLOCK ***** + */ +package com.zimbra.soap.mail.message; + +import java.util.Arrays; + +import javax.xml.bind.annotation.XmlAccessType; +import javax.xml.bind.annotation.XmlAccessorType; +import javax.xml.bind.annotation.XmlAttribute; +import javax.xml.bind.annotation.XmlEnum; +import javax.xml.bind.annotation.XmlRootElement; + +import com.google.common.base.Objects; +import com.zimbra.common.service.ServiceException; +import com.zimbra.common.soap.MailConstants; + +@XmlAccessorType(XmlAccessType.NONE) +@XmlRootElement(name = MailConstants.E_RESTORE_CONTACTS_REQUEST) +public class RestoreContactsRequest { + + /** + * @zm-api-field-tag contact-backup + * @zm-api-field-description Filename of contact backup file + */ + @XmlAttribute(name = MailConstants.A_CONTACTS_BACKUP_FILE_NAME, required = true) + private String contactsBackupFileName; + + /** + * @zm-api-field-tag resolve + * @zm-api-field-description Restore resolve action - one of ignore|modify|replace|reset
+ * Default value - reset
+ *
    + *
  • ignore - In case of conflict, ignore the existing contact. Create new contact from backup file. + *
  • modify - In case of conflict, merge the existing contact with contact in backup file. + *
  • replace - In case of conflict, replace the existing contact with contact in backup file. + *
  • reset - Delete all existing contacts and restore contacts from backup file. + *
+ */ + @XmlAttribute(name=MailConstants.A_CONTACTS_RESTORE_RESOLVE /* resolve */, required=false) + private Resolve resolve; + + public Resolve getResolve() { + return resolve; + } + + public void setResolve(Resolve resolve) { + this.resolve = resolve; + } + + @XmlEnum + public static enum Resolve { + ignore, modify, replace, reset; + + public static Resolve fromString(String value) throws ServiceException { + if (value == null) { + return null; + } + try { + return Resolve.valueOf(value); + } catch (IllegalArgumentException e) { + throw ServiceException.INVALID_REQUEST( + "Invalid value: " + value + ", valid values: " + Arrays.asList(Resolve.values()), null); + } + } + } + + public String getContactsBackupFileName() { + return contactsBackupFileName; + } + + public void setContactsBackupFileName(String contactsBackupFileName) { + this.contactsBackupFileName = contactsBackupFileName; + } + + public Objects.ToStringHelper addToStringInfo(Objects.ToStringHelper helper) { + return helper.add("contactsBackupFileName", contactsBackupFileName).add("resolve", resolve); + } + + @Override + public String toString() { + return addToStringInfo(Objects.toStringHelper(this)).toString(); + } +} diff --git a/soap/src/java/com/zimbra/soap/mail/message/RestoreContactsResponse.java b/soap/src/java/com/zimbra/soap/mail/message/RestoreContactsResponse.java new file mode 100644 index 00000000000..1071e285c98 --- /dev/null +++ b/soap/src/java/com/zimbra/soap/mail/message/RestoreContactsResponse.java @@ -0,0 +1,29 @@ +/* + * ***** BEGIN LICENSE BLOCK ***** + * Zimbra Collaboration Suite Server + * Copyright (C) 2017 Synacor, Inc. + * + * This program is free software: you can redistribute it and/or modify it under + * the terms of the GNU General Public License as published by the Free Software Foundation, + * version 2 of the License. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * You should have received a copy of the GNU General Public License along with this program. + * If not, see . + * ***** END LICENSE BLOCK ***** + */ +package com.zimbra.soap.mail.message; + +import javax.xml.bind.annotation.XmlAccessType; +import javax.xml.bind.annotation.XmlAccessorType; +import javax.xml.bind.annotation.XmlRootElement; + +import com.zimbra.common.soap.MailConstants; + +@XmlAccessorType(XmlAccessType.NONE) +@XmlRootElement(name = MailConstants.E_RESTORE_CONTACTS_RESPONSE) +public class RestoreContactsResponse { + +} diff --git a/soap/src/java/com/zimbra/soap/mail/message/SaveIMAPSubscriptionsRequest.java b/soap/src/java/com/zimbra/soap/mail/message/SaveIMAPSubscriptionsRequest.java index a4ec4506360..837eae94f1d 100644 --- a/soap/src/java/com/zimbra/soap/mail/message/SaveIMAPSubscriptionsRequest.java +++ b/soap/src/java/com/zimbra/soap/mail/message/SaveIMAPSubscriptionsRequest.java @@ -7,6 +7,7 @@ import javax.xml.bind.annotation.XmlRootElement; import com.google.common.base.Objects; +import com.google.common.collect.Sets; import com.zimbra.common.soap.AccountConstants; import com.zimbra.common.soap.MailConstants; @@ -24,6 +25,10 @@ public class SaveIMAPSubscriptionsRequest { private final Set subscriptions; public Set getSubscriptions() { + if (subscriptions == null) { + Set empty = Collections.emptySet(); + return empty; + } return Collections.unmodifiableSet(subscriptions); } @@ -32,7 +37,7 @@ public Set getSubscriptions() { */ @SuppressWarnings("unused") public SaveIMAPSubscriptionsRequest() { - this((Set)null); + this(Sets.newHashSet()); } public SaveIMAPSubscriptionsRequest(Set subs) { diff --git a/soap/src/java/com/zimbra/soap/mail/message/TestDataSourceRequest.java b/soap/src/java/com/zimbra/soap/mail/message/TestDataSourceRequest.java index 5d48e62d1fb..55c6d3d3544 100644 --- a/soap/src/java/com/zimbra/soap/mail/message/TestDataSourceRequest.java +++ b/soap/src/java/com/zimbra/soap/mail/message/TestDataSourceRequest.java @@ -17,13 +17,13 @@ package com.zimbra.soap.mail.message; -import com.google.common.base.Objects; import javax.xml.bind.annotation.XmlAccessType; import javax.xml.bind.annotation.XmlAccessorType; import javax.xml.bind.annotation.XmlElement; import javax.xml.bind.annotation.XmlElements; import javax.xml.bind.annotation.XmlRootElement; +import com.google.common.base.Objects; import com.zimbra.common.soap.MailConstants; import com.zimbra.soap.mail.type.MailCalDataSource; import com.zimbra.soap.mail.type.MailCaldavDataSource; diff --git a/soap/src/java/com/zimbra/soap/mail/message/TestDataSourceResponse.java b/soap/src/java/com/zimbra/soap/mail/message/TestDataSourceResponse.java index b18ab91d452..cb8110cedef 100644 --- a/soap/src/java/com/zimbra/soap/mail/message/TestDataSourceResponse.java +++ b/soap/src/java/com/zimbra/soap/mail/message/TestDataSourceResponse.java @@ -17,53 +17,61 @@ package com.zimbra.soap.mail.message; -import com.google.common.base.Objects; +import java.util.Collections; +import java.util.List; + import javax.xml.bind.annotation.XmlAccessType; import javax.xml.bind.annotation.XmlAccessorType; -import javax.xml.bind.annotation.XmlAttribute; +import javax.xml.bind.annotation.XmlElement; +import javax.xml.bind.annotation.XmlElements; import javax.xml.bind.annotation.XmlRootElement; +import com.google.common.base.Objects; +import com.google.common.collect.Iterables; +import com.google.common.collect.Lists; import com.zimbra.common.soap.MailConstants; -import com.zimbra.soap.type.ZmBoolean; +import com.zimbra.soap.mail.type.TestDataSource; @XmlAccessorType(XmlAccessType.NONE) @XmlRootElement(name=MailConstants.E_TEST_DATA_SOURCE_RESPONSE) public class TestDataSourceResponse { - /** - * @zm-api-field-tag success - * @zm-api-field-description Flags whether the test was successful + * @zm-api-field-description Data source information */ - @XmlAttribute(name=MailConstants.A_DS_SUCCESS /* success */, required=true) - private final ZmBoolean success; + @XmlElements({ + @XmlElement(name=MailConstants.E_DS_IMAP /* imap */, type=TestDataSource.class), + @XmlElement(name=MailConstants.E_DS_POP3 /* pop3 */, type=TestDataSource.class), + @XmlElement(name=MailConstants.E_DS_CALDAV /* caldav */, type=TestDataSource.class), + @XmlElement(name=MailConstants.E_DS_YAB /* yab */, type=TestDataSource.class), + @XmlElement(name=MailConstants.E_DS_RSS /* rss */, type=TestDataSource.class), + @XmlElement(name=MailConstants.E_DS_GAL /* gal */, type=TestDataSource.class), + @XmlElement(name=MailConstants.E_DS_CAL /* cal */, type=TestDataSource.class), + @XmlElement(name=MailConstants.E_DS_UNKNOWN /* unknown */, type=TestDataSource.class) + }) + private List dataSources = Lists.newArrayList(); - /** - * @zm-api-field-tag error-message - * @zm-api-field-description Error message - */ - @XmlAttribute(name=MailConstants.A_DS_ERROR /* error */, required=false) - private String error; + public void setDataSources(Iterable dataSources) { + this.dataSources.clear(); + if (dataSources != null) { + Iterables.addAll(this.dataSources,dataSources); + } + } - /** - * no-argument constructor wanted by JAXB - */ - @SuppressWarnings("unused") private TestDataSourceResponse() { - this(false); } - public TestDataSourceResponse(boolean success) { - this.success = ZmBoolean.fromBool(success); + public TestDataSourceResponse addDataSource(TestDataSource dataSource) { + this.dataSources.add(dataSource); + return this; } - public void setError(String error) { this.error = error; } - public boolean getSuccess() { return ZmBoolean.toBool(success); } - public String getError() { return error; } + public List getDataSources() { + return Collections.unmodifiableList(dataSources); + } public Objects.ToStringHelper addToStringInfo(Objects.ToStringHelper helper) { return helper - .add("success", success) - .add("error", error); + .add("dataSources", dataSources); } @Override diff --git a/soap/src/java/com/zimbra/soap/mail/type/CalDataSourceId.java b/soap/src/java/com/zimbra/soap/mail/type/CalDataSourceId.java index adff1519b25..6d84f62eb72 100644 --- a/soap/src/java/com/zimbra/soap/mail/type/CalDataSourceId.java +++ b/soap/src/java/com/zimbra/soap/mail/type/CalDataSourceId.java @@ -20,7 +20,6 @@ import javax.xml.bind.annotation.XmlAccessType; import javax.xml.bind.annotation.XmlAccessorType; -import com.zimbra.common.soap.MailConstants; import com.zimbra.soap.type.Id; @XmlAccessorType(XmlAccessType.NONE) diff --git a/soap/src/java/com/zimbra/soap/mail/type/CalDataSourceNameOrId.java b/soap/src/java/com/zimbra/soap/mail/type/CalDataSourceNameOrId.java index 783f277d812..a96c4bbe1aa 100644 --- a/soap/src/java/com/zimbra/soap/mail/type/CalDataSourceNameOrId.java +++ b/soap/src/java/com/zimbra/soap/mail/type/CalDataSourceNameOrId.java @@ -22,4 +22,15 @@ @XmlAccessorType(XmlAccessType.NONE) public class CalDataSourceNameOrId extends DataSourceNameOrId { + public static CalDataSourceNameOrId createForName(String name) { + CalDataSourceNameOrId obj = new CalDataSourceNameOrId(); + obj.setName(name); + return obj; + } + + public static CalDataSourceNameOrId createForId(String id) { + CalDataSourceNameOrId obj = new CalDataSourceNameOrId(); + obj.setId(id); + return obj; + } } diff --git a/soap/src/java/com/zimbra/soap/mail/type/DataSourceNameOrId.java b/soap/src/java/com/zimbra/soap/mail/type/DataSourceNameOrId.java index 3c450b7ea9f..06754012807 100644 --- a/soap/src/java/com/zimbra/soap/mail/type/DataSourceNameOrId.java +++ b/soap/src/java/com/zimbra/soap/mail/type/DataSourceNameOrId.java @@ -17,6 +17,17 @@ package com.zimbra.soap.mail.type; -abstract public class DataSourceNameOrId +public class DataSourceNameOrId extends NameOrId { + public static DataSourceNameOrId createForName(String name) { + DataSourceNameOrId obj = new DataSourceNameOrId(); + obj.setName(name); + return obj; + } + + public static DataSourceNameOrId createForId(String id) { + DataSourceNameOrId obj = new DataSourceNameOrId(); + obj.setId(id); + return obj; + } } diff --git a/soap/src/java/com/zimbra/soap/mail/type/FilterAction.java b/soap/src/java/com/zimbra/soap/mail/type/FilterAction.java index ddfd9fc7d1e..208327f2aa8 100644 --- a/soap/src/java/com/zimbra/soap/mail/type/FilterAction.java +++ b/soap/src/java/com/zimbra/soap/mail/type/FilterAction.java @@ -682,7 +682,7 @@ public static class DeleteheaderAction extends FilterAction { * @zm-api-field-tag filterTests * @zm-api-field-description tests */ - @XmlElement(name=AdminConstants.E_TEST /* test */, required=false) + @XmlElement(name=AdminConstants.E_TEST /* test */, required=true) private EditheaderTest test; private DeleteheaderAction() { @@ -749,9 +749,6 @@ public void validateDeleteheaderAction() throws ServiceException { if (last != null && last && offset == null) { throw ServiceException.PARSE_ERROR(":index must be provided with :last", null); } - if (test == null) { - throw ServiceException.PARSE_ERROR(" is mandatory in action", null); - } test.validateEditheaderTest(); } diff --git a/soap/src/java/com/zimbra/soap/mail/type/FilterTest.java b/soap/src/java/com/zimbra/soap/mail/type/FilterTest.java index 91395409c78..18d3924c1e8 100644 --- a/soap/src/java/com/zimbra/soap/mail/type/FilterTest.java +++ b/soap/src/java/com/zimbra/soap/mail/type/FilterTest.java @@ -138,6 +138,12 @@ public static class AddressTest extends FilterTest { @XmlAttribute(name=MailConstants.A_COUNT_COMPARISON /* countComparison */, required=false) private String countComparison; + /** + * @zm-api-field-tag value-comparison-comparator + * @zm-api-field-description value comparison comparator - i;ascii-numeric|i;ascii-casemap|i;octet + */ + @XmlAttribute(name=MailConstants.A_VALUE_COMPARISON_COMPARATOR /* valueComparisonComparator */, required=false) + private String valueComparisonComparator; public String getHeader() { return header; @@ -198,6 +204,14 @@ public void setCountComparison(String countComparison) { this.countComparison = countComparison; } + public String getValueComparisonComparator() { + return valueComparisonComparator; + } + + public void setValueComparisonComparator(String valueComparisonComparator) { + this.valueComparisonComparator = valueComparisonComparator; + } + @Override public String toString() { return Objects.toStringHelper(this) @@ -205,6 +219,7 @@ public String toString() { .add("part", part) .add("comparison", comparison) .add("valueComparison", valueComparison) + .add("valueComparisonComparator", valueComparisonComparator) .add("countComparison", countComparison) .add("caseSensitive", caseSensitive) .add("value", value) @@ -516,7 +531,7 @@ public String toString() { } @XmlAccessorType(XmlAccessType.NONE) - @JsonPropertyOrder({ "index", "negative", "header", "caseSensitive", "stringComparison", "valueComparison", "countComparison", "value" }) + @JsonPropertyOrder({ "index", "negative", "header", "caseSensitive", "stringComparison", "valueComparison","valueComparisonComparator", "countComparison", "value" }) public static final class HeaderTest extends FilterTest { // Comma separated list @@ -548,6 +563,13 @@ public static final class HeaderTest extends FilterTest { @XmlAttribute(name=MailConstants.A_COUNT_COMPARISON /* countComparison */, required=false) private String countComparison; + /** + * @zm-api-field-tag value-comparison-comparator + * @zm-api-field-description value comparison comparator - i;ascii-numeric|i;ascii-casemap|i;octet + */ + @XmlAttribute(name=MailConstants.A_VALUE_COMPARISON_COMPARATOR /* valueComparisonComparator */, required=false) + private String valueComparisonComparator; + /** * @zm-api-field-tag value * @zm-api-field-description Value @@ -625,12 +647,21 @@ public void setValue(String value) { this.value = value; } + public String getValueComparisonComparator() { + return valueComparisonComparator; + } + + public void setValueComparisonComparator(String valueComparisonComparator) { + this.valueComparisonComparator = valueComparisonComparator; + } + @Override public String toString() { return Objects.toStringHelper(this) .add("headers", headers) .add("stringComparison", stringComparison) .add("valueComparison", valueComparison) + .add("valueComparisonComparator", valueComparisonComparator) .add("countComparison", countComparison) .add("value", value) .add("caseSensitive", caseSensitive) diff --git a/soap/src/java/com/zimbra/soap/mail/type/GalDataSourceId.java b/soap/src/java/com/zimbra/soap/mail/type/GalDataSourceId.java index 63cfd37216b..e60366f8661 100644 --- a/soap/src/java/com/zimbra/soap/mail/type/GalDataSourceId.java +++ b/soap/src/java/com/zimbra/soap/mail/type/GalDataSourceId.java @@ -20,7 +20,6 @@ import javax.xml.bind.annotation.XmlAccessType; import javax.xml.bind.annotation.XmlAccessorType; -import com.zimbra.common.soap.MailConstants; import com.zimbra.soap.type.Id; @XmlAccessorType(XmlAccessType.NONE) diff --git a/soap/src/java/com/zimbra/soap/mail/type/ImapDataSourceId.java b/soap/src/java/com/zimbra/soap/mail/type/ImapDataSourceId.java index bb1a9d67135..6081cf079a9 100644 --- a/soap/src/java/com/zimbra/soap/mail/type/ImapDataSourceId.java +++ b/soap/src/java/com/zimbra/soap/mail/type/ImapDataSourceId.java @@ -20,7 +20,6 @@ import javax.xml.bind.annotation.XmlAccessType; import javax.xml.bind.annotation.XmlAccessorType; -import com.zimbra.common.soap.MailConstants; import com.zimbra.soap.type.Id; @XmlAccessorType(XmlAccessType.NONE) diff --git a/soap/src/java/com/zimbra/soap/mail/type/MailDataSource.java b/soap/src/java/com/zimbra/soap/mail/type/MailDataSource.java index 1d4377d7198..842c1ec427d 100644 --- a/soap/src/java/com/zimbra/soap/mail/type/MailDataSource.java +++ b/soap/src/java/com/zimbra/soap/mail/type/MailDataSource.java @@ -36,10 +36,7 @@ @XmlAccessorType(XmlAccessType.NONE) @XmlType(propOrder = {"lastError", "attributes"}) -abstract public class MailDataSource -implements DataSource { - - +public class MailDataSource implements DataSource { /** * @zm-api-field-tag data-source-id * @zm-api-field-description Unique ID for data source @@ -251,7 +248,19 @@ abstract public class MailDataSource */ @XmlElement(name=MailConstants.E_DS_LAST_ERROR /* lastError */, required=false) private String lastError; + /** + * @zm-api-field-tag data-source-refreshToken + * @zm-api-field-description refresh token for refreshing data source oauth token + */ + @XmlAttribute(name = MailConstants.A_DS_REFRESH_TOKEN /* refreshToken */, required = false) + private String refreshToken; + /** + * @zm-api-field-tag data-source-refreshTokenUrl + * @zm-api-field-description refreshTokenUrl for refreshing data source oauth token + */ + @XmlAttribute(name = MailConstants.A_DS_REFRESH_TOKEN_URL /* refreshTokenUrl */, required = false) + private String refreshTokenUrl; /** * @zm-api-field-tag data-source-attrs * @zm-api-field-description Properties for the data source @@ -273,6 +282,8 @@ public void copy(DataSource from) { setEnabled(from.isEnabled()); setImportOnly(from.isImportOnly()); host = from.getHost(); + refreshToken = from.getRefreshToken(); + refreshTokenUrl = from.getRefreshTokenUrl(); port = from.getPort(); mdsConnectionType = MdsConnectionType.CT_TO_MCT.apply( from.getConnectionType()); @@ -413,16 +424,22 @@ public void addAttribute(String attribute) { public List getAttributes() { return Collections.unmodifiableList(attributes); } - @Override public ConnectionType getConnectionType() { return MdsConnectionType.MCT_TO_CT.apply(mdsConnectionType); } - @Override public void setConnectionType(ConnectionType connectionType) { this.mdsConnectionType = MdsConnectionType.CT_TO_MCT.apply(connectionType); } + @Override + public void setRefreshToken(String refreshToken) { this.refreshToken = refreshToken; } + @Override + public String getRefreshToken() { return refreshToken; } + @Override + public void setRefreshTokenUrl(String refreshTokenUrl) { this.refreshTokenUrl = refreshTokenUrl; } + @Override + public String getRefreshTokenUrl() { return refreshTokenUrl; } public Objects.ToStringHelper addToStringInfo(Objects.ToStringHelper helper) { return helper diff --git a/soap/src/java/com/zimbra/soap/mail/type/OAuthDataSourceNameOrId.java b/soap/src/java/com/zimbra/soap/mail/type/OAuthDataSourceNameOrId.java new file mode 100644 index 00000000000..e91d10dd159 --- /dev/null +++ b/soap/src/java/com/zimbra/soap/mail/type/OAuthDataSourceNameOrId.java @@ -0,0 +1,20 @@ +package com.zimbra.soap.mail.type; + +import javax.xml.bind.annotation.XmlAccessType; +import javax.xml.bind.annotation.XmlAccessorType; + +@XmlAccessorType(XmlAccessType.NONE) +public class OAuthDataSourceNameOrId extends DataSourceNameOrId { + + public static OAuthDataSourceNameOrId createForName(String name) { + OAuthDataSourceNameOrId obj = new OAuthDataSourceNameOrId(); + obj.setName(name); + return obj; + } + + public static OAuthDataSourceNameOrId createForId(String id) { + OAuthDataSourceNameOrId obj = new OAuthDataSourceNameOrId(); + obj.setId(id); + return obj; + } +} diff --git a/soap/src/java/com/zimbra/soap/mail/type/Pop3DataSourceNameOrId.java b/soap/src/java/com/zimbra/soap/mail/type/Pop3DataSourceNameOrId.java index 673cbf0d3f5..2a42a83d4c9 100644 --- a/soap/src/java/com/zimbra/soap/mail/type/Pop3DataSourceNameOrId.java +++ b/soap/src/java/com/zimbra/soap/mail/type/Pop3DataSourceNameOrId.java @@ -22,4 +22,15 @@ @XmlAccessorType(XmlAccessType.NONE) public class Pop3DataSourceNameOrId extends DataSourceNameOrId { + public static Pop3DataSourceNameOrId createForName(String name) { + Pop3DataSourceNameOrId obj = new Pop3DataSourceNameOrId(); + obj.setName(name); + return obj; + } + + public static Pop3DataSourceNameOrId createForId(String id) { + Pop3DataSourceNameOrId obj = new Pop3DataSourceNameOrId(); + obj.setId(id); + return obj; + } } diff --git a/soap/src/java/com/zimbra/soap/mail/type/RssDataSourceNameOrId.java b/soap/src/java/com/zimbra/soap/mail/type/RssDataSourceNameOrId.java index 145c7a03590..957c123a411 100644 --- a/soap/src/java/com/zimbra/soap/mail/type/RssDataSourceNameOrId.java +++ b/soap/src/java/com/zimbra/soap/mail/type/RssDataSourceNameOrId.java @@ -22,4 +22,15 @@ @XmlAccessorType(XmlAccessType.NONE) public class RssDataSourceNameOrId extends DataSourceNameOrId { + public static RssDataSourceNameOrId createForName(String name) { + RssDataSourceNameOrId obj = new RssDataSourceNameOrId(); + obj.setName(name); + return obj; + } + + public static RssDataSourceNameOrId createForId(String id) { + RssDataSourceNameOrId obj = new RssDataSourceNameOrId(); + obj.setId(id); + return obj; + } } diff --git a/soap/src/java/com/zimbra/soap/mail/type/TestDataSource.java b/soap/src/java/com/zimbra/soap/mail/type/TestDataSource.java new file mode 100644 index 00000000000..2be542d71bd --- /dev/null +++ b/soap/src/java/com/zimbra/soap/mail/type/TestDataSource.java @@ -0,0 +1,44 @@ +package com.zimbra.soap.mail.type; + +import javax.xml.bind.annotation.XmlAttribute; + +import com.zimbra.common.soap.MailConstants; + +public class TestDataSource { + /** + * @zm-api-field-tag data-source-success + * @zm-api-field-description 0 if data source test failed, 1 if test succeeded + */ + @XmlAttribute(name = MailConstants.A_DS_SUCCESS /* success */, required = true) + private int success; + + /** + * @zm-api-field-tag data-source-error + * @zm-api-field-description error message passed by DatImport::test method of the datasource being tested + */ + @XmlAttribute(name = MailConstants.A_DS_ERROR /* error */, required = false) + private String error; + + public TestDataSource() { + this.success = 1; + this.error = null; + } + + public TestDataSource(String error) { + if(error != null && !error.isEmpty()) { + success = 0; + } + this.error = error; + } + + public TestDataSource(int success, String error) { + this.success = success; + this.error = error; + } + + public void setSuccess(int success) { this.success = success; } + public int getSuccess() { return success; } + + public void setError(String error) { this.error = error; } + public String getError() { return this.error; } +} diff --git a/soap/src/java/com/zimbra/soap/type/AccountWithModifications.java b/soap/src/java/com/zimbra/soap/type/AccountWithModifications.java index c0d17d9e979..e1034708ea0 100644 --- a/soap/src/java/com/zimbra/soap/type/AccountWithModifications.java +++ b/soap/src/java/com/zimbra/soap/type/AccountWithModifications.java @@ -18,7 +18,6 @@ package com.zimbra.soap.type; import java.util.Collection; -import java.util.List; import javax.xml.bind.annotation.XmlAccessType; import javax.xml.bind.annotation.XmlAccessorType; @@ -26,7 +25,6 @@ import javax.xml.bind.annotation.XmlElement; import javax.xml.bind.annotation.XmlRootElement; -import com.google.common.collect.Lists; import com.zimbra.common.soap.AdminConstants; import com.zimbra.common.soap.MailConstants; import com.zimbra.soap.mail.type.PendingFolderModifications; diff --git a/soap/src/java/com/zimbra/soap/type/DataSource.java b/soap/src/java/com/zimbra/soap/type/DataSource.java index e11b6d58ab1..a3f64622aff 100644 --- a/soap/src/java/com/zimbra/soap/type/DataSource.java +++ b/soap/src/java/com/zimbra/soap/type/DataSource.java @@ -92,4 +92,8 @@ public static ConnectionType fromString(String s) public Long getFailingSince(); public String getLastError(); public List getAttributes(); + public void setRefreshToken(String refreshToken); + public String getRefreshToken(); + public void setRefreshTokenUrl(String refreshTokenUrl); + public String getRefreshTokenUrl(); } diff --git a/soap/src/java/com/zimbra/soap/type/DataSources.java b/soap/src/java/com/zimbra/soap/type/DataSources.java index 45983ef5990..d89af8a7b1e 100644 --- a/soap/src/java/com/zimbra/soap/type/DataSources.java +++ b/soap/src/java/com/zimbra/soap/type/DataSources.java @@ -18,12 +18,22 @@ package com.zimbra.soap.type; import com.zimbra.soap.account.type.AccountCalDataSource; +import com.zimbra.soap.account.type.AccountDataSource; import com.zimbra.soap.account.type.AccountImapDataSource; import com.zimbra.soap.account.type.AccountPop3DataSource; import com.zimbra.soap.account.type.AccountRssDataSource; +import com.zimbra.soap.account.type.AccountUnknownDataSource; public class DataSources { + public static AccountDataSource newDataSource(DataSource data) { + return new AccountDataSource(data); + } + + public static AccountDataSource newDataSource() { + return new AccountUnknownDataSource(); + } + public static Pop3DataSource newPop3DataSource() { return new AccountPop3DataSource(); } diff --git a/store-conf/conf/msgs/ZsMsg.properties b/store-conf/conf/msgs/ZsMsg.properties index 0d15176e7b8..22751aa8e79 100644 --- a/store-conf/conf/msgs/ZsMsg.properties +++ b/store-conf/conf/msgs/ZsMsg.properties @@ -475,7 +475,7 @@ zimbra_index_lucene_max_buffered_docs = The minimal number of documents required documents are flushed as a new segment. Large values generally give faster indexing. zimbra_index_lucene_ram_buffer_size_kb = Lucene Max Buffered RAM in KB zimbra_index_lucene_io_impl = Lucene FSDirectory implementation used for indexing IO. \ - Possible values + Possible values \ nio - Use NIOFSDirectory. Uses java.nio's FileChannel's positional io when reading to avoid synchronization when reading from the same file. \ mmap - Use MMapDirectory. Uses memory-mapped IO when reading. A good choice if there is plenty of virtual memory relative to index size. \ simple - Use SimpleFSDirectory. Uses java.io.RandomAccessFile. It has poor concurrent performance as it synchronizes when multiple threads read the same file. \ @@ -906,3 +906,19 @@ zipFile = Zip File seiveRejectMDNSubject = Disposition notification seiveRejectMDNErrorMsg = The message was refused by the recipient's mail filtering program. The reason given was as follows: + +# email address verification +verifyEmailSubject = Request for email address verification by {0} +verifyEmailBodyText =\ + {0} has requested verification of your email address\n\ + \n\ + Click on the link below to verify your email address: {1}\n\ + The link expires by: {2}\n\ + *~*~*~*~*~*~*~*~*~*\n +verifyEmailBodyHtml =\ +
\ +

{0} has requested verification of your email address

\ +
\ + Click on this link to verify your email address
\ + The link expires by {2}\ +
diff --git a/store-conf/conf/msgs/ZsMsg_ar.properties b/store-conf/conf/msgs/ZsMsg_ar.properties index 3167e472d55..c043ffa8d80 100644 --- a/store-conf/conf/msgs/ZsMsg_ar.properties +++ b/store-conf/conf/msgs/ZsMsg_ar.properties @@ -475,7 +475,7 @@ zimbra_index_lucene_max_buffered_docs = \u0627\u0644\u062d\u062f \u0627\u0644\u0 \u0627\u0644\u0645\u062e\u0632\u0646\u0629 \u0645\u0624\u0642\u062a\u064b\u0627 \u0641\u064a \u0627\u0644\u0630\u0627\u0643\u0631\u0629 \u0643\u0645\u0642\u0637\u0639 \u062c\u062f\u064a\u062f. \u0639\u0627\u062f\u0629 \u0645\u0646 \u062a\u0648\u0641\u0631 \u0627\u0644\u0642\u064a\u0645 \u0627\u0644\u0643\u0628\u064a\u0631\u0629 \u0641\u0647\u0631\u0633\u0629 \u0623\u0633\u0631\u0639.\u200f zimbra_index_lucene_ram_buffer_size_kb = \u0623\u0642\u0635\u0649 \u062d\u062f \u0644\u0630\u0627\u0643\u0631\u0629 \u0627\u0644\u0648\u0635\u0648\u0644 \u0627\u0644\u0639\u0634\u0648\u0627\u0626\u064a \u0630\u0627\u062a \u0627\u0644\u0645\u062e\u0632\u0646 \u0627\u0644\u0645\u0624\u0642\u062a Lucene \u0628\u0627\u0644\u0643\u064a\u0644\u0648\u0628\u0627\u064a\u062a zimbra_index_lucene_io_impl = \u062a\u0646\u0641\u064a\u0630 Lucene FSDirectory \u0627\u0644\u0645\u0633\u062a\u062e\u062f\u0645 \u0644\u0641\u0647\u0631\u0633\u0629 \u0627\u0644\u0625\u062f\u062e\u0627\u0644/\u0627\u0644\u0625\u062e\u0631\u0627\u062c. \ - \u0627\u0644\u0642\u064a\u0645 \u0627\u0644\u0645\u062d\u062a\u0645\u0644\u0629 + \u0627\u0644\u0642\u064a\u0645 \u0627\u0644\u0645\u062d\u062a\u0645\u0644\u0629 \ nio - \u0627\u0633\u062a\u062e\u062f\u0645 NIOFSDirectory. \u0627\u0633\u062a\u062e\u062f\u0627\u0645 \u0627\u0644\u0625\u062f\u062e\u0627\u0644/\u0627\u0644\u0625\u062e\u0631\u0627\u062c \u0627\u0644\u0645\u0648\u0636\u0639\u064a \u0644\u0640 java.nio's FileChannel \u0639\u0646\u062f \u0627\u0644\u0642\u0631\u0627\u0621\u0629 \u0644\u062a\u062c\u0646\u0628 \u0627\u0644\u0645\u0632\u0627\u0645\u0646\u0629 \u0639\u0646\u062f \u0627\u0644\u0642\u0631\u0627\u0621\u0629 \u0645\u0646 \u0646\u0641\u0633 \u0627\u0644\u0645\u0644\u0641. \ mmap \u2013 \u0627\u0633\u062a\u062e\u062f\u0627\u0645 MMapDirectory \u0627\u0633\u062a\u062e\u062f\u0627\u0645 \u0627\u0644\u0625\u062f\u062e\u0627\u0644/\u0627\u0644\u0625\u062e\u0631\u0627\u062c \u0627\u0644\u0645\u062e\u0637\u0637 \u0627\u0644\u0630\u0627\u0643\u0631\u0629 \u0639\u0646\u062f \u0627\u0644\u0642\u0631\u0627\u0621\u0629. \u0627\u062e\u062a\u064a\u0627\u0631 \u062c\u064a\u062f \u0641\u064a \u062d\u0627\u0644\u0629 \u0648\u062c\u0648\u062f \u0645\u0633\u0627\u062d\u0629 \u0643\u0628\u064a\u0631\u0629 \u0645\u0646 \u0627\u0644\u0630\u0627\u0643\u0631\u0629 \u0627\u0644\u0638\u0627\u0647\u0631\u064a\u0629 \u0630\u0627\u062a \u0627\u0644\u0635\u0644\u0629 \u0628\u062d\u062c\u0645 \u0627\u0644\u0641\u0647\u0631\u0633. \ \u0628\u0633\u064a\u0637 \u2013 \u0627\u0633\u062a\u062e\u062f\u0627\u0645 SimpleFSDirectory \u0627\u0633\u062a\u062e\u062f\u0627\u0645 java.io.RandomAccessFile. \u0644\u0647 \u0623\u062f\u0627\u0621 \u0645\u062a\u0632\u0627\u0645\u0646 \u0631\u062f\u064a\u0621 \u062d\u064a\u062b \u064a\u0642\u0648\u0645 \u0628\u0627\u0644\u0645\u0632\u0627\u0645\u0646\u0629 \u0639\u0646\u062f\u0645\u0627 \u062a\u0642\u0648\u0645 \u062e\u064a\u0648\u0637 \u0645\u062a\u0639\u062f\u062f\u0629 \u0628\u0642\u0631\u0627\u0621\u0629 \u0646\u0641\u0633 \u0627\u0644\u0645\u0644\u0641. \ @@ -567,7 +567,7 @@ ldap_cache_share_locator_maxage = \u0630\u0627\u0643\u0631\u0629 \u0627\u0644\u0 ldap_cache_timezone_maxsize = \u0623\u0642\u0635\u0649 \u0639\u062f\u062f \u0645\u0646 \u0643\u0627\u0626\u0646\u0627\u062a \u0627\u0644\u0645\u0646\u0637\u0642\u0629 \u0627\u0644\u0632\u0645\u0646\u064a\u0629 \u062a\u064f\u062e\u0632\u0651\u0646 \u0645\u0624\u0642\u062a\u064b\u0627. ldap_cache_zimlet_maxsize = \u0623\u0642\u0635\u0649 \u0639\u062f\u062f \u0645\u0646 \u0643\u0627\u0626\u0646\u0627\u062a zimlet \u062a\u064f\u062e\u0632\u0651\u0646 \u0645\u0624\u0642\u062a\u064b\u0627. ldap_cache_zimlet_maxage = \u0623\u0642\u0635\u0649 \u0639\u0645\u0631 (\u0628\u0627\u0644\u062f\u0642\u0627\u0626\u0642) \u0644\u0643\u0627\u0626\u0646\u0627\u062a zimlet \u0641\u064a \u0630\u0627\u0643\u0631\u0629 \u0627\u0644\u062a\u062e\u0632\u064a\u0646 \u0627\u0644\u0645\u0624\u0642\u062a. -ldap_cache_custom_dynamic_group_membership_maxage_ms = \u0623\u0642\u0635\u0649 \u0639\u0645\u0631 (\u0628\u0627\u0644\u0645\u0644\u0644\u064a \u062b\u0627\u0646\u064a\u0629) \u0644\u0644\u062a\u062e\u0632\u064a\u0646 \u0627\u0644\u0645\u0624\u0642\u062a \u0644\u0623\u064a \u0645\u062c\u0645\u0648\u0639\u0627\u062a \u062f\u064a\u0646\u0627\u0645\u064a\u0643\u064a\u0629. +ldap_cache_custom_dynamic_group_membership_maxage_ms = \u0623\u0642\u0635\u0649 \u0639\u0645\u0631 (\u0628\u0627\u0644\u0645\u0644\u0644\u064a \u062b\u0627\u0646\u064a\u0629) \u0644\u0644\u062a\u062e\u0632\u064a\u0646 \u0627\u0644\u0645\u0624\u0642\u062a \u0644\u0623\u064a \u0645\u062c\u0645\u0648\u0639\u0627\u062a \u062f\u064a\u0646\u0627\u0645\u064a\u0643\u064a\u0629.\ \u0627\u0644\u062d\u0633\u0627\u0628 \u0647\u0648 \u0639\u0636\u0648 \u0644\u0647\u0630\u0647 \u0627\u0644\u0645\u062c\u0645\u0648\u0639\u0627\u062a \u0627\u0644\u062a\u064a \u064a\u062a\u0645 \u062a\u062d\u062f\u064a\u062f \u0639\u0636\u0648\u064a\u062a\u0647\u0627 \u0628\u0627\u0633\u062a\u062e\u062f\u0627\u0645 MemberURL \u0645\u062e\u0635\u0635. mysql_directory = \u0645\u0648\u0642\u0639 \u062a\u062b\u0628\u064a\u062a MySQL. mysql_data_directory = \u0627\u0644\u062f\u0644\u064a\u0644 \u0627\u0644\u0630\u064a \u064a\u062c\u0628 \u0623\u0646 \u064a\u0648\u062c\u062f \u0641\u064a\u0647 \u0628\u064a\u0627\u0646\u0627\u062a MySQL\u200f. diff --git a/store-conf/conf/msgs/ZsMsg_ca.properties b/store-conf/conf/msgs/ZsMsg_ca.properties index aa949fc34f9..cb84fa3aed2 100644 --- a/store-conf/conf/msgs/ZsMsg_ca.properties +++ b/store-conf/conf/msgs/ZsMsg_ca.properties @@ -366,7 +366,7 @@ passwordViolation = Vulneraci\u00f3 de la contrasenya: account={0}, violation={1 # local config settings - zimbra_home = \ +zimbra_home = \ Directori arrel d\u2019instal\u00b7laci\u00f3 del Zimbra i directori d\u2019inici \ de l\u2019usuari \u2019zimbra\u2019 d\u2019UNIX. No podeu traslladar el directori arrel d\u2019instal\u00b7laci\u00f3. \ No canvieu aquesta configuraci\u00f3. @@ -380,7 +380,7 @@ zimbra_extensions_directory = Directori amb els subdirectoris que contenen les e zimbra_extensions_common_directory = Directori amb fitxers jar comuns a totes les extensions. zimbra_mysql_shutdown_timeout = Temps d\u2019espera per donar per acabat el proc\u00e9s MySQL/MariaDB de la b\u00fastia de correu. \ Augmenteu el temps d\u2019espera si mysql.server triga massa a tancar el proc\u00e9s. - zimbra_mysql_user = \ +zimbra_mysql_user = \ Nom d\u2019usuari de MySQL per crear/accedir a les bases de dades i taules \ del zimbra. Aquest \u00e9s el valor que indicar\u00edeu a \ l\u2019opci\u00f3 \u2019-u\u2019 del programa de l\u00ednia d\u2019ordres \u2019mysql\u2019. @@ -398,7 +398,7 @@ zimbra_ldap_password = \ es puguin autenticar. Per canviar la contrasenya, \ feu servir el programa zmmypasswd, que canviar\u00e0 la \ contrasenya tant a LDAP com a la configuraci\u00f3 local. - zimbra_server_hostname = \ +zimbra_server_hostname = \ Nom aprovisionat d\u2019aquest servidor. Cal que hi hagi \ una entrada \u2019server\u2019 corresponent a LDAP. Consulteu \ la documentaci\u00f3 de l\u2019ordre CreateServer del programa zmprov. @@ -475,7 +475,7 @@ zimbra_index_lucene_max_buffered_docs = Nombre m\u00ednim de documents necessari desats a la mem\u00f2ria interm\u00e8dia es bolquin com a nou segment. Amb valors grans, la indexaci\u00f3 acostuma a ser m\u00e9s r\u00e0pida. zimbra_index_lucene_ram_buffer_size_kb = RAM interm\u00e8dia m\u00e0xima en KB per a Lucene zimbra_index_lucene_io_impl = Implementaci\u00f3 de FSDirectory de Lucene emprada per l\u2019E/S d\u2019indexaci\u00f3. \ - Valors possibles + Valors possibles \ nio - Fer servir el NIOFSDirectory. Fa servir l\u2019E/S posicional de FileChannel de java.nio durant la lectura per evitar la sincronitzaci\u00f3 quan es llegeix del mateix fitxer. \ mmap - Fer servir el MMapDirectory. Fa servir E/S assignada a mem\u00f2ria durant la lectura. Bona opci\u00f3 si es disposa de molta mem\u00f2ria virtual respecte de la mida de l\u2019\u00edndex. \ simple - Fer servir SimpleFSDirectory. Fa servir java.io.RandomAccessFile. T\u00e9 un rendiment concurrent feble, perqu\u00e8 sincronitza quan hi ha m\u00e9s d\u2019un fil llegint el mateix fitxer. \ @@ -577,7 +577,7 @@ mysql_pidfile = Fitxer on s\u2019emmagatzema l\u2019identificador de proc\u00e9s mysql_mycnf = Ruta de my.cnf, el fitxer de configuraci\u00f3 del MySQL. mysql_bind_address = Interf\u00edcie de l\u2019amfitri\u00f3 al qual s\u2019enlla\u00e7ar\u00e0 el MySQL. mysql_port = N\u00famero de port que ha d\u2019escoltar el servidor MySQL. - mysql_root_password = \ +mysql_root_password = \ Contrasenya de l\u2019usuari \u2019root\u2019 integrat de MySQL, que no s\u2019ha \ de confondre amb l\u2019usuari root d\u2019UNIX. Per comoditat, durant la inicialitzaci\u00f3 \ de la base de dades es genera una contrasenya aleat\u00f2ria \ @@ -772,7 +772,7 @@ zimbra_zmjava_java_ext_dirs = Propietat java.ext.dirs de Java per a l\u2019scrip debug_update_config_use_old_scheme = \ Si \u00e9s TRUE, DbMailbox.updateConfig() far\u00e0 DELETE/INSERT enlloc d\u2019UPDATE. debug_xmpp_disable_client_tls = inhabilita TLS per al protocol XMPP C2S - im_dnsutil_dnsoverride = \ +im_dnsutil_dnsoverride = \ Configuraci\u00f3 de l\u2019anul\u00b7laci\u00f3 del DNS per a la federaci\u00f3 de missatges instantanis, en el format \u2019{domain,host:port}, {domain,host:port},...\u2019 javamail_pop3_debug = Si cal permetre la depuraci\u00f3 de JavaMail per POP3. javamail_zparser = Si cal fer servir l\u2019analitzador MIME del Zimbra enlloc del de JavaMail. diff --git a/store-conf/conf/msgs/ZsMsg_da.properties b/store-conf/conf/msgs/ZsMsg_da.properties index ad5b85d9d41..2706b19dbed 100644 --- a/store-conf/conf/msgs/ZsMsg_da.properties +++ b/store-conf/conf/msgs/ZsMsg_da.properties @@ -416,7 +416,7 @@ zimbra_index_lucene_max_buffered_docs = Det mindste antal dokumenter, som er n\u t\u00f8mmes som et nyt segment. St\u00f8rre v\u00e6rdier giver generelt hurtigere indeksering. zimbra_index_lucene_ram_buffer_size_kb = Lucene \u2013 Maks. buffer-RAM i KB zimbra_index_lucene_io_impl = Lucene FSDirectory implementering brugt til indeksering af IO. \ - Mulige v\u00e6rdier + Mulige v\u00e6rdier \ nio - Brug NIOFSDirectory. Bruger java.nio's FileChannels stillings-io under l\u00e6sning for at undg\u00e5 synkronisering, n\u00e5r der l\u00e6ses fra den samme fil. \ mmap - Brug MMapDirectory. Bruger hukommelseskortlagt IO under l\u00e6sning. Et godt valg, hvis der er nok virtuel hukommelse i forhold til indeksst\u00f8rrelsen. \ simpel - Brug SimpleFSDirectory. Bruger java.io.RandomAccessFile. Det har d\u00e5rlig sidel\u00f8bende ydeevne under synkronisering, n\u00e5r flere tr\u00e5de l\u00e6ser den samme fil. \ diff --git a/store-conf/conf/msgs/ZsMsg_de.properties b/store-conf/conf/msgs/ZsMsg_de.properties index 1641d19fb82..9e64490df2a 100644 --- a/store-conf/conf/msgs/ZsMsg_de.properties +++ b/store-conf/conf/msgs/ZsMsg_de.properties @@ -474,7 +474,7 @@ zimbra_index_lucene_max_buffered_docs = Die Mindestanzahl von Dokumenten, die n\ Dokumente als neues Segment geleert werden. Gr\u00f6\u00dfere Werte ergeben in der Regel schnellere Indizierung. zimbra_index_lucene_ram_buffer_size_kb = H\u00f6chstwert des gepufferten RAM in KB f\u00fcr Lucene zimbra_index_lucene_io_impl = Implementierung des Lucene-FSDirectory f\u00fcr die Indizierung von E/A. \ - M\u00f6gliche Werte + M\u00f6gliche Werte \ nio - NIOFSDirectory verwenden. Verwendet beim Lesen java.nio's FileChannel's positional IO, um beim Lesen aus der gleichen Datei eine Synchronisierung zu vermeiden. \ mmap - MMapDirectory verwenden. Verwendet beim Lesen Memory-Mapped IO. Eine gute Wahl, wenn in Bezug auf die Indexgr\u00f6\u00dfe viel virtueller Speicher vorhanden ist. \ simple - SimpleFSDirectory verwenden. Verwendet java.io.RandomAccessFile. Zeigt bei der Synchronisierung eine schlechte gleichzeitige Leistung, wenn mehrere Threads die gleiche Datei lesen. \ diff --git a/store-conf/conf/msgs/ZsMsg_en_AU.properties b/store-conf/conf/msgs/ZsMsg_en_AU.properties index cda28bdc0f3..64a0cf91aab 100644 --- a/store-conf/conf/msgs/ZsMsg_en_AU.properties +++ b/store-conf/conf/msgs/ZsMsg_en_AU.properties @@ -475,7 +475,7 @@ zimbra_index_lucene_max_buffered_docs = The minimal number of documents required documents are flushed as a new segment. Large values generally give faster indexing. zimbra_index_lucene_ram_buffer_size_kb = Lucene Max Buffered RAM in KB zimbra_index_lucene_io_impl = Lucene FSDirectory implementation used for indexing IO. \ - Possible values + Possible values \ nio - Use NIOFSDirectory. Uses java.nio's FileChannel's positional io when reading to avoid synchronisation when reading from the same file. \ mmap - Use MMapDirectory. Uses memory-mapped IO when reading. A good choice if there is plenty of virtual memory relative to index size. \ simple - Use SimpleFSDirectory. Uses java.io.RandomAccessFile. It has poor concurrent performance as it synchronises when multiple threads read the same file. \ @@ -567,7 +567,7 @@ ldap_cache_share_locator_maxage = Named entry cache TTL in minutes. ldap_cache_timezone_maxsize = Maximum number of timezone objects to cache. ldap_cache_zimlet_maxsize = Maximum number of zimlet objects to cache. ldap_cache_zimlet_maxage = Maximum age (in minutes) of zimlet objects in cache. -ldap_cache_custom_dynamic_group_membership_maxage_ms = Maximum age (in minutes) of group objects in cache. +ldap_cache_custom_dynamic_group_membership_maxage_ms = Maximum age (in milliseconds) to cache which dynamic groups \ an account is a member of for those groups whose membership is determind using a custom MemberURL. mysql_directory = Location of MySQL installation. mysql_data_directory = Directory in which MySQL data should reside. diff --git a/store-conf/conf/msgs/ZsMsg_en_GB.properties b/store-conf/conf/msgs/ZsMsg_en_GB.properties index c66c3c6e884..3ceb07ef443 100644 --- a/store-conf/conf/msgs/ZsMsg_en_GB.properties +++ b/store-conf/conf/msgs/ZsMsg_en_GB.properties @@ -416,7 +416,7 @@ zimbra_index_lucene_max_buffered_docs = The minimal number of documents required documents are flushed as a new segment. Large values generally give faster indexing. zimbra_index_lucene_ram_buffer_size_kb = Lucene Max Buffered RAM in KB zimbra_index_lucene_io_impl = Lucene FSDirectory implementation used for indexing IO. \ - Possible values + Possible values \ nio - Use NIOFSDirectory. Uses java.nio's FileChannel's positional io when reading to avoid synchronisation when reading from the same file. \ mmap - Use MMapDirectory. Uses memory-mapped IO when reading. A good choice if there is plenty of virtual memory relative to index size. \ simple - Use SimpleFSDirectory. Uses java.io.RandomAccessFile. It has poor concurrent performance as it synchronises when multiple threads read the same file. \ diff --git a/store-conf/conf/msgs/ZsMsg_es.properties b/store-conf/conf/msgs/ZsMsg_es.properties index 4002ac34624..634453371e3 100644 --- a/store-conf/conf/msgs/ZsMsg_es.properties +++ b/store-conf/conf/msgs/ZsMsg_es.properties @@ -474,7 +474,7 @@ zimbra_index_lucene_max_buffered_docs = El n\u00famero m\u00ednimo de documentos en la memoria sean liberados como un segmento nuevo. Los valores grandes proporcionan generalmente un indizado m\u00e1s r\u00e1pido. zimbra_index_lucene_ram_buffer_size_kb = RAM m\u00e1ximo en KB guardado en el b\u00fafer de Lucene zimbra_index_lucene_io_impl = La implementaci\u00f3n de FSDirectory de Lucene usado para el indizado de IO. \ - Posibles valores + Posibles valores \ nio - Usa NIOFSDirectory. Usa el io posicional de FileChannel de java.nio al leer para evitar la sincronizaci\u00f3n cuando leas el mismo archivo. \ mmap \u2013 Usa MMapDirectory. Usa el IO mapeado en la memoria para leer. Una buena elecci\u00f3n en caso que se disponga de mucha memoria virtual en relaci\u00f3n al tama\u00f1o del indizado. \ simple \u2013 Usa SimpleFSDirectory. Usa java.io.RandomAccessFile. Tiene un rendimiento pobre ya que realiza la sincronizaci\u00f3n cuando varios subprocesos leen el mismo archivo. \ diff --git a/store-conf/conf/msgs/ZsMsg_eu.properties b/store-conf/conf/msgs/ZsMsg_eu.properties index 03517c1147d..887482a8fdd 100644 --- a/store-conf/conf/msgs/ZsMsg_eu.properties +++ b/store-conf/conf/msgs/ZsMsg_eu.properties @@ -463,7 +463,7 @@ zimbra_index_lucene_max_buffered_docs = Gutxienez zenbat dokumentu behar diren, dokumentuak segmentu berri gisa hustu baino lehen. Balio handiek, oro har, indexazio azkarra sortzen dute. zimbra_index_lucene_ram_buffer_size_kb = Gehienezko RAM Lucene-ko buferrean gordetako KB-n zimbra_index_lucene_io_impl = Lucene FSDirectory inplementatu da, IO indexatzeko. \ - Balio posibleak: + Balio posibleak: \ nio - Erabili NIOFSDirectory. Fitxategi beretik irakurtzen denean, irakurketa egiterakoan, java.nio's FileChannel-en posizio-IO erabiltzen du, sinkronizazioa saihesteko. \ mmap - Erabili MMapDirectory. Memoria-mapatutako IO erabiltzen du, irakurketa bitartean. Aukera ona da, baldin eta, indizearen tamaina aintzat hartuta, memoria birtual handia badago. \ sinplea - Erabili SimpleFSDirectory. java.io.RandomAccessFile erabiltzen du. Errendimendua ez da oso ona, azpiprozesu anitzek fitxategi bera irakurtzen dutenean, sinkronizatu egin delako. \ diff --git a/store-conf/conf/msgs/ZsMsg_fr.properties b/store-conf/conf/msgs/ZsMsg_fr.properties index 2f96b14bfe9..dac05a08688 100644 --- a/store-conf/conf/msgs/ZsMsg_fr.properties +++ b/store-conf/conf/msgs/ZsMsg_fr.properties @@ -474,7 +474,7 @@ zimbra_index_lucene_max_buffered_docs = Nombre minimum de documents \u00e0 attei documents stock\u00e9s dans la m\u00e9moire tampon soient vid\u00e9s sous la forme d\u2019un nouveau segment. En g\u00e9n\u00e9ral, les valeurs \u00e9lev\u00e9es garantissent une indexation plus rapide. zimbra_index_lucene_ram_buffer_size_kb = Taille maximale de la RAM avec m\u00e9moire tampon Lucene en Ko zimbra_index_lucene_io_impl = Impl\u00e9mentation Lucene FSDirectory utilis\u00e9e pour l\u2019indexation des E/S. \ - Valeurs possibles + Valeurs possibles \ nio - Utiliser NIOFSDirectory. Utilise les E/S positionnelles FileChannel du package java.nio pendant la lecture pour \u00e9viter la synchronisation si la lecture s\u2019effectue dans le m\u00eame fichier. \ mmap - Utiliser MMapDirectory. Utilise des E/S mapp\u00e9es sur la m\u00e9moire pendant la lecture (choix int\u00e9ressant si la taille de la m\u00e9moire virtuelle est beaucoup plus importante que la taille de l\u2019index). \ simple - Utiliser SimpleFSDirectory. Utilise RandomAccessFile du package java.io. Performances concurrentes m\u00e9diocres dans la mesure o\u00f9 la synchronisation s\u2019effectue chaque fois que plusieurs fils lisent le m\u00eame fichier. \ diff --git a/store-conf/conf/msgs/ZsMsg_fr_CA.properties b/store-conf/conf/msgs/ZsMsg_fr_CA.properties index a563c6d7b22..723192c067f 100644 --- a/store-conf/conf/msgs/ZsMsg_fr_CA.properties +++ b/store-conf/conf/msgs/ZsMsg_fr_CA.properties @@ -475,7 +475,7 @@ zimbra_index_lucene_max_buffered_docs = Le nombre minimal de documents requis av soient transpos\u00e9s dans un nouveau segment. Les valeurs plus \u00e9lev\u00e9es permettent g\u00e9n\u00e9ralement une indexation plus rapide. zimbra_index_lucene_ram_buffer_size_kb = Taille maximale de la RAM avec m\u00e9moire tampon Lucene en Ko zimbra_index_lucene_io_impl = Impl\u00e9mentation de Lucene FSDirectory pour l\u2019indexation des E/S. \ - Valeurs possibles + Valeurs possibles \ nio - Utilisez NIOFSDirectory. Utilise l\u2019E/S de position FileChannel de java.nio lors de la lecture pour \u00e9viter la synchronisation pendant la lecture d\u2019un m\u00eame fichier. \ mmap - Utilisez MMapDirectory. Utilise les E/S mapp\u00e9s en m\u00e9moire pendant la lecture. C\u2019est un bon choix si la m\u00e9moire virtuelle est importante comparativement \u00e0 la taille de l\u2019index. \ simple - Utilisez SimpleFSDirectory. Utilise java.io.RandomAccessFile. Sa performance concurrente est faible pendant la synchronisation puisque plusieurs threads lisent le m\u00eame fichier. \ diff --git a/store-conf/conf/msgs/ZsMsg_hi.properties b/store-conf/conf/msgs/ZsMsg_hi.properties index 61ce04edeea..0067b6bc8d8 100644 --- a/store-conf/conf/msgs/ZsMsg_hi.properties +++ b/store-conf/conf/msgs/ZsMsg_hi.properties @@ -416,7 +416,7 @@ zimbra_index_lucene_max_buffered_docs = \u092e\u0947\u092e\u094b\u0930\u0940- \u \u0926\u0938\u094d\u0924\u093e\u0935\u0947\u091c\u093c \u0928\u090f \u0905\u0928\u0941\u092d\u093e\u0917 \u0915\u0947 \u0930\u0942\u092a \u092e\u0947\u0902 \u0928\u093f\u0915\u093e\u0932\u0947 \u091c\u093e\u0924\u0947 \u0939\u0948\u0902. \u092c\u0921\u093c\u093e \u092e\u093e\u0928 \u0938\u093e\u092e\u093e\u0928\u094d\u092f\u0924\u092f\u093e \u0924\u0940\u0935\u094d\u0930\u0924\u0930 \u0907\u0902\u0921\u0947\u0915\u094d\u0938\u093f\u0902\u0917 \u0926\u0947\u0924\u093e \u0939\u0948. zimbra_index_lucene_ram_buffer_size_kb = \u0932\u094d\u092f\u0942\u0938\u093f\u0928 \u0905\u0927\u093f\u0915\u0924\u092e \u092c\u092b\u093c\u0930 \u0915\u0940 \u0917\u0908 RAM \u0915\u0947\u092c\u0940 \u092e\u0947\u0902 zimbra_index_lucene_io_impl = IO \u0907\u0902\u0921\u0947\u0915\u094d\u0938\u093f\u0902\u0917 \u0915\u0947 \u0932\u093f\u090f Lucene FSDirectory \u0915\u093e\u0930\u094d\u092f\u093e\u0928\u094d\u0935\u092f\u0928 \u0915\u093e \u0909\u092a\u092f\u094b\u0917 \u0915\u093f\u092f\u093e \u091c\u093e\u0924\u093e \u0939\u0948. \ - \u0938\u0902\u092d\u093e\u0935\u093f\u0924 \u092e\u093e\u0928 + \u0938\u0902\u092d\u093e\u0935\u093f\u0924 \u092e\u093e\u0928 \ nio \u2013 NIOFSDirectory \u0915\u093e \u0909\u092a\u092f\u094b\u0917 \u0915\u0930\u0947\u0902. \u0909\u0938\u0940 \u092b\u093c\u093e\u0907\u0932 \u0938\u0947 \u092a\u0922\u093c\u0928\u0947 \u0915\u0947 \u0926\u094c\u0930\u093e\u0928 \u092a\u0922\u093c\u0924\u0947 \u0938\u092e\u092f \u0938\u093f\u0902\u0915\u094d\u0930\u0928\u093e\u0907\u091c\u093c\u0947\u0936\u0928 \u0938\u0947 \u092c\u091a\u0928\u0947 \u0915\u0947 \u0932\u093f\u090f java.nio \u0915\u0947 FileChannel \u0915\u0947 \u0938\u094d\u0925\u093f\u0924\u093f io \u0915\u093e \u0909\u092a\u092f\u094b\u0917 \u0915\u0930\u0924\u093e \u0939\u0948. \ mmap \u2013 MmapDirectory \u0915\u093e \u0909\u092a\u092f\u094b\u0917 \u0915\u0930\u0947\u0902. \u092a\u0922\u093c\u0924\u0947 \u0938\u092e\u092f \u092e\u0947\u092e\u094b\u0930\u0940-mapped IO \u0915\u093e \u0909\u092a\u092f\u094b\u0917 \u0915\u0930\u0924\u093e \u0939\u0948. A good choice \u092f\u0926\u093f \u0907\u0902\u0921\u0947\u0915\u094d\u0938 \u0906\u0915\u093e\u0930 \u0938\u0947 \u0938\u0902\u092c\u0902\u0927\u093f\u0924 \u0935\u0930\u094d\u091a\u0941\u0905\u0932 \u0915\u0940 \u0905\u0927\u093f\u0915\u0924\u093e \u0939\u094b \u0924\u094b \u0905\u091a\u094d\u091b\u0940 \u092a\u0938\u0902\u0926 \u0939\u0948. \ \u0938\u093e\u092e\u093e\u0928\u094d\u092f \u2013 SimpleFSDirectory \u0915\u093e \u0909\u092a\u092f\u094b\u0917 \u0915\u0930\u0947\u0902. java.io.RandomAccessFile \u0915\u093e \u0909\u092a\u092f\u094b\u0917 \u0915\u0930\u0924\u093e \u0939\u0948. \u0907\u0938\u0915\u093e \u092a\u094d\u0930\u0926\u0930\u094d\u0936\u0928 \u0938\u0902\u092f\u0941\u0915\u094d\u0924 \u0930\u0942\u092a \u0938\u0947 \u0915\u092e\u091c\u094b\u0930 \u0939\u094b\u0924\u093e \u0939\u0948 \u0915\u094d\u092f\u094b\u0902\u0915\u093f \u090f\u0915\u093e\u0927\u093f\u0915 \u0925\u094d\u0930\u0947\u0921 \u0926\u094d\u0935\u093e\u0930\u093e \u0909\u0938\u0940 \u092b\u093c\u093e\u0907\u0932 \u0915\u094b \u092a\u0922\u093c\u0924\u0947 \u0938\u092e\u092f \u092f\u0939 \u0938\u093f\u0902\u0915\u094d\u0930\u0928\u093e\u0907\u091c\u093c \u0915\u0930\u0924\u093e \u0939\u0948. \ diff --git a/store-conf/conf/msgs/ZsMsg_hu.properties b/store-conf/conf/msgs/ZsMsg_hu.properties index 57d472eb106..10643a78f86 100644 --- a/store-conf/conf/msgs/ZsMsg_hu.properties +++ b/store-conf/conf/msgs/ZsMsg_hu.properties @@ -475,7 +475,7 @@ zimbra_index_lucene_max_buffered_docs = A mem\u00f3ri\u00e1ba pufferel\u00e9s el \u00faj szegmensk\u00e9nt van ki\u00fcr\u00edtve. A nagyobb \u00e9rt\u00e9kek \u00e1ltal\u00e1ban gyorsabb indexel\u00e9st biztos\u00edtanak. zimbra_index_lucene_ram_buffer_size_kb = Lucene max. pufferelt RAM KB-ban zimbra_index_lucene_io_impl = Lucene FSDirectory alkalmaz\u00e1sa haszn\u00e1lva az IO indexel\u00e9shez. \ -Lehets\u00e9ges \u00e9rt\u00e9kek +Lehets\u00e9ges \u00e9rt\u00e9kek \ nio \u2013 NIOFSDirectory haszn\u00e1lata. Olvas\u00e1skor a java.nio FileChannel helyzetbe\u00e1ll\u00edt\u00f3 io-j\u00e1t haszn\u00e1lja, hogy elker\u00fclje a szinkroniz\u00e1l\u00e1st, amikor ugyanabb\u00f3l a f\u00e1jlb\u00f3l olvas. \ mmap \u2013 MMapDirectory haszn\u00e1lata. Olvas\u00e1skor a mem\u00f3ri\u00e1ba \u00e1gyazott IO-t haszn\u00e1lja. J\u00f3 v\u00e1laszt\u00e1s, ha az index m\u00e9ret\u00e9hez k\u00e9pest sok a virtu\u00e1lis mem\u00f3ria. \ egyszer\u0171 \u2013 SimpleFSDirectory haszn\u00e1lata. java.io.RandomAccessFile haszn\u00e1lata. A p\u00e1rhuzamos teljes\u00edtm\u00e9nye gyenge, mivel akkor szinkroniz\u00e1l, amikor t\u00f6bb sz\u00e1l olvassa ugyanazt a f\u00e1jlt. \ diff --git a/store-conf/conf/msgs/ZsMsg_in.properties b/store-conf/conf/msgs/ZsMsg_in.properties index 6512b8cc5e0..b41d85ebda2 100644 --- a/store-conf/conf/msgs/ZsMsg_in.properties +++ b/store-conf/conf/msgs/ZsMsg_in.properties @@ -475,7 +475,7 @@ zimbra_index_lucene_max_buffered_docs = Jumlah minimal dokumen yang dibutuhkan s dokumen dibersihkan sebagai segmen baru. Nilai yang lebih besar biasanya memberi pengindeksan yang lebih cepat. zimbra_index_lucene_ram_buffer_size_kb = RAM Buffer Maks Lucene dalam KB zimbra_index_lucene_io_impl = Implementasi FSDirectory Lucene yang digunakan untuk IO pengindeksan. \ - Nilai yang mungkin + Nilai yang mungkin \ nio - Gunakan NIOFSDirectory. Menggunakan io posisi FileChannel java.nio saat membaca untuk menghindari sinkronisasi ketika membaca dari file yang sama. \ mmap - Gunakan MMapDirectory. Menggunakan IO yang dipetakan-memori saat membaca. Pilihan yang tepat jika ada banyak memori virtual yang relatif terhadap ukuran indeks. \ simple - Gunakan SimpleFSDirectory. Menggunakan java.io.RandomAccessFile. Kinerja bersamanya buruk karena ini melakukan sinkronisasi ketika beberapa thread membaca file yang sama. \ diff --git a/store-conf/conf/msgs/ZsMsg_it.properties b/store-conf/conf/msgs/ZsMsg_it.properties index 82a6281ef3a..ce1eeda07bc 100644 --- a/store-conf/conf/msgs/ZsMsg_it.properties +++ b/store-conf/conf/msgs/ZsMsg_it.properties @@ -416,7 +416,7 @@ zimbra_index_lucene_max_buffered_docs = Il numero minimo di documenti richiesto come un nuovo segmento. Valori pi\u00f9 elevati di norma determinano un'indicizzazione pi\u00f9 rapida. zimbra_index_lucene_ram_buffer_size_kb = Quantit\u00e0 massima di RAM in buffer Lucene in KB zimbra_index_lucene_io_impl = Per l'indicizzazione dell'IO si usa l'implementazione Lucene FSDirectory. \ - Valori possibili + Valori possibili \ nio - Uso di NIOFSDirectory. Utilizza l'io posizionale del FileChannel di java.nio durante la lettura per evitare la sincronizzazione con lettura dallo stesso file. \ mmap - Uso di MMapDirectory. Durante la lettura utilizza l'IO mappato in memoria. Una buona scelta se si dispone di molta memoria virtuale relativa alle dimensioni dell'indice. \ semplice - Uso di SimpleFSDirectory. Utilizza java.io.RandomAccessFile. Le prestazioni concorrenti sono scarse in quanto sincronizza quando pi\u00f9 thread leggono lo stesso file. \ diff --git a/store-conf/conf/msgs/ZsMsg_iw.properties b/store-conf/conf/msgs/ZsMsg_iw.properties index 7cf56d1f0d1..5517085f6a8 100644 --- a/store-conf/conf/msgs/ZsMsg_iw.properties +++ b/store-conf/conf/msgs/ZsMsg_iw.properties @@ -475,7 +475,7 @@ zimbra_index_lucene_max_buffered_docs = \u05de\u05e1\u05e4\u05e8 \u05d4\u05de\u0 \u05e2\u05d5\u05d1\u05e8\u05d9\u05dd flush \u05db\u05de\u05e7\u05d8\u05e2 \u05d7\u05d3\u05e9. \u05e2\u05e8\u05db\u05d9\u05dd \u05d2\u05d1\u05d5\u05d4\u05d9\u05dd \u05d9\u05d5\u05ea\u05e8 \u05de\u05e1\u05e4\u05e7\u05d9\u05dd \u05d1\u05d3"\u05db \u05e8\u05d9\u05e9\u05d5\u05dd \u05de\u05d4\u05d9\u05e8 \u05d9\u05d5\u05ea\u05e8 \u05d1\u05d0\u05d9\u05e0\u05d3\u05e7\u05e1. zimbra_index_lucene_ram_buffer_size_kb = \u05d4-RAM \u05d4\u05de\u05e8\u05d1\u05d9 \u05d1\u05de\u05d0\u05d2\u05e8 \u05e9\u05dc Lucene \u05d1-KB zimbra_index_lucene_io_impl = \u05d9\u05d9\u05e9\u05d5\u05dd Lucene FSDirectory \u05dc\u05e6\u05d5\u05e8\u05da \u05e8\u05d9\u05e9\u05d5\u05dd IO \u05d1\u05d0\u05d9\u05e0\u05d3\u05e7\u05e1. \ - \u05e2\u05e8\u05db\u05d9\u05dd \u05d0\u05e4\u05e9\u05e8\u05d9\u05d9\u05dd + \u05e2\u05e8\u05db\u05d9\u05dd \u05d0\u05e4\u05e9\u05e8\u05d9\u05d9\u05dd \ nio - \u05de\u05e9\u05ea\u05de\u05e9 \u05d1-NIOFSDirectory. \u05de\u05e9\u05ea\u05de\u05e9 \u05d1-java.nio's FileChannel's positional io \u05d1\u05e2\u05ea \u05d4\u05e7\u05e8\u05d9\u05d0\u05d4 \u05db\u05d3\u05d9 \u05dc\u05d4\u05d9\u05de\u05e0\u05e2 \u05de\u05e1\u05e0\u05db\u05e8\u05d5\u05df \u05d1\u05e2\u05ea \u05e7\u05e8\u05d9\u05d0\u05d4 \u05de\u05d0\u05d5\u05ea\u05d5 \u05e7\u05d5\u05d1\u05e5. \ mmap - \u05de\u05e9\u05ea\u05de\u05e9 \u05d1-MMapDirectory. \u05de\u05e9\u05ea\u05de\u05e9 \u05d1-memory-mapped IO \u05d1\u05e2\u05ea \u05d4\u05e7\u05e8\u05d9\u05d0\u05d4. \u05d1\u05d7\u05d9\u05e8\u05d4 \u05d8\u05d5\u05d1\u05d4 \u05d0\u05dd \u05d9\u05e9 \u05d4\u05e8\u05d1\u05d4 \u05d6\u05d9\u05db\u05e8\u05d5\u05df \u05d5\u05d9\u05e8\u05d8\u05d5\u05d0\u05dc\u05d9 \u05d1\u05d9\u05d7\u05e1 \u05dc\u05d2\u05d5\u05d3\u05dc \u05d4\u05d0\u05d9\u05e0\u05d3\u05e7\u05e1. \ simple - \u05de\u05e9\u05ea\u05de\u05e9 \u05d1-SimpleFSDirectory. \u05de\u05e9\u05ea\u05de\u05e9 \u05d1-java.io.RandomAccessFile. \u05d9\u05e9 \u05dc\u05d5 \u05d1\u05d9\u05e6\u05d5\u05e2\u05d9\u05dd \u05d1\u05d5-\u05d6\u05de\u05e0\u05d9\u05d9\u05dd \u05d2\u05e8\u05d5\u05e2\u05d9\u05dd \u05d1\u05de\u05d4\u05dc\u05da \u05d4\u05e1\u05e0\u05db\u05e8\u05d5\u05df \u05d1\u05e2\u05ea \u05e9\u05ea\u05d4\u05dc\u05d9\u05db\u05d9 \u05de\u05e9\u05e0\u05d4 \u05e8\u05d1\u05d9\u05dd \u05e7\u05d5\u05e8\u05d0\u05d9\u05dd \u05d0\u05ea \u05d0\u05d5\u05ea\u05d5 \u05e7\u05d5\u05d1\u05e5. \ diff --git a/store-conf/conf/msgs/ZsMsg_ja.properties b/store-conf/conf/msgs/ZsMsg_ja.properties index 1109d6e5e38..88b92708c44 100644 --- a/store-conf/conf/msgs/ZsMsg_ja.properties +++ b/store-conf/conf/msgs/ZsMsg_ja.properties @@ -474,7 +474,7 @@ zimbra_index_lucene_max_buffered_docs = \u30d0\u30c3\u30d5\u30a1\u3055\u308c\u30 \u5fc5\u8981\u306a\u6587\u66f8\u306e\u6700\u4f4e\u6570\u3002\u4e00\u822c\u306b\u3001\u5024\u3092\u5927\u304d\u304f\u3059\u308b\u3068\u30a4\u30f3\u30c7\u30c3\u30af\u30b9\u4f5c\u6210\u304c\u901f\u304f\u306a\u308a\u307e\u3059\u3002 zimbra_index_lucene_ram_buffer_size_kb = Lucene\u6700\u5927\u30d0\u30c3\u30d5\u30a1RAM\uff08KB\u5358\u4f4d\uff09 zimbra_index_lucene_io_impl = IO\u306e\u30a4\u30f3\u30c7\u30c3\u30af\u30b9\u4f5c\u6210\u306b\u4f7f\u7528\u3055\u308c\u308bLucene FSDirectory\u5b9f\u88c5\u3002 \ - \u53ef\u80fd\u306a\u5024 + \u53ef\u80fd\u306a\u5024 \ nio \u2013 NIOFSDirectory\u3092\u4f7f\u7528\u3057\u307e\u3059\u3002\u8aad\u307f\u53d6\u308a\u6642\u306bjava.nio\u306eFileChannel\u306e\u4f4d\u7f6e\u3092\u4f7f\u7528\u3059\u308b\u3053\u3068\u3067\u3001\u540c\u3058\u30d5\u30a1\u30a4\u30eb\u304b\u3089\u8aad\u307f\u53d6\u308b\u3068\u304d\u306e\u540c\u671f\u3092\u56de\u907f\u3057\u307e\u3059\u3002 \ mmap \u2013 MmapDirectory\u3092\u4f7f\u7528\u3057\u307e\u3059\u3002\u8aad\u307f\u53d6\u308a\u6642\u306b\u30e1\u30e2\u30ea\u30de\u30c3\u30d7\u3055\u308c\u305fIO\u3092\u4f7f\u7528\u3057\u307e\u3059\u3002\u30a4\u30f3\u30c7\u30c3\u30af\u30b9\u30b5\u30a4\u30ba\u306b\u5bfe\u3057\u3066\u5341\u5206\u306a\u91cf\u306e\u4eee\u60f3\u30e1\u30e2\u30ea\u304c\u3042\u308b\u5834\u5408\u306b\u9069\u3057\u3066\u3044\u307e\u3059\u3002 \ simple \u2013 SimpleFSDirectory\u3092\u4f7f\u7528\u3057\u307e\u3059\u3002java.io.RandomAccessFile\u3092\u4f7f\u7528\u3057\u307e\u3059\u3002\u8907\u6570\u306e\u30b9\u30ec\u30c3\u30c9\u304c\u540c\u4e00\u306e\u30d5\u30a1\u30a4\u30eb\u3092\u8aad\u307f\u53d6\u308b\u3068\u304d\u306b\u540c\u671f\u3092\u5b9f\u884c\u3059\u308b\u305f\u3081\u3001\u540c\u6642\u5b9f\u884c\u30d1\u30d5\u30a9\u30fc\u30de\u30f3\u30b9\u306f\u3088\u304f\u3042\u308a\u307e\u305b\u3093\u3002 \ diff --git a/store-conf/conf/msgs/ZsMsg_ko.properties b/store-conf/conf/msgs/ZsMsg_ko.properties index 5cf2d7fc898..058e75e1118 100644 --- a/store-conf/conf/msgs/ZsMsg_ko.properties +++ b/store-conf/conf/msgs/ZsMsg_ko.properties @@ -474,7 +474,7 @@ zimbra_index_lucene_max_buffered_docs = \ubc84\ud37c\ub9c1\ub41c \uba54\ubaa8\ub \ucd5c\uc18c \ubb38\uc11c \uc218. \ubcf4\ud1b5 \uac12\uc774 \ud074\uc218\ub85d \uc0c9\uc778\ud654 \uc18d\ub3c4\uac00 \ub354 \ube60\ub985\ub2c8\ub2e4. zimbra_index_lucene_ram_buffer_size_kb = Lucene \ubc84\ud37c\ub41c \ucd5c\ub300 RAM \ud06c\uae30(KB) zimbra_index_lucene_io_impl = IO \uc0c9\uc778\ud654\uc5d0 \uc0ac\uc6a9\ub41c Lucene FSDirectory \uad6c\ud604. \ - \uac00\ub2a5\ud55c \uac12 + \uac00\ub2a5\ud55c \uac12 \ nio - NIOFSDirectory\ub97c \uc0ac\uc6a9\ud569\ub2c8\ub2e4. \ub3d9\uc77c\ud55c \ud30c\uc77c\ub85c\ubd80\ud130 \uc77d\uc744 \ub54c \ub3d9\uae30\ud654\ub97c \ud53c\ud558\uae30 \uc704\ud574 java.nio\uc758 FileChannel \uc704\uce58 io\ub97c \uc0ac\uc6a9\ud569\ub2c8\ub2e4. \ mmap \u2013 MmapDirectory\ub97c \uc0ac\uc6a9\ud569\ub2c8\ub2e4. \uc77d\uc744 \ub54c \uba54\ubaa8\ub9ac \ub9e4\ud551\ub41c IO\ub97c \uc0ac\uc6a9\ud569\ub2c8\ub2e4. \uc0c9\uc778\ud654 \ud06c\uae30\uc5d0 \ube44\ud574 \uac00\uc0c1 \uba54\ubaa8\ub9ac\uac00 \ucda9\ubd84\ud55c \uacbd\uc6b0\uc5d0 \uc88b\uc740 \uc120\ud0dd\uc785\ub2c8\ub2e4. \ simple \u2013 SimpleFSDirectory\ub97c \uc0ac\uc6a9\ud569\ub2c8\ub2e4. java.io.RandomAccessFile\uc744 \uc0ac\uc6a9\ud569\ub2c8\ub2e4. \uc5ec\ub7ec \uc2a4\ub808\ub4dc\uac00 \ub3d9\uc77c\ud55c \ud30c\uc77c\uc744 \uc77d\uc744 \ub54c \ub3d9\uae30\ud654\ud558\ubbc0\ub85c \ub3d9\uc2dc \uc131\ub2a5\uc774 \ub098\uc069\ub2c8\ub2e4. \ diff --git a/store-conf/conf/msgs/ZsMsg_lo.properties b/store-conf/conf/msgs/ZsMsg_lo.properties index b75e9b1da9d..1dc3a4f3a77 100644 --- a/store-conf/conf/msgs/ZsMsg_lo.properties +++ b/store-conf/conf/msgs/ZsMsg_lo.properties @@ -475,7 +475,7 @@ zimbra_index_lucene_max_buffered_docs = \u0e88\u0eb3\u200b\u0e99\u0ea7\u0e99\u20 \u0e96\u0eb7\u0e81\u0e95\u0eb1\u0e99\u200b\u0ec0\u0e9b\u0eb1\u0e99\u200b\u0eaa\u0ec8\u0ea7\u0e99\u200b\u0ec3\u0edd\u0ec8. \u0ec2\u0e94\u0e8d\u200b\u0e97\u0ebb\u0ec8\u0ea7\u200b\u0ec4\u0e9b\u200b\u0e84\u0ec8\u0eb2\u200b\u0ec3\u0eab\u0e8d\u0ec8\u200b\u0ec3\u0eab\u0ec9\u200b\u0e81\u0eb2\u0e99\u200b\u0ec0\u0eae\u0eb1\u0e94\u200b\u0e94\u0eb1\u0e94\u200b\u0e8a\u0eb0\u200b\u0e99\u0eb5\u200b\u0ec4\u0e94\u0ec9\u200b\u0ec4\u0ea7\u200b\u0e81\u0ea7\u0ec8\u0eb2. zimbra_index_lucene_ram_buffer_size_kb = RAM \u0e81\u0eb3\u200b\u0e99\u0ebb\u0e94\u200b\u0ead\u0eb1\u0e94\u200b\u0e95\u0eb2\u200b\u0ec1\u0ea5\u0ec9\u0ea7\u200b\u0eaa\u0eb9\u0e87\u200b\u0eaa\u0eb8\u0e94\u200b\u0ec0\u0e9b\u0eb1\u0e99 KB zimbra_index_lucene_io_impl = \u0e81\u0eb2\u0e99\u200b\u0e88\u0eb1\u0e94\u200b\u0e95\u0eb1\u0ec9\u0e87\u200b\u0e9b\u0eb0\u200b\u0e95\u0eb4\u200b\u0e9a\u0eb1\u0e94Lucene FSDirectory\u200b\u0ec3\u0e8a\u0ec9\u200b\u0eaa\u0eb3\u200b\u0ea5\u0eb1\u0e9a\u200b\u0e81\u0eb2\u0e99\u200b\u0ec0\u0eae\u0eb1\u0e94\u200b\u0e94\u0eb1\u0e94\u200b\u0e8a\u0eb0\u200b\u0e99\u0eb5 IO. \ - \u0e84\u0ec8\u0eb2\u200b\u0e97\u0eb5\u0ec8\u200b\u0ec0\u0e9b\u0eb1\u0e99\u200b\u0ec4\u0e9b\u200b\u0ec4\u0e94\u0ec9 + \u0e84\u0ec8\u0eb2\u200b\u0e97\u0eb5\u0ec8\u200b\u0ec0\u0e9b\u0eb1\u0e99\u200b\u0ec4\u0e9b\u200b\u0ec4\u0e94\u0ec9 \ nio - \u0ec3\u0e8a\u0ec9 NIOFSDirectory. \u0ec3\u0e8a\u0ec9 io \u0e95\u0eb3\u200b\u0ec1\u0edc\u0ec8\u0e87\u200b\u0e82\u0ead\u0e87 FileChannel \u0e82\u0ead\u0e87 java.nio \u0ec0\u0ea1\u0eb7\u0ec8\u0ead\u200b\u0e81\u0eb3\u200b\u0ea5\u0eb1\u0e87\u200b\u0ead\u0ec8\u0eb2\u0e99\u0ec0\u0e9e\u0eb7\u0ec8\u0ead\u200b\u0eab\u0ebc\u0eb5\u0e81\u200b\u0ec0\u0ea7\u0eb1\u0ec9\u0e99\u200b\u0e81\u0eb2\u0e99\u200b\u0e8a\u0eb4\u0e87\u0e84\u0ecc\u0e82\u0ecd\u0ec9\u0ea1\u0eb9\u0e99\u0ec0\u0ea1\u0eb7\u0ec8\u0ead\u200b\u0e81\u0eb3\u200b\u0ea5\u0eb1\u0e87\u200b\u0ead\u0ec8\u0eb2\u0e99\u200b\u0e88\u0eb2\u0e81\u200b\u0ec4\u0e9f\u200b\u0ea5\u0ecc\u200b\u0e94\u0ebd\u0ea7\u200b\u0e81\u0eb1\u0e99. \ mmap - .-h MMapDirectory. \u0ec3\u0e8a\u0ec9 IO \u0e84\u0ea7\u0eb2\u0ea1\u200b\u0e88\u0eb3-\u0ec0\u0eae\u0eb1\u0e94\u200b\u0ec1\u0e9c\u0e99\u200b\u0e97\u0eb5\u0ec8\u200b\u0ec1\u0ea5\u0ec9\u0ea7\u0ec0\u0ea1\u0eb7\u0ec8\u0ead\u200b\u0e81\u0eb3\u200b\u0ea5\u0eb1\u0e87\u200b\u0ead\u0ec8\u0eb2\u0e99. \u0e97\u0eb2\u0e87\u200b\u0ec0\u0ea5\u0eb7\u0ead\u0e81\u200b\u0e97\u0eb5\u0ec8\u200b\u0e94\u0eb5\u200b\u0ead\u0eb1\u0e99\u200b\u0edc\u0eb6\u0ec8\u0e87\u0e96\u0ec9\u0eb2\u200b\u0ea1\u0eb5\u200b\u0eab\u0ebc\u0eb2\u0e8d\u0e84\u0ea7\u0eb2\u0ea1\u200b\u0e88\u0eb3\u0eaa\u0eb0\u200b\u0ec0\u0edd\u0eb7\u0ead\u0e99\u200b\u0e81\u0ec8\u0ebd\u0ea7\u200b\u0e82\u0ec9\u0ead\u0e87\u0e81\u0eb1\u0e9a\u200b\u0e82\u0eb0\u200b\u0edc\u0eb2\u0e94\u200b\u0e94\u0eb1\u0e94\u200b\u0e8a\u0eb0\u200b\u0e99\u0eb5. \ \u0e87\u0ec8\u0eb2\u0e8d - \u0ec3\u0e8a\u0ec9 SimpleFSDirectory. \u0ec3\u0e8a\u0ec9 java.io.RandomAccessFile. \u0ea1\u0eb1\u0e99\u200b\u0ea1\u0eb5\u200b\u0e81\u0eb2\u0e99\u200b\u0e94\u0eb3\u200b\u0ec0\u0e99\u0eb5\u0e99\u200b\u0e81\u0eb2\u0e99\u200b\u0e9e\u0ec9\u0ead\u0ea1\u200b\u0e81\u0eb1\u0e99\u200b\u0e97\u0eb5\u0ec8\u200b\u0e9a\u0ecd\u0ec8\u200b\u0e94\u0eb5\u0ec0\u0e99\u0eb7\u0ec8\u0ead\u0e87\u200b\u0e88\u0eb2\u0e81\u200b\u0ea1\u0eb1\u0e99\u200b\u0e8a\u0eb4\u0e87\u200b\u0e84\u0ecc\u0e82\u0ecd\u0ec9\u0ea1\u0eb9\u0e99\u0ec0\u0ea1\u0eb7\u0ec8\u0ead\u200b\u0eab\u0ebc\u0eb2\u0e8d\u200b\u0e81\u0eb2\u0e99\u200b\u0eaa\u0ebb\u0e99\u200b\u0e97\u0eb0\u200b\u0e99\u0eb2\u200b\u0ead\u0ec8\u0eb2\u0e99\u200b\u0ec4\u0e9f\u200b\u0ea5\u0ecc\u200b\u0e94\u0ebd\u0ea7\u200b\u0e81\u0eb1\u0e99. \ diff --git a/store-conf/conf/msgs/ZsMsg_ms.properties b/store-conf/conf/msgs/ZsMsg_ms.properties index ba8c468a572..73712fa8e58 100644 --- a/store-conf/conf/msgs/ZsMsg_ms.properties +++ b/store-conf/conf/msgs/ZsMsg_ms.properties @@ -475,7 +475,7 @@ zimbra_index_lucene_max_buffered_docs = Bilangan minimum dokumen diperlukan sebe penimbal dikosongkan sebagai segmen baharu. Nilai yang lebih besar biasanya memberi pengindeksan yang lebih pantas. zimbra_index_lucene_ram_buffer_size_kb = RAM Bertimbal Maks Lucene dalam KB zimbra_index_lucene_io_impl = Pelaksanaan FSDirectory Lucene digunakan untuk pengindeksan IO. \ -Nilai kemungkinan +Nilai kemungkinan \ nio - Guna NIOFSDirectory. Gunakan io posisi FileChannel java.nio semasa membaca untuk mengelakkan penyegerakkan apabila membaca fail yang sama. \ mmap - Guna MMapDirectory. Guna IO pemetaan ingatan semasa membaca. Pilihan yang bijak jika terdapat banyak ingatan maya relatif dengan saiz indeks. \ ringkas - Guna SimpleFSDirectory. Gunakan java.io.RandomAccessFile. Ia mempunyai prestasi serentak yang buruk disebabkan ia segerakkan dengan berbilang jaluran pada masa yang sama. \ diff --git a/store-conf/conf/msgs/ZsMsg_nl.properties b/store-conf/conf/msgs/ZsMsg_nl.properties index 2d92ff2ddf3..35677430564 100644 --- a/store-conf/conf/msgs/ZsMsg_nl.properties +++ b/store-conf/conf/msgs/ZsMsg_nl.properties @@ -416,7 +416,7 @@ zimbra_index_lucene_max_buffered_docs = Het minimale aantal documenten dat is ve geplaatste documenten als een een nieuw segment worden verwijderd. Grote waarden zorgen doorgaans voor een snellere indexering. zimbra_index_lucene_ram_buffer_size_kb = Lucene Max. gebufferde RAM, in KB (Max Buffered RAM in kB) zimbra_index_lucene_io_impl = Lucene FSDirectory-implementatie die wordt gebruikt voor indexering van IO. \ - Mogelijke waarden + Mogelijke waarden \ nio \u2013 NIOFSDirectory gebruiken. Maakt gebruik van java.nio's FileChannel's positionele IO bij het lezen om synchronisatie te voorkomen bij het lezen van hetzelfde bestand. \ mmap \u2013 MmapDirectory gebruiken. Gebruikt geheugentoegewezen IO tijdens het lezen. Een goede keuze als er voldoende virtueel geheugen is in verhouding tot de indexgrootte. \ simple \u2013 SimpleFSDirectory gebruiken. Gebruikt java.io.RandomAccessFile. Dit heeft slechte gelijktijdige prestaties wanneer wordt gesynchroniseerd terwijl meerdere threads hetzelfde bestand lezen. \ diff --git a/store-conf/conf/msgs/ZsMsg_no.properties b/store-conf/conf/msgs/ZsMsg_no.properties index 5993e37c40c..445686f5232 100644 --- a/store-conf/conf/msgs/ZsMsg_no.properties +++ b/store-conf/conf/msgs/ZsMsg_no.properties @@ -475,7 +475,7 @@ zimbra_index_lucene_max_buffered_docs = Minimumsantallet dokumenter som kreves f i minnet blir t\u00f8mt som et nytt segment. Store verdier gir generelt raskere indeksering. zimbra_index_lucene_ram_buffer_size_kb = Maksimal buffer-RAM i kB for Lucene zimbra_index_lucene_io_impl = Lucene FSDirectory-implementering brukt til IU-indeksering. \ - Mulige verdier + Mulige verdier \ nio \u2013 Bruk NIOFSDirectory. Bruker FileChannels posisjon-IU i java.nio ved lesing for \u00e5 unng\u00e5 synkronisering n\u00e5r det leses fra samme fil. \ mmap \u2013 Bruk MMapDirectory. Bruker minnetilordnet IU ved lesing. Et godt valg hvis det er nok virtuelt minne i forhold til indeksst\u00f8rrelsen. \ simple \u2013 Bruk SimpleFSDirectory. Bruker java.io.RandomAccessFile. Den har d\u00e5rlig samtidig ytelse fordi den synkroniserer n\u00e5r flere tr\u00e5der leser samme fil. \ diff --git a/store-conf/conf/msgs/ZsMsg_pl.properties b/store-conf/conf/msgs/ZsMsg_pl.properties index 33fc311b8a0..9b2a2d3861f 100644 --- a/store-conf/conf/msgs/ZsMsg_pl.properties +++ b/store-conf/conf/msgs/ZsMsg_pl.properties @@ -468,7 +468,7 @@ zimbra_index_lucene_max_buffered_docs = Minimalna liczba dokument\u00f3w wymagan dokumenty zostan\u0105 zapisane jako nowy segment. Du\u017ce warto\u015bci zazwyczaj zwi\u0119kszaj\u0105 szybko\u015b\u0107 indeksowania. zimbra_index_lucene_ram_buffer_size_kb = Lucene \u2013 maksymalna wielko\u015b\u0107 bufora pami\u0119ci RAM w kB zimbra_index_lucene_io_impl = Implementacja FSDirectory u\u017cywana przez Lucene do indeksowania we/wy. \ - Mo\u017cliwe warto\u015bci + Mo\u017cliwe warto\u015bci \ nio \u2013 u\u017cywana jest implementacja NIOFSDirectory. W\u00a0celu unikni\u0119cia synchronizacji podczas odczytu z\u00a0tego samego pliku wykorzystuje pozycyjne we/wy klasy FileChannel z\u00a0interfejsu java.nio. \ mmap \u2013 u\u017cywana jest implementacja MMapDirectory. Do odczytu wykorzystuje mapowane na pami\u0119\u0107 we/wy. Dobry wyb\u00f3r, je\u015bli do dyspozycji jest relatywnie du\u017co pami\u0119ci wirtualnej wzgl\u0119dem rozmiaru indeksu. \ simple \u2013 u\u017cywana jest implementacja SimpleFSDirectory. Wykorzystuje klas\u0119 java.io.RandomAccessFile. Ma niewielk\u0105 wydajno\u015b\u0107 przy pracy wsp\u00f3\u0142bie\u017cnej, poniewa\u017c przeprowadza synchronizacj\u0119, gdy kilka w\u0105tk\u00f3w odczytuje ten sam plik. \ diff --git a/store-conf/conf/msgs/ZsMsg_pt.properties b/store-conf/conf/msgs/ZsMsg_pt.properties index 5a3bb6f0a29..b5267d6630b 100644 --- a/store-conf/conf/msgs/ZsMsg_pt.properties +++ b/store-conf/conf/msgs/ZsMsg_pt.properties @@ -475,7 +475,7 @@ zimbra_index_lucene_max_buffered_docs = O n\u00famero m\u00ednimo de documentos mem\u00f3ria interm\u00e9dia serem esvaziados como um novo segmento. Os valores mais altos fornecem geralmente uma indexa\u00e7\u00e3o mais r\u00e1pida. zimbra_index_lucene_ram_buffer_size_kb = RAM de mem\u00f3ria interm\u00e9dia m\u00e1xima do Lucene em kB zimbra_index_lucene_io_impl = Implementa\u00e7\u00e3o do FSDirectory do Lucene utilizada para a indexa\u00e7\u00e3o de E/S. \ - Valores poss\u00edveis + Valores poss\u00edveis \ nio - Utilize o NIOFSDirectory. Utiliza a E/S posicional do FileChannel do java.nio ao efetuar leituras para evitar a sincroniza\u00e7\u00e3o ao ler a partir do mesmo ficheiro. \ mmap - Utilize o MMapDirectory. Utiliza uma E/S mapeada na mem\u00f3ria ao efetuar leituras. Uma boa escolha se existir bastante mem\u00f3ria virtual relativamente ao tamanho do \u00edndice. \ simple \u2013 Utilize o SimpleFSDirectory. Utiliza o java.io.RandomAccessFile. Tem um desempenho simult\u00e2neo fraco, uma vez que efetua a sincroniza\u00e7\u00e3o sempre que v\u00e1rios encadeamentos leem o mesmo ficheiro. \ diff --git a/store-conf/conf/msgs/ZsMsg_pt_BR.properties b/store-conf/conf/msgs/ZsMsg_pt_BR.properties index 1caf7d610a3..9132c5a472f 100644 --- a/store-conf/conf/msgs/ZsMsg_pt_BR.properties +++ b/store-conf/conf/msgs/ZsMsg_pt_BR.properties @@ -416,7 +416,7 @@ zimbra_index_lucene_max_buffered_docs = O n\u00famero m\u00ednimo de documentos em buffer sejam esvaziados como um novo segmento. Grandes valores geralmente proporcionam indexa\u00e7\u00e3o mais r\u00e1pida. zimbra_index_lucene_ram_buffer_size_kb = RAM em buffer m\u00e1xima do Lucene em KB zimbra_index_lucene_io_impl = A implementa\u00e7\u00e3o do Lucene FSDirectory utilizada para indexa\u00e7\u00e3o de E/S. \ - Valores poss\u00edveis + Valores poss\u00edveis \ nio - Utiliza NIOFSDirectory. Utiliza E/S posicional java.nio's FileChannel's durante a leitura para evitar sincroniza\u00e7\u00e3o quando est\u00e1 lendo a partir do mesmo arquivo. \ mmap - Utiliza MMapDirectory. Utiliza E/S mapeada por mem\u00f3ria durante a leitura. Uma boa escolha caso haja grande quantidade de mem\u00f3ria virtual relacionada ao tamanho do \u00edndice. \ simples - Utiliza SimpleFSDirectory. Utiliza java.io.RandomAccessFile. Tem desempenho concomitante fraco pois sincroniza quando encadeamentos m\u00faltiplos fazem a leitura do mesmo arquivo. \ diff --git a/store-conf/conf/msgs/ZsMsg_ro.properties b/store-conf/conf/msgs/ZsMsg_ro.properties index 445532905f0..12003030a09 100644 --- a/store-conf/conf/msgs/ZsMsg_ro.properties +++ b/store-conf/conf/msgs/ZsMsg_ro.properties @@ -475,7 +475,7 @@ zimbra_index_lucene_max_buffered_docs = Num\u0103rul minim de documente necesare tampon s\u0103 fie golite ca segment nou. Valori mai mari ofer\u0103 \u00een general o indexare mai rapid\u0103. zimbra_index_lucene_ram_buffer_size_kb = RAM maxim\u0103 tamponat\u0103 pentru Lucene, \u00een KB zimbra_index_lucene_io_impl = Implementarea Lucene FSDirectory utilizat\u0103 pentru indexarea IO. \ - Valori posibile + Valori posibile \ nio - Utilizeaz\u0103 NIOFSDirectory. Utilizeaz\u0103 IO pozi\u0163ional\u0103 a FileChannel din java.nio la citire pentru a evita sincronizarea la citirea din acela\u015fi fi\u015fier. \ mmap - Utilizeaz\u0103 MMapDirectory. Utilizeaz\u0103 IO mapate \u00een memorie \u00een momentul citirii. Este o alegere bun\u0103 dac\u0103 exist\u0103 destul\u0103 memorie virtual\u0103 fa\u0163\u0103 de dimensiunea indec\u015filor. \ simple - Utilizeaz\u0103 SimpleFSDirectory. Utilizeaz\u0103 java.io.RandomAccessFile. Are o performan\u0163\u0103 slab\u0103 la opera\u0163ii concurente deoarece sincronizeaz\u0103 c\u00e2nd mai multe fire de execu\u0163ie citesc acela\u015fi fi\u015fier. \ diff --git a/store-conf/conf/msgs/ZsMsg_ru.properties b/store-conf/conf/msgs/ZsMsg_ru.properties index 69b3f49527d..73505a2060a 100644 --- a/store-conf/conf/msgs/ZsMsg_ru.properties +++ b/store-conf/conf/msgs/ZsMsg_ru.properties @@ -416,7 +416,7 @@ zimbra_index_lucene_max_buffered_docs = \u041c\u0438\u043d\u0438\u043c\u0430\u04 \u0438\u0437 \u0431\u0443\u0444\u0435\u0440\u0430 \u043f\u0430\u043c\u044f\u0442\u0438 \u0432 \u043d\u043e\u0432\u044b\u0439 \u0441\u0435\u0433\u043c\u0435\u043d\u0442. \u041f\u0440\u0438 \u0431\u043e\u043b\u044c\u0448\u0438\u0445 \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u044f\u0445 \u0438\u043d\u0434\u0435\u043a\u0441\u0430\u0446\u0438\u044f \u043e\u0431\u044b\u0447\u043d\u043e \u0443\u0441\u043a\u043e\u0440\u044f\u0435\u0442\u0441\u044f. zimbra_index_lucene_ram_buffer_size_kb = \u041c\u0430\u043a\u0441\u0438\u043c\u0430\u043b\u044c\u043d\u044b\u0439 \u0440\u0430\u0437\u043c\u0435\u0440 (\u041a\u0411) \u041e\u0417\u0423 Lucene zimbra_index_lucene_io_impl = \u0420\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u044f Lucene FSDirectory, \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c\u0430\u044f \u0434\u043b\u044f \u0438\u043d\u0434\u0435\u043a\u0441\u0430\u0446\u0438\u0438 \u0432\u0432\u043e\u0434\u0430-\u0432\u044b\u0432\u043e\u0434\u0430. \ - \u0412\u043e\u0437\u043c\u043e\u0436\u043d\u044b\u0435 \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u044f + \u0412\u043e\u0437\u043c\u043e\u0436\u043d\u044b\u0435 \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u044f \ nio - \u0418\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c NIOFSDirectory. \u0414\u043b\u044f \u0447\u0442\u0435\u043d\u0438\u044f \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442\u0441\u044f \u043f\u043e\u0437\u0438\u0446\u0438\u043e\u043d\u043d\u044b\u0439 \u0432\u0432\u043e\u0434-\u0432\u044b\u0432\u043e\u0434 FileChannel \u0432 java.nio \u0434\u043b\u044f \u043f\u0440\u0435\u0434\u043e\u0442\u0432\u0440\u0430\u0449\u0435\u043d\u0438\u044f \u0441\u0438\u043d\u0445\u0440\u043e\u043d\u0438\u0437\u0430\u0446\u0438\u0438 \u043f\u0440\u0438 \u0447\u0442\u0435\u043d\u0438\u0438 \u0438\u0437 \u0442\u043e\u0433\u043e \u0436\u0435 \u0444\u0430\u0439\u043b\u0430. \ mmap - \u0418\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c MMapDirectory. \u041f\u0440\u0438 \u0447\u0442\u0435\u043d\u0438\u0438 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442\u0441\u044f \u0432\u0432\u043e\u0434-\u0432\u044b\u0432\u043e\u0434 \u0441 \u043e\u0442\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u0435\u043c \u043d\u0430 \u043f\u0430\u043c\u044f\u0442\u044c. \u041f\u043e\u043b\u0435\u0437\u043d\u043e \u043f\u0440\u0438\u043c\u0435\u043d\u044f\u0442\u044c \u043f\u0440\u0438 \u0431\u043e\u043b\u044c\u0448\u043e\u043c \u043e\u0431\u044a\u0435\u043c\u0435 \u0432\u0438\u0440\u0442\u0443\u0430\u043b\u044c\u043d\u043e\u0439 \u043f\u0430\u043c\u044f\u0442\u0438 \u043f\u043e \u0441\u0440\u0430\u0432\u043d\u0435\u043d\u0438\u044e \u0441 \u0440\u0430\u0437\u043c\u0435\u0440\u043e\u043c \u0438\u043d\u0434\u0435\u043a\u0441\u0430. \ simple - \u0418\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c SimpleFSDirectory. \u0418\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442\u0441\u044f java.io.RandomAccessFile. \u042d\u0442\u043e\u0442 \u043c\u0435\u0442\u043e\u0434 \u0438\u043c\u0435\u0435\u0442 \u043d\u0438\u0437\u043a\u043e\u0435 \u043f\u0430\u0440\u0430\u043b\u043b\u0435\u043b\u044c\u043d\u043e\u0435 \u0431\u044b\u0441\u0442\u0440\u043e\u0434\u0435\u0439\u0441\u0442\u0432\u0438\u0435, \u0442\u0430\u043a \u043a\u0430\u043a \u0441\u0438\u043d\u0445\u0440\u043e\u043d\u0438\u0437\u0430\u0446\u0438\u044f \u043f\u0440\u043e\u0432\u043e\u0434\u0438\u0442\u0441\u044f \u043f\u0440\u0438 \u0447\u0442\u0435\u043d\u0438\u0438 \u0438\u0437 \u043e\u0434\u043d\u043e\u0433\u043e \u0444\u0430\u0439\u043b\u0430 \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u0438\u043c\u0438 \u043f\u043e\u0442\u043e\u043a\u0430\u043c\u0438. \ diff --git a/store-conf/conf/msgs/ZsMsg_sl.properties b/store-conf/conf/msgs/ZsMsg_sl.properties index 06d0f2c1c68..0a249634895 100644 --- a/store-conf/conf/msgs/ZsMsg_sl.properties +++ b/store-conf/conf/msgs/ZsMsg_sl.properties @@ -475,7 +475,7 @@ zimbra_index_lucene_max_buffered_docs = Najmanj\u0161e \u0161tevilo potrebnih do predpomnilniku strnejo kot nov segment. Velike vrednosti obi\u010dajno pomenijo hitrej\u0161e indeksiranje. zimbra_index_lucene_ram_buffer_size_kb = Lucene najv. medpomn. RAM v KB zimbra_index_lucene_io_impl = Uvedba Lucene FSDirectory uporabljena za V/I indeksiranja. \ - Mo\u017ene vrednosti + Mo\u017ene vrednosti \ nio - Uporabite NIOFSDirectory. Pri branju uporablja pozicijski V/I datote\u010dnega kanala java.nio, da prepre\u010di sinhronizacijo pri branju iz iste datoteke. \ mmap - Uporabite MMapDirectory. Pri branju uporablja V/I s preslikavo pomnilnika. Dober izbor, \u010de je glede na velikost kazala na voljo veliko navideznega pomnilnika. \ simple - Uporabite SimpleFSDirectory. Uporablja java.io.RandomAccessFile. Ima slabo so\u010dasno zmogljivost, ker se sinhronizira, ko ve\u010d niti bere isto datoteko. \ diff --git a/store-conf/conf/msgs/ZsMsg_sv.properties b/store-conf/conf/msgs/ZsMsg_sv.properties index 04ed4bc317c..027a4e68592 100644 --- a/store-conf/conf/msgs/ZsMsg_sv.properties +++ b/store-conf/conf/msgs/ZsMsg_sv.properties @@ -416,7 +416,7 @@ zimbra_index_lucene_max_buffered_docs = Det minsta antal dokument som kr\u00e4vs i minnet t\u00f6ms som ett nytt segment. Stora v\u00e4rden ger generellt snabbare indexering. zimbra_index_lucene_ram_buffer_size_kb = Maximalt buffrat RAM i kB f\u00f6r Lucene zimbra_index_lucene_io_impl = Lucene FSDirectory-implementering som anv\u00e4nds f\u00f6r IO-indexering. \ - M\u00f6jliga v\u00e4rden + M\u00f6jliga v\u00e4rden \ nio - Anv\u00e4nd NIOFSDirectory. Anv\u00e4nder java.nios FileChannels positions-IO vid l\u00e4sning f\u00f6r att undvika synkronisering vid l\u00e4sning fr\u00e5n samma fil. \ mmap - Anv\u00e4nd MMapDirectory. Anv\u00e4nder minnesmappade IO vid l\u00e4sning. Ett bra val om det finns gott om virtuellt minne i relation till indexstorleken. \ simple - Anv\u00e4nd SimpleFSDirectory. Anv\u00e4nder java.io.RandomAccessFile. Den har d\u00e5lig samtidig prestanda eftersom den synkroniserar n\u00e4r flera tr\u00e5da l\u00e4ser samma fil. \ diff --git a/store-conf/conf/msgs/ZsMsg_th.properties b/store-conf/conf/msgs/ZsMsg_th.properties index 7f20aaa18a0..a6e099abb2b 100644 --- a/store-conf/conf/msgs/ZsMsg_th.properties +++ b/store-conf/conf/msgs/ZsMsg_th.properties @@ -475,7 +475,7 @@ zimbra_index_lucene_max_buffered_docs = \u0e08\u0e33\u0e19\u0e27\u0e19\u0e40\u0e \u0e02\u0e2d\u0e07\u0e40\u0e2d\u0e01\u0e2a\u0e32\u0e23\u0e08\u0e30\u0e16\u0e39\u0e01\u0e25\u0e49\u0e32\u0e07\u0e43\u0e2b\u0e49\u0e40\u0e1b\u0e47\u0e19\u0e2a\u0e48\u0e27\u0e19\u0e43\u0e2b\u0e21\u0e48 \u0e1b\u0e01\u0e15\u0e34\u0e41\u0e25\u0e49\u0e27\u0e04\u0e48\u0e32\u0e17\u0e35\u0e48\u0e2a\u0e39\u0e07\u0e08\u0e30\u0e17\u0e33\u0e14\u0e31\u0e0a\u0e19\u0e35\u0e44\u0e14\u0e49\u0e40\u0e23\u0e47\u0e27\u0e01\u0e27\u0e48\u0e32 zimbra_index_lucene_ram_buffer_size_kb = \u0e1a\u0e31\u0e1f\u0e40\u0e1f\u0e2d\u0e23\u0e4c RAM \u0e2a\u0e39\u0e07\u0e2a\u0e38\u0e14\u0e2a\u0e33\u0e2b\u0e23\u0e31\u0e1a Lucene \u0e40\u0e1b\u0e47\u0e19 KB zimbra_index_lucene_io_impl = \u0e01\u0e32\u0e23\u0e19\u0e33 Lucene FSDirectory \u0e44\u0e1b\u0e43\u0e0a\u0e49\u0e07\u0e32\u0e19\u0e42\u0e14\u0e22\u0e43\u0e0a\u0e49\u0e2a\u0e33\u0e2b\u0e23\u0e31\u0e1a\u0e01\u0e32\u0e23\u0e17\u0e33\u0e14\u0e31\u0e0a\u0e19\u0e35 IO \ - \u0e04\u0e48\u0e32\u0e17\u0e35\u0e48\u0e40\u0e1b\u0e47\u0e19\u0e44\u0e1b\u0e44\u0e14\u0e49 + \u0e04\u0e48\u0e32\u0e17\u0e35\u0e48\u0e40\u0e1b\u0e47\u0e19\u0e44\u0e1b\u0e44\u0e14\u0e49 \ nio \u2013 \u0e43\u0e0a\u0e49 NIOFSDirectory \u0e43\u0e0a\u0e49 IO \u0e15\u0e32\u0e21\u0e15\u0e33\u0e41\u0e2b\u0e19\u0e48\u0e07\u0e02\u0e2d\u0e07 FileChannel \u0e02\u0e2d\u0e07 java.nio \u0e40\u0e21\u0e37\u0e48\u0e2d\u0e2d\u0e48\u0e32\u0e19 \u0e40\u0e1e\u0e37\u0e48\u0e2d\u0e44\u0e21\u0e48\u0e43\u0e2b\u0e49\u0e40\u0e01\u0e34\u0e14\u0e01\u0e32\u0e23\u0e1b\u0e23\u0e31\u0e1a\u0e02\u0e49\u0e2d\u0e21\u0e39\u0e25\u0e43\u0e2b\u0e49\u0e15\u0e23\u0e07\u0e01\u0e31\u0e19\u0e40\u0e21\u0e37\u0e48\u0e2d\u0e2d\u0e48\u0e32\u0e19\u0e08\u0e32\u0e01\u0e44\u0e1f\u0e25\u0e4c\u0e40\u0e14\u0e35\u0e22\u0e27\u0e01\u0e31\u0e19 \ mmap \u2013 \u0e43\u0e0a\u0e49 MMapDirectory \u0e43\u0e0a\u0e49 IO \u0e17\u0e35\u0e48\u0e41\u0e21\u0e1b\u0e2b\u0e19\u0e48\u0e27\u0e22\u0e04\u0e27\u0e32\u0e21\u0e08\u0e33\u0e40\u0e21\u0e37\u0e48\u0e2d\u0e2d\u0e48\u0e32\u0e19 \u0e17\u0e32\u0e07\u0e40\u0e25\u0e37\u0e2d\u0e01\u0e17\u0e35\u0e48\u0e40\u0e2b\u0e21\u0e32\u0e30\u0e2a\u0e21\u0e16\u0e49\u0e32\u0e21\u0e35\u0e2b\u0e19\u0e48\u0e27\u0e22\u0e04\u0e27\u0e32\u0e21\u0e08\u0e33\u0e40\u0e2a\u0e21\u0e37\u0e2d\u0e19\u0e08\u0e33\u0e19\u0e27\u0e19\u0e21\u0e32\u0e01\u0e40\u0e21\u0e37\u0e48\u0e2d\u0e40\u0e17\u0e35\u0e22\u0e1a\u0e01\u0e31\u0e1a\u0e02\u0e19\u0e32\u0e14\u0e02\u0e2d\u0e07\u0e14\u0e31\u0e0a\u0e19\u0e35 \ \u0e07\u0e48\u0e32\u0e22 \u2013 \u0e43\u0e0a\u0e49 SimpleFSDirectory \u0e43\u0e0a\u0e49 java.io.RandomAccessFile \u0e0b\u0e36\u0e48\u0e07\u0e17\u0e33\u0e07\u0e32\u0e19\u0e1e\u0e23\u0e49\u0e2d\u0e21\u0e01\u0e31\u0e19\u0e44\u0e14\u0e49\u0e44\u0e21\u0e48\u0e14\u0e35\u0e19\u0e31\u0e01\u0e02\u0e13\u0e30\u0e17\u0e35\u0e48\u0e1b\u0e23\u0e31\u0e1a\u0e02\u0e49\u0e2d\u0e21\u0e39\u0e25\u0e43\u0e2b\u0e49\u0e15\u0e23\u0e07\u0e01\u0e31\u0e19\u0e40\u0e21\u0e37\u0e48\u0e2d\u0e21\u0e35\u0e2b\u0e25\u0e32\u0e22\u0e40\u0e18\u0e23\u0e14\u0e2d\u0e48\u0e32\u0e19\u0e44\u0e1f\u0e25\u0e4c\u0e40\u0e14\u0e35\u0e22\u0e27\u0e01\u0e31\u0e19 \ diff --git a/store-conf/conf/msgs/ZsMsg_tr.properties b/store-conf/conf/msgs/ZsMsg_tr.properties index 31f1469a87f..81213d3eada 100644 --- a/store-conf/conf/msgs/ZsMsg_tr.properties +++ b/store-conf/conf/msgs/ZsMsg_tr.properties @@ -474,7 +474,7 @@ zimbra_index_lucene_max_buffered_docs = Ara belle\u011fe al\u0131nm\u0131\u015f gereken minimum belge say\u0131s\u0131. B\u00fcy\u00fck de\u011ferler genellikle daha h\u0131zl\u0131 dizinleme i\u015flemi sa\u011flar. zimbra_index_lucene_ram_buffer_size_kb = Lucene Arabelle\u011fe Al\u0131nm\u0131\u015f RAM \u00dcst S\u0131n\u0131r\u0131 (KB) zimbra_index_lucene_io_impl = G\u00c7 dizinleme i\u015flemi i\u00e7in kullan\u0131lan Lcene FSDirectory uygulamas\u0131. \ - Olas\u0131 de\u011ferler + Olas\u0131 de\u011ferler \ nio - Use NIOFSDirectory. Ayn\u0131 dosya \u00fczerinden okurken e\u015fzamanlama i\u015flemini \u00f6nlemek i\u00e7in okurken java.nio FileChannel\u2019a ait konumsal g\u00e7\u2019yi kullan\u0131r. \ mmap - MMapDirectory Kullan. Okuma i\u015flemi s\u0131ras\u0131nda bellek e\u015flemeli G\u00c7 kullan\u0131r. Dizin boyutuna g\u00f6re y\u00fcksek miktarda sanal bellek varsa iyi bir se\u00e7im. \ basit - SimpleFSDirectory Kullan. java.io.RandomAccessFile kullan\u0131r. \u00c7ok i\u015f par\u00e7ac\u0131klar\u0131 ayn\u0131 dosyay\u0131 okudu\u011funda e\u015fzamanlad\u0131\u011f\u0131 i\u00e7in e\u015fzamanl\u0131 performans\u0131 d\u00fc\u015f\u00fckt\u00fcr. \ diff --git a/store-conf/conf/msgs/ZsMsg_uk.properties b/store-conf/conf/msgs/ZsMsg_uk.properties index 32b597c701d..f09ac9b28ac 100644 --- a/store-conf/conf/msgs/ZsMsg_uk.properties +++ b/store-conf/conf/msgs/ZsMsg_uk.properties @@ -475,7 +475,7 @@ zimbra_index_lucene_max_buffered_docs = \u041c\u0456\u043d\u0456\u043c\u0430\u04 \u0437 \u0431\u0443\u0444\u0435\u0440\u0430 \u043f\u0430\u043c'\u044f\u0442\u0456 \u0434\u043e \u043d\u043e\u0432\u043e\u0433\u043e \u0441\u0435\u0433\u043c\u0435\u043d\u0442\u0430. \u0417 \u0431\u0456\u043b\u044c\u0448\u0438\u043c\u0438 \u0437\u043d\u0430\u0447\u0435\u043d\u043d\u044f\u043c\u0438 \u0456\u043d\u0434\u0435\u043a\u0441\u0430\u0446\u0456\u044f \u0437\u0430\u0437\u0432\u0438\u0447\u0430\u0439 \u043f\u0440\u0438\u0441\u043a\u043e\u0440\u044e\u0454\u0442\u044c\u0441\u044f. zimbra_index_lucene_ram_buffer_size_kb = \u041c\u0430\u043a\u0441\u0438\u043c\u0430\u043b\u044c\u043d\u0438\u0439 \u043e\u0431\u0441\u044f\u0433 \u0431\u0443\u0444\u0435\u0440\u0438\u0437\u043e\u0432\u0430\u043d\u043e\u0457 \u043e\u043f\u0435\u0440\u0430\u0442\u0438\u0432\u043d\u043e\u0457 \u043f\u0430\u043c'\u044f\u0442\u0456 (\u0443 \u041a\u0431\u0430\u0439\u0442) \u0443 Lucene zimbra_index_lucene_io_impl = \u0420\u0435\u0430\u043b\u0456\u0437\u0430\u0446\u0456\u044f Lucene FSDirectory, \u044f\u043a\u0430 \u0432\u0438\u043a\u043e\u0440\u0438\u0441\u0442\u043e\u0432\u0443\u0454\u0442\u044c\u0441\u044f \u0434\u043b\u044f \u0432\u0432\u043e\u0434\u0443-\u0432\u0438\u0432\u043e\u0434\u0443 \u0456\u043d\u0434\u0435\u043a\u0441\u0430\u0446\u0456\u0457. \ - \u041c\u043e\u0436\u043b\u0438\u0432\u0456 \u0437\u043d\u0430\u0447\u0435\u043d\u043d\u044f + \u041c\u043e\u0436\u043b\u0438\u0432\u0456 \u0437\u043d\u0430\u0447\u0435\u043d\u043d\u044f \ nio - \u0412\u0438\u043a\u043e\u0440\u0438\u0441\u0442\u043e\u0432\u0443\u0432\u0430\u0442\u0438 NIOFSDirectory. \u0414\u043b\u044f \u0447\u0438\u0442\u0430\u043d\u043d\u044f \u0432\u0438\u043a\u043e\u0440\u0438\u0441\u0442\u043e\u0432\u0443\u0454\u0442\u044c\u0441\u044f \u043f\u043e\u0437\u0438\u0446\u0456\u0439\u043d\u0438\u0439 \u0432\u0432\u0456\u0434-\u0432\u0438\u0432\u0456\u0434 FileChannel \u0432 java.nio, \u0449\u043e\u0431 \u0437\u0430\u043f\u043e\u0431\u0456\u0433\u0442\u0438 \u0441\u0438\u043d\u0445\u0440\u043e\u043d\u0456\u0437\u0430\u0446\u0456\u0457 \u043f\u0456\u0434 \u0447\u0430\u0441 \u0447\u0438\u0442\u0430\u043d\u043d\u044f \u0437 \u043e\u0434\u043d\u043e\u0433\u043e \u0439 \u0442\u043e\u0433\u043e \u0441\u0430\u043c\u043e\u0433\u043e \u0444\u0430\u0439\u043b\u0443. \ mmap - \u0412\u0438\u043a\u043e\u0440\u0438\u0441\u0442\u043e\u0432\u0443\u0432\u0430\u0442\u0438 MMapDirectory. \u0414\u043b\u044f \u0447\u0438\u0442\u0430\u043d\u043d\u044f \u0432\u0438\u043a\u043e\u0440\u0438\u0441\u0442\u043e\u0432\u0443\u0454\u0442\u044c\u0441\u044f \u0432\u0432\u0456\u0434-\u0432\u0438\u0432\u0456\u0434 \u0437 \u0432\u0456\u0434\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u043d\u044f\u043c \u043d\u0430 \u043f\u0430\u043c'\u044f\u0442\u044c. \u0414\u043e\u0446\u0456\u043b\u044c\u043d\u043e \u0432\u0438\u043a\u043e\u0440\u0438\u0441\u0442\u043e\u0432\u0443\u0432\u0430\u0442\u0438, \u043a\u043e\u043b\u0438 \u043e\u0431\u0441\u044f\u0433 \u0432\u0456\u0440\u0442\u0443\u0430\u043b\u044c\u043d\u043e\u0457 \u043f\u0430\u043c'\u044f\u0442\u0456 \u0432\u0435\u043b\u0438\u043a\u0438\u0439 \u043f\u043e\u0440\u0456\u0432\u043d\u044f\u043d\u043e \u0437 \u0440\u043e\u0437\u043c\u0456\u0440\u043e\u043c \u0456\u043d\u0434\u0435\u043a\u0441\u0443. \ simple - \u0412\u0438\u043a\u043e\u0440\u0438\u0441\u0442\u043e\u0432\u0443\u0432\u0430\u0442\u0438 SimpleFSDirectory. \u0412\u0438\u043a\u043e\u0440\u0438\u0441\u0442\u043e\u0432\u0443\u0454\u0442\u044c\u0441\u044f java.io.RandomAccessFile. \u0426\u0435\u0439 \u043c\u0435\u0442\u043e\u0434 \u043c\u0430\u0454 \u043d\u0438\u0437\u044c\u043a\u0443 \u043f\u0430\u0440\u0430\u043b\u0435\u043b\u044c\u043d\u0443 \u0448\u0432\u0438\u0434\u043a\u043e\u0434\u0456\u044e, \u043e\u0441\u043a\u0456\u043b\u044c\u043a\u0438 \u0441\u0438\u043d\u0445\u0440\u043e\u043d\u0456\u0437\u0430\u0446\u0456\u044f \u0432\u0438\u043a\u043e\u043d\u0443\u0454\u0442\u044c\u0441\u044f \u043f\u0456\u0434 \u0447\u0430\u0441 \u0447\u0438\u0442\u0430\u043d\u043d\u044f \u0437 \u043e\u0434\u043d\u043e\u0433\u043e \u0444\u0430\u0439\u043b\u0443 \u043a\u0456\u043b\u044c\u043a\u043e\u043c\u0430 \u043f\u043e\u0442\u043e\u043a\u0430\u043c\u0438. \ diff --git a/store-conf/conf/msgs/ZsMsg_zh_CN.properties b/store-conf/conf/msgs/ZsMsg_zh_CN.properties index fa05ce8192d..c013a5ee8ce 100644 --- a/store-conf/conf/msgs/ZsMsg_zh_CN.properties +++ b/store-conf/conf/msgs/ZsMsg_zh_CN.properties @@ -474,7 +474,7 @@ zimbra_index_lucene_max_buffered_docs = \u7f13\u51b2\u5185\u5b58\u6587\u6863\u52 \u5c06\u6e05\u9664\u4e3a\u65b0\u6bb5\u3002\u8f83\u5927\u503c\u65f6\uff0c\u7d22\u5f15\u7684\u5efa\u7acb\u901a\u5e38\u8f83\u5feb\u3002 zimbra_index_lucene_ram_buffer_size_kb = Lucene \u6700\u5927\u7f13\u51b2 RAM (KB) zimbra_index_lucene_io_impl = Lucene FSDirectory \u5b9e\u65bd\u7528\u4e8e\u5efa\u7acb IO \u7d22\u5f15\u3002 \ - \u53ef\u80fd\u503c + \u53ef\u80fd\u503c \ nio \u2013 \u4f7f\u7528 NIOFSDirectory\u3002\u4ece\u76f8\u540c\u6587\u4ef6\u8bfb\u53d6\u4ee5\u907f\u514d\u540c\u6b65\u65f6\uff0c\u8bf7\u4f7f\u7528 java.nio FileChannel \u7684\u4f4d\u7f6e io\u3002 \ mmap \u2013 \u4f7f\u7528 MmapDirectory\u3002\u5728\u8bfb\u53d6\u65f6\uff0c\u4f7f\u7528\u5185\u5b58\u6620\u5c04\u7684 IO\u3002\u5982\u679c\u76f8\u5bf9\u4e8e\u7d22\u5f15\u5927\u5c0f\u6709\u5927\u91cf\u865a\u62df\u5185\u5b58\uff0c\u5219\u4e0d\u5931\u4e3a\u4e00\u4e2a\u597d\u7684\u9009\u62e9\u3002 \ simple \u2013 \u4f7f\u7528 SimpleFSDirectory\u3002\u4f7f\u7528 java.io.RandomAccessFile\u3002\u5f53\u6709\u591a\u4e2a\u7ebf\u7a0b\u8bfb\u53d6\u76f8\u540c\u6587\u4ef6\u65f6\uff0c\u5982\u679c\u8fdb\u884c\u540c\u6b65\uff0c\u5c31\u4f1a\u9020\u6210\u5e76\u53d1\u6027\u80fd\u5dee\u3002 \ diff --git a/store-conf/conf/msgs/ZsMsg_zh_HK.properties b/store-conf/conf/msgs/ZsMsg_zh_HK.properties index 79baa13ab90..f8b7d06361e 100644 --- a/store-conf/conf/msgs/ZsMsg_zh_HK.properties +++ b/store-conf/conf/msgs/ZsMsg_zh_HK.properties @@ -430,7 +430,7 @@ zimbra_index_lucene_max_buffered_docs = \u5728\u8a18\u61b6\u9ad4\u5167\u7de9\u88 \u6700\u5c11\u6587\u4ef6\u6578\u91cf\u3002\u5927\u6578\u503c\u901a\u5e38\u6703\u52a0\u5feb\u7d22\u5f15\u5efa\u7acb\u904e\u7a0b\u3002 zimbra_index_lucene_ram_buffer_size_kb = Lucene \u7de9\u885d RAM \u6578\u4e0a\u9650 (KB) zimbra_index_lucene_io_impl = \u7d22\u5f15 IO \u4f7f\u7528\u7684 Lucene FSDirectory \u5de5\u5177\u3002 \ - \u53ef\u7528\u6578\u503c + \u53ef\u7528\u6578\u503c \ nio \u2013 \u4f7f\u7528 NIOFSDirectory\u3002\u8b80\u53d6\u6642\u4f7f\u7528 java.nio \u7684 FileChannel \u5b9a\u4f4d io\uff0c\u4ee5\u907f\u514d\u8b80\u53d6\u540c\u4e00\u6a94\u6848\u6642\u7684\u540c\u6b65\u52d5\u4f5c\u3002 \ mmap \u2013 \u4f7f\u7528 MmapDirectory\u3002\u8b80\u53d6\u6642\u4f7f\u7528\u8a18\u61b6\u5c0d\u61c9 IO\u3002\u76f8\u5c0d\u65bc\u7d22\u5f15\u5927\u5c0f\uff0c\u82e5\u6709\u8a31\u591a\u865b\u64ec\u5167\u5b58\uff0c\u5247\u4e0d\u5931\u70ba\u4e00\u9805\u8f03\u597d\u7684\u9078\u64c7\u3002 \ simple \u2013 \u4f7f\u7528 SimpleFSDirectory\u3002\u4f7f\u7528 java.io.RandomAccessFile\u3002\u7576\u591a\u500b\u57f7\u884c\u7dd2\u8b80\u53d6\u540c\u4e00\u6a94\u6848\u6642\uff0c\u9019\u4e00\u5de5\u5177\u7684\u4e26\u884c\u6548\u80fd\u5728\u540c\u6b65\u6642\u8f03\u5dee\u3002 \ diff --git a/store-conf/conf/msgs/ZsMsg_zh_TW.properties b/store-conf/conf/msgs/ZsMsg_zh_TW.properties index 43d56ab7877..0aad75e48ec 100644 --- a/store-conf/conf/msgs/ZsMsg_zh_TW.properties +++ b/store-conf/conf/msgs/ZsMsg_zh_TW.properties @@ -474,7 +474,7 @@ zimbra_index_lucene_max_buffered_docs = \u7de9\u885d\u8a18\u61b6\u9ad4\u6587\u4e \u5c07\u6e05\u9664\u70ba\u65b0\u6bb5\u3002\u8f03\u5927\u503c\u6642\uff0c\u7d22\u5f15\u7684\u5efa\u7acb\u901a\u5e38\u8f03\u5feb\u3002 zimbra_index_lucene_ram_buffer_size_kb = Lucene \u6700\u5927\u7de9\u885d RAM (\u4ee5 KB \u70ba\u55ae\u4f4d) zimbra_index_lucene_io_impl = Lucene FSDirectory \u5be6\u65bd\u7528\u65bc\u5efa\u7acb IO \u7d22\u5f15\u3002 \ - \u53ef\u80fd\u503c + \u53ef\u80fd\u503c \ nio \u2013 \u4f7f\u7528 NIOFSDirectory\u3002\u5f9e\u76f8\u540c\u6a94\u6848\u8b80\u53d6\u4ee5\u907f\u514d\u540c\u6b65\u6642\uff0c\u8acb\u4f7f\u7528 java.nio FileChannel \u7684\u4f4d\u7f6e io\u3002 \ mmap \u2013 \u4f7f\u7528 MmapDirectory\u3002\u5728\u8b80\u53d6\u6642\uff0c\u4f7f\u7528\u8a18\u61b6\u9ad4\u5c0d\u61c9\u7684 IO\u3002\u5982\u679c\u76f8\u5c0d\u65bc\u7d22\u5f15\u5927\u5c0f\u6709\u5927\u91cf\u865b\u64ec\u8a18\u61b6\u9ad4\uff0c\u5247\u4e0d\u5931\u70ba\u4e00\u500b\u597d\u7684\u9078\u64c7\u3002 \ simple \u2013 \u4f7f\u7528 SimpleFSDirectory\u3002\u4f7f\u7528 java.io.RandomAccessFile\u3002\u7576\u6709\u591a\u500b\u57f7\u884c\u7dd2\u8b80\u53d6\u76f8\u540c\u6a94\u6848\u6642\uff0c\u5982\u679c\u9032\u884c\u540c\u6b65\uff0c\u5c31\u6703\u9020\u6210\u4f75\u767c\u6548\u80fd\u5dee\u3002 \ diff --git a/store/conf/attrs/zimbra-attrs.xml b/store/conf/attrs/zimbra-attrs.xml index 041443225bf..afe63263d49 100755 --- a/store/conf/attrs/zimbra-attrs.xml +++ b/store/conf/attrs/zimbra-attrs.xml @@ -121,7 +121,8 @@ TODO - add support for multi-line values in globalConfigValue and defaultCOSValu mailRecipient, account, alias, distributionList, cos, globalConfig, domain, securityGroup, server, mimeEntry, objectEntry, zimletEntry, calendarResource; - attribute, alwaysOnCluster + attribute, alwaysOnCluster, dataSource, pop3DataSource, + rssDataSource, imapDataSource, galDataSource flags: accountInfo............returned as part of the GetInfo call @@ -8944,19 +8945,19 @@ TODO: delete them permanently from here Subject prefix for the spam training messages used to sent to the zimbraSpamIsSpamAccount/zimbraSpamIsNotSpamAccount account. - + Client Id for OAuth token - + Client Secret for OAuth token - + Refresh token for authentication using OAuth - + Url for refreshing OAuth Token @@ -9376,14 +9377,73 @@ TODO: delete them permanently from here Whether to enable zimbra network new generation modules. - - TRUE + + FALSE Whether to enable zimbra network new generation mobile sync module. - + TRUE Whether to enable old zimbra network admin module. + This attribute has been renamed to zimbraNetworkAdminNGEnabled + + + + TRUE + Whether the declaration of the Sieve extension feature is mandatory by the 'require' control. If TRUE, before ZCS evaluates a Sieve extension test or action, it checks the corresponding capability string at 'require' control; and if the capability string is not declared in the 'require', the entire Sieve filter execution will be failed. If FALSE, any Sieve extensions can be used without declaring the capability string in the 'require' control. + + + + FALSE + Whether edit header commands in admin sieve scripts are enabled or disabled. If TRUE, the addheader, deleteheader and replaceheader commands will be executed during admin sieve script execution. + + + + Received,DKIM-Signature,Authentication-Results,Received-SPF,Message-ID,Content-Type,Content-Disposition,Content-Transfer-Encoding,MIME-Version,Auto-Submitted + Comma separated list of sieve immutable headers + + + + FALSE + Mark messages sent to a forwarding address as read + + + + 1d + + Sleep time between subsequent contact backups. 0 means that contact + backup is disabled. + + + + + 15d + + Duration for which the backups should be preserved. + + + + + FALSE + Enable end-user email address verification + + + + 1d + Expiry time for end-user email address verification + + + + RFC822 email address under verification for an account + + + + End-user email address verification status + + + + FALSE + Whether to enable zimbra network new generation admin module. @@ -9549,18 +9609,4 @@ TODO: delete them permanently from here Information about the latest run of zmmigrateattrs. Includes the URL of the destination ephemeral store and the state of the migration (in progress, completed, failed) - - TRUE - Whether the declaration of the Sieve extension feature is mandatory by the 'require' control. If TRUE, before ZCS evaluates a Sieve extension test or action, it checks the corresponding capability string at 'require' control; and if the capability string is not declared in the 'require', the entire Sieve filter execution will be failed. If FALSE, any Sieve extensions can be used without declaring the capability string in the 'require' control. - - - - FALSE - Whether edit header commands in admin sieve scripts are enabled or disabled. If TRUE, the addheader, deleteheader and replaceheader commands will be executed during admin sieve script execution. - - - - Received,DKIM-Signature,Authentication-Results,Received-SPF,Message-ID,Content-Type,Content-Disposition,Content-Transfer-Encoding,MIME-Version,Auto-Submitted - Comma separated list of sieve immutable headers - diff --git a/store/docs/rest.txt b/store/docs/rest.txt index 49dbf98fa58..5ea7983865b 100644 --- a/store/docs/rest.txt +++ b/store/docs/rest.txt @@ -52,7 +52,7 @@ URL ------------------------------------------------------------ http://server/home/[~][{username}]/[{folder}]?[{query-params}] - fmt={ics, csv, etc} + fmt={ics, csv, ldif etc} id={item-id} list={item-id}*[,{item-id}] imap_id={item-imap-id} (must also specify folder) diff --git a/store/docs/soap-admin.txt b/store/docs/soap-admin.txt index 0a7d68c0577..0895881c14d 100644 --- a/store/docs/soap-admin.txt +++ b/store/docs/soap-admin.txt @@ -2997,6 +2997,16 @@ Tests: + + + + + @@ -3009,6 +3019,8 @@ Tests: + + {method}+ diff --git a/store/docs/soap.txt b/store/docs/soap.txt index 43c75186d1f..2aaef1bbfa9 100644 --- a/store/docs/soap.txt +++ b/store/docs/soap.txt @@ -3259,6 +3259,16 @@ Tests: + + + + + @@ -3271,6 +3281,8 @@ Tests: + + {method}+ @@ -4682,3 +4694,33 @@ If IMAP tracking is already enabled, does nothing. + +----------------------------- +API to restore contact backup file from the contact backup list + + + +{restore_resolve} = ignore | modify | reset | replace + +Note: First, retrieve the contact backup file name with GetContactBackupList Soap API, +then send that file name in 'contactsBackupFileName' parameter of RestoreContactsRequest. +RestoreContactsRequest will search for file inside 'ContactsBackup' briefcase folder +and if found, will restore it. +The 'resolve' parameter is optional. It supports ignore, modify, reset, replace options in +import Rest API. By default, the resolve action is 'reset'. + + + + +----------------------------- +Api to get list of available contact backup files + + + + [ + file1_name + file2_name + file3_name + ... + ] + diff --git a/store/ivy.xml b/store/ivy.xml index 59b312ed8ac..b7223e3dc02 100644 --- a/store/ivy.xml +++ b/store/ivy.xml @@ -5,13 +5,15 @@ - - - - - - - + + + + + + + + + @@ -70,6 +72,7 @@ + diff --git a/store/src/java-test/Truncated.tgz b/store/src/java-test/Truncated.tgz new file mode 100644 index 00000000000..c33b1d85abe Binary files /dev/null and b/store/src/java-test/Truncated.tgz differ diff --git a/store/src/java-test/backup_dummy_test.tgz b/store/src/java-test/backup_dummy_test.tgz new file mode 100644 index 00000000000..e69de29bb2d diff --git a/store/src/java-test/com/zimbra/cs/account/ExtShareInfoTest.java b/store/src/java-test/com/zimbra/cs/account/ExtShareInfoTest.java index c812ac9ebb6..ef8bff0ce79 100644 --- a/store/src/java-test/com/zimbra/cs/account/ExtShareInfoTest.java +++ b/store/src/java-test/com/zimbra/cs/account/ExtShareInfoTest.java @@ -33,6 +33,7 @@ import com.google.common.collect.Maps; import com.zimbra.common.service.ServiceException; +import com.zimbra.common.util.L10nUtil; import com.zimbra.cs.mailbox.ACL; import com.zimbra.cs.mailbox.MailItem; import com.zimbra.cs.mailbox.MailboxManager; @@ -66,6 +67,7 @@ public void setUp() throws Exception { // this MailboxManager does everything except use SMTP to deliver mail MailboxManager.setInstance(new DirectInsertionMailboxManager()); + L10nUtil.setMsgClassLoader("../store-conf/conf/msgs"); } @Test @@ -90,7 +92,6 @@ public void testGenNotifyBody() { sid.setOwnerAcctDisplayName("Demo User Two"); try { - sid.setRights(ACL.stringToRights("rwidxap")); MimeMultipart mmp = ShareInfo.NotificationSender.genNotifBody(sid, notes, locale, null, null); diff --git a/store/src/java-test/com/zimbra/cs/datasource/DataSourceManagerTest.java b/store/src/java-test/com/zimbra/cs/datasource/DataSourceManagerTest.java new file mode 100644 index 00000000000..77a0b75eb6e --- /dev/null +++ b/store/src/java-test/com/zimbra/cs/datasource/DataSourceManagerTest.java @@ -0,0 +1,134 @@ +/* + * ***** BEGIN LICENSE BLOCK ***** + * Zimbra Collaboration Suite Server + * Copyright (C) 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2016 Synacor, Inc. + * + * This program is free software: you can redistribute it and/or modify it under + * the terms of the GNU General Public License as published by the Free Software Foundation, + * version 2 of the License. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * You should have received a copy of the GNU General Public License along with this program. + * If not, see . + * ***** END LICENSE BLOCK ***** + */ + +package com.zimbra.cs.datasource; + +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + +import java.util.HashMap; +import java.util.Map; + +import org.junit.After; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; + +import com.zimbra.common.service.ServiceException; +import com.zimbra.cs.account.Account; +import com.zimbra.cs.account.DataSource; +import com.zimbra.cs.account.DataSource.DataImport; +import com.zimbra.cs.account.Provisioning; +import com.zimbra.cs.datasource.imap.ImapSync; +import com.zimbra.cs.gal.GalImport; +import com.zimbra.cs.mailbox.MailboxTestUtil; +import com.zimbra.soap.admin.type.DataSourceType; + +public class DataSourceManagerTest { + private Account testAccount = null; + private String OAUTH_DS_ID = "testOAuthDS"; + private String POP3_DS_ID = "testPop3DS"; + private String IMAP_DS_ID = "testImap3DS"; + private String CALDAV_DS_ID = "CalDavDS"; + private String RSS_DS_ID = "RSSDataSource"; + private String CAL_DS_ID = "CalDataSource"; + private String GAL_DS_ID = "GALDataSource"; + + private String OAUTH_DS_NAME = "TestOAuthDataSource"; + private String POP3_DS_NAME = "TestPop3DataSource"; + private String IMAP_DS_NAME = "TestImapDataSource"; + private String CALDAV_DS_NAME = "TestCalDavDataSource"; + private String RSS_DS_NAME = "TestRSSDataSource"; + private String CAL_DS_NAME = "TestCalDataSource"; + private String GAL_DS_NAME = "TestGALDataSource"; + + @BeforeClass + public static void init() throws Exception { + MailboxTestUtil.initServer(); + } + + @Before + public void setUp() throws Exception { + MailboxTestUtil.clearData(); + Provisioning prov = Provisioning.getInstance(); + testAccount = prov.createAccount("test@zimbra.com", "secret", new HashMap()); + } + + @After + public void tearDown() throws Exception { + MailboxTestUtil.clearData(); + } + + @Test + public void testGetDataImportWithDefaultClass() throws ServiceException { + Map testAttrs = new HashMap(); + testAttrs.put(Provisioning.A_zimbraDataSourceDomain, "zimbra.com"); + testAttrs.put(Provisioning.A_zimbraDataSourcePort, "1234"); + testAttrs.put(Provisioning.A_zimbraDataSourceHost, "localhost"); + testAttrs.put(Provisioning.A_zimbraDataSourceUsername, "test"); + testAttrs.put(Provisioning.A_zimbraDataSourcePassword, "test"); + + DataSource ds = new DataSource(testAccount, DataSourceType.pop3, POP3_DS_NAME, POP3_DS_ID, testAttrs, null); + DataImport di = DataSourceManager.getInstance().getDataImport(ds); + assertNotNull("DataImport should not be NULL", di); + assertTrue("DataImport for 'pop3' should be Pop3Sync", di instanceof Pop3Sync); + + ds = new DataSource(testAccount, DataSourceType.imap, IMAP_DS_NAME, IMAP_DS_ID, testAttrs, null); + di = DataSourceManager.getInstance().getDataImport(ds); + assertNotNull("DataImport should not be NULL", di); + assertTrue("DataImport for 'imap' should be ImapSync", di instanceof ImapSync); + + ds = new DataSource(testAccount, DataSourceType.caldav, CALDAV_DS_NAME, CALDAV_DS_ID, testAttrs, null); + di = DataSourceManager.getInstance().getDataImport(ds); + assertNotNull("DataImport should not be NULL", di); + assertTrue("DataImport for 'caldav' should be CalDavDataImport", di instanceof CalDavDataImport); + + ds = new DataSource(testAccount, DataSourceType.rss, RSS_DS_NAME, RSS_DS_ID, testAttrs, null); + di = DataSourceManager.getInstance().getDataImport(ds); + assertNotNull("DataImport should not be NULL", di); + assertTrue("DataImport for 'rss' should be RssImport", di instanceof RssImport); + + ds = new DataSource(testAccount, DataSourceType.cal, CAL_DS_NAME, CAL_DS_ID, testAttrs, null); + di = DataSourceManager.getInstance().getDataImport(ds); + assertNotNull("DataImport should not be NULL", di); + assertTrue("DataImport for 'cal' should be RssImport", di instanceof RssImport); + + ds = new DataSource(testAccount, DataSourceType.gal, GAL_DS_NAME, GAL_DS_ID, testAttrs, null); + di = DataSourceManager.getInstance().getDataImport(ds); + assertNotNull("DataImport should not be NULL", di); + assertTrue("DataImport for 'gal' should be GalImport", di instanceof GalImport); + } + + @Test + public void testGetDataImportClass() throws ServiceException { + Map testAttrs = new HashMap(); + testAttrs.put(Provisioning.A_zimbraDataSourceDomain, "zimbra.com"); + testAttrs.put(Provisioning.A_zimbraDataSourceImportClassName, "com.zimbra.cs.datasource.DataSourceManagerTest.TestDSImport"); + DataSource ds = new DataSource(testAccount, DataSourceType.unknown, OAUTH_DS_NAME, OAUTH_DS_ID, testAttrs, null); + assertNotNull("DataSource should not be NULL", ds); + DataImport di = DataSourceManager.getInstance().getDataImport(ds); + assertNull("should not be able to instantiate non existent DataImport class", di); + + testAttrs.put(Provisioning.A_zimbraDataSourceImportClassName, "com.zimbra.cs.gal.GalImport"); + ds = new DataSource(testAccount, DataSourceType.unknown, OAUTH_DS_NAME, OAUTH_DS_ID, testAttrs, null); + assertNotNull("DataSource should not be NULL", ds); + di = DataSourceManager.getInstance().getDataImport(ds); + assertNotNull("DataImport should not be NULL", di); + assertTrue("DataImport for 'unknown' should be GalImport", di instanceof GalImport); + } + } diff --git a/store/src/java-test/com/zimbra/cs/extension/ExtensionTestUtil.java b/store/src/java-test/com/zimbra/cs/extension/ExtensionTestUtil.java index 4736a1bfb50..59f1da7ab77 100644 --- a/store/src/java-test/com/zimbra/cs/extension/ExtensionTestUtil.java +++ b/store/src/java-test/com/zimbra/cs/extension/ExtensionTestUtil.java @@ -30,7 +30,7 @@ public class ExtensionTestUtil { private static URL classpath; public static void init() throws Exception { - classpath = new File("build/test/extensions").toURI().toURL(); + classpath = new File("store/build/test/extensions").toURI().toURL(); LC.zimbra_extension_common_directory.setDefault(null); LC.zimbra_extension_directory.setDefault(null); } diff --git a/store/src/java-test/com/zimbra/cs/filter/AddressTest.java b/store/src/java-test/com/zimbra/cs/filter/AddressTest.java index b4f446d2c2b..9792f2050db 100644 --- a/store/src/java-test/com/zimbra/cs/filter/AddressTest.java +++ b/store/src/java-test/com/zimbra/cs/filter/AddressTest.java @@ -1,7 +1,7 @@ /* * ***** BEGIN LICENSE BLOCK ***** * Zimbra Collaboration Suite Server - * Copyright (C) 2014, 2016 Synacor, Inc. + * Copyright (C) 2014, 2016, 2017 Synacor, Inc. * * This program is free software: you can redistribute it and/or modify it under * the terms of the GNU General Public License as published by the Free Software Foundation, @@ -340,7 +340,8 @@ public void compareEmptyStringWithAsciiNumeric() { RuleManager.clearCachedRules(acct); Mailbox mbox = MailboxManager.getInstance().getMailboxByAccount(acct); - String filterScript = "if address :is :comparator \"i;ascii-numeric\" \"To\" \"\" {" + String filterScript = "require [\"comparator-i;ascii-numeric\"];" + + "if address :is :comparator \"i;ascii-numeric\" \"To\" \"\" {" + " tag \"compareEmptyStringWithAsciiNumeric\";" + "}"; @@ -363,7 +364,7 @@ public void testNumericNegativeValueIs() { RuleManager.clearCachedRules(acct); Mailbox mbox = MailboxManager.getInstance().getMailboxByAccount(acct); - String filterScript = "require \"tag\";\n" + String filterScript = "require [\"tag\", \"relational\", \"comparator-i;ascii-numeric\"];\n" + "if address :count \"lt\" :comparator \"i;ascii-numeric\" \"To\" \"-1\" {" + " tag \"compareAsciiNumericNegativeValue\";" + "}"; @@ -387,7 +388,7 @@ public void compareHeaderNameWithLeadingSpaces() { RuleManager.clearCachedRules(acct); Mailbox mbox = MailboxManager.getInstance().getMailboxByAccount(acct); - String filterScript = "require [\"tag\"];\n" + String filterScript = "require [\"tag\", \"comparator-i;ascii-numeric\"];\n" + "if address :is :comparator \"i;ascii-numeric\" \" To\" \"test1@zimbra.com\" {" + " tag \"t1\";" + "}" @@ -412,7 +413,7 @@ public void compareHeaderNameWithTrailingSpaces() { RuleManager.clearCachedRules(acct); Mailbox mbox = MailboxManager.getInstance().getMailboxByAccount(acct); - String filterScript = "require [\"tag\"];\n" + String filterScript = "require [\"tag\", \"comparator-i;ascii-numeric\"];\n" + "if address :is :comparator \"i;ascii-numeric\" \"To \" \"test1@zimbra.com\" {" + " tag \"t2\";" + "}" @@ -437,7 +438,7 @@ public void compareHeaderNameWithLeadingAndTrailingSpaces() { RuleManager.clearCachedRules(acct); Mailbox mbox = MailboxManager.getInstance().getMailboxByAccount(acct); - String filterScript = "require [\"tag\"];\n" + String filterScript = "require [\"tag\", \"comparator-i;ascii-numeric\"];\n" + "if address :is :comparator \"i;ascii-numeric\" \" To \" \"test1@zimbra.com\" {" + " tag \"t3\";" + "}" @@ -521,4 +522,41 @@ public void testMalencodedHeader() throws Exception { fail("No exception should be thrown" + e); } } + + /* + * The ascii-numeric comparator should be looked up in the list of the "require". + */ + @Test + public void testMissingComparatorNumericDeclaration() throws Exception { + // Default match type :is is used. + // No "comparator-i;ascii-numeric" capability text in the require command + String filterScript = "require [\"tag\"];" + + "if address :comparator \"i;ascii-numeric\" \"To\" \"test1@zimbra.com\" {\n" + + " tag \"is\";\n" + + "} else {\n" + + " tag \"not is\";\n" + + "}"; + try { + Account account = Provisioning.getInstance().getAccount( + MockProvisioning.DEFAULT_ACCOUNT_ID); + RuleManager.clearCachedRules(account); + Mailbox mbox = MailboxManager.getInstance().getMailboxByAccount(account); + + account.unsetAdminSieveScriptBefore(); + account.unsetMailSieveScript(); + account.unsetAdminSieveScriptAfter(); + account.setMailSieveScript(filterScript); + List ids = RuleManager.applyRulesToIncomingMessage( + new OperationContext(mbox), mbox, + new ParsedMessage("To: test1@zimbra.com\nSubject: example\n".getBytes(), false), 0, + account.getName(), + new DeliveryContext(), + Mailbox.ID_FOLDER_INBOX, true); + Assert.assertEquals(1, ids.size()); + Message msg = mbox.getMessageById(null, ids.get(0).getId()); + Assert.assertEquals(null, ArrayUtil.getFirstElement(msg.getTags())); + } catch (Exception e) { + fail("No exception should be thrown " + e); + } + } } diff --git a/store/src/java-test/com/zimbra/cs/filter/BodyTest.java b/store/src/java-test/com/zimbra/cs/filter/BodyTest.java index 78644d05592..6b512076002 100644 --- a/store/src/java-test/com/zimbra/cs/filter/BodyTest.java +++ b/store/src/java-test/com/zimbra/cs/filter/BodyTest.java @@ -1,7 +1,7 @@ /* * ***** BEGIN LICENSE BLOCK ***** * Zimbra Collaboration Suite Server - * Copyright (C) 2016 Synacor, Inc. + * Copyright (C) 2016, 2017 Synacor, Inc. * * This program is free software: you can redistribute it and/or modify it under * the terms of the GNU General Public License as published by the Free Software Foundation, @@ -16,6 +16,8 @@ */ package com.zimbra.cs.filter; +import static org.junit.Assert.fail; + import java.util.HashMap; import java.util.List; @@ -24,6 +26,7 @@ import org.junit.BeforeClass; import org.junit.Test; +import com.zimbra.common.util.ArrayUtil; import com.zimbra.cs.account.Account; import com.zimbra.cs.account.MockProvisioning; import com.zimbra.cs.account.Provisioning; @@ -107,4 +110,41 @@ private void test(String script, String message) throws Exception { Message msg = mbox.getMessageById(null, ids.get(0).getId()); Assert.assertTrue(msg.isTagged(FlagInfo.FLAGGED)); } + + /* + * The ascii-numeric comparator should be looked up in the list of the "require". + */ + @Test + public void testMissingComparatorNumericDeclaration() throws Exception { + // Default match type :is is used. + // No "comparator-i;ascii-numeric" capability text in the require command + String filterScript = "require [\"tag\"];" + + "if body :contains :comparator \"i;ascii-numeric\" \"Sample message\" {\n" + + " tag \"contains\";\n" + + "} else {\n" + + " tag \"not contains\";\n" + + "}"; + try { + Account account = Provisioning.getInstance().getAccount( + MockProvisioning.DEFAULT_ACCOUNT_ID); + RuleManager.clearCachedRules(account); + Mailbox mbox = MailboxManager.getInstance().getMailboxByAccount(account); + + account.unsetAdminSieveScriptBefore(); + account.unsetMailSieveScript(); + account.unsetAdminSieveScriptAfter(); + account.setMailSieveScript(filterScript); + List ids = RuleManager.applyRulesToIncomingMessage( + new OperationContext(mbox), mbox, + new ParsedMessage("To: test1@zimbra.com\nSubject: test\n\nSample message".getBytes(), false), 0, + account.getName(), + new DeliveryContext(), + Mailbox.ID_FOLDER_INBOX, true); + Assert.assertEquals(1, ids.size()); + Message msg = mbox.getMessageById(null, ids.get(0).getId()); + Assert.assertEquals(null, ArrayUtil.getFirstElement(msg.getTags())); + } catch (Exception e) { + fail("No exception should be thrown " + e); + } + } } diff --git a/store/src/java-test/com/zimbra/cs/filter/DeleteHeaderTest.java b/store/src/java-test/com/zimbra/cs/filter/DeleteHeaderTest.java index 33aea5ba016..01b4a667c72 100644 --- a/store/src/java-test/com/zimbra/cs/filter/DeleteHeaderTest.java +++ b/store/src/java-test/com/zimbra/cs/filter/DeleteHeaderTest.java @@ -1,7 +1,7 @@ /* * ***** BEGIN LICENSE BLOCK ***** * Zimbra Collaboration Suite Server - * Copyright (C) 2016 Synacor, Inc. + * Copyright (C) 2016, 2017 Synacor, Inc. * * This program is free software: you can redistribute it and/or modify it under * the terms of the GNU General Public License as published by the Free Software Foundation, @@ -431,7 +431,7 @@ null, new DeliveryContext(), @Test public void testDeleteHeaderWithNumericComparisionUsingValue() { try { - String filterScript = "require [\"editheader\"];\n" + String filterScript = "require [\"editheader\", \"relational\", \"comparator-i;ascii-numeric\"];\n" + " deleteheader :value \"lt\" :comparator \"i;ascii-numeric\" \"X-Numeric-Header\" \"3\" \r\n" + " ;\n"; Account acct1 = Provisioning.getInstance().get(Key.AccountBy.name, "test@zimbra.com"); @@ -468,7 +468,7 @@ null, new DeliveryContext(), @Test public void testDeleteHeaderWithNumericComparisionUsingCount() { try { - String filterScript = "require [\"editheader\"];\n" + String filterScript = "require [\"editheader\", \"relational\", \"comparator-i;ascii-numeric\"];\n" + " deleteheader :count \"ge\" :comparator \"i;ascii-numeric\" \"X-Numeric-Header\" \"3\" \r\n" + " ;\n"; Account acct1 = Provisioning.getInstance().get(Key.AccountBy.name, "test@zimbra.com"); @@ -845,7 +845,7 @@ null, new DeliveryContext(), @Test public void testDeleteHeaderWithValueComparisionForCasemapComparator() { try { - String filterScript = "require [\"editheader\"];\n" + String filterScript = "require [\"editheader\", \"relational\"];\n" + " deleteheader :value \"lt\" :comparator \"i;ascii-casemap\" \"X-Numeric-Header\" \"3\" \r\n" + " ;\n"; Account acct1 = Provisioning.getInstance().get(Key.AccountBy.name, "test@zimbra.com"); @@ -918,7 +918,7 @@ null, new DeliveryContext(), @Test public void testDeleteNoValuePattern() { try { - String filterScript = "require [\"editheader\"];\n" + String filterScript = "require [\"editheader\", \"relational\", \"comparator-i;ascii-numeric\"];\n" + " deleteheader :count \"gt\" :comparator \"i;ascii-numeric\" \"Subject\" \"\" \r\n" + " ;\n"; Account acct1 = Provisioning.getInstance().get(Key.AccountBy.name, "test@zimbra.com"); @@ -995,7 +995,7 @@ null, new DeliveryContext(), @Test public void testDeleteHeaderCountNegative() { try { - String filterScript = "require [\"editheader\"];\n" + String filterScript = "require [\"editheader\", \"relational\", \"comparator-i;ascii-numeric\"];\n" + "deleteheader :count \"le\" :comparator \"i;ascii-numeric\" \"X-Numeric-Header\" \"-1\";\n"; Account acct1 = Provisioning.getInstance().get(Key.AccountBy.name, "test@zimbra.com"); Mailbox mbox1 = MailboxManager.getInstance().getMailboxByAccount(acct1); @@ -1086,7 +1086,7 @@ public void testDeleteHeaderAsciiNumbericIsComparator() { + "\tby edge01e.zimbra.com (Postfix) with ESMTP id 9245B13575C;\n" + "\tFri, 24 Jun 2016 01:45:31 -0400 (EDT)\n" + "Subject: 1\n" + "to: test@zimbra.com\n"; - String filterScript = "require [\"editheader\"];\n" + String filterScript = "require [\"editheader\", \"comparator-i;ascii-numeric\"];\n" + "deleteheader :is :comparator \"i;ascii-numeric\" \"Subject\" \"1\";\n"; Account acct1 = Provisioning.getInstance().get(Key.AccountBy.name, "test@zimbra.com"); Mailbox mbox1 = MailboxManager.getInstance().getMailboxByAccount(acct1); @@ -1321,4 +1321,115 @@ mbox, new ParsedMessage("X-Mal-Encoded-Header: =?ABC?A?GyRCJFskMhsoQg==?=".getBy fail("No exception should be thrown" + e); } } + + /* + * The ascii-numeric comparator should be looked up in the list of the "require". + */ + @Test + public void testMissingComparatorNumericDeclaration() throws Exception { + // Default match type :is is used. + // No "comparator-i;ascii-numeric" capability text in the require command + String script = "require [\"editheader\"];\n" + + "deleteheader :comparator \"i;ascii-numeric\" \"X-Header\" \"example\";"; + try { + Account account = Provisioning.getInstance().get(Key.AccountBy.name, "test@zimbra.com"); + Mailbox mbox = MailboxManager.getInstance().getMailboxByAccount(account); + RuleManager.clearCachedRules(account); + account.setSieveEditHeaderEnabled(true); + account.setAdminSieveScriptBefore(script); + account.setMailSieveScript(script); + List ids = RuleManager.applyRulesToIncomingMessage(new OperationContext(mbox), + mbox, new ParsedMessage("X-Header: example".getBytes(), false), 0, + account.getName(), new DeliveryContext(), Mailbox.ID_FOLDER_INBOX, true); + Message message = mbox.getMessageById(null, ids.get(0).getId()); + Assert.assertNotNull(message.getMimeMessage().getHeader("X-Header")); + } catch (Exception e) { + fail("No exception should be thrown" + e); + } + } + + @Test + public void testBackslashAsciiCasemap4bs() throws Exception { + // Matches four backslashes + String script = "require [\"editheader\"];\n" + + "deleteheader :comparator \"i;ascii-casemap\" \"X-Header\" \"Sample\\\\\\\\\\\\\\\\Pattern\";" + + "deleteheader :comparator \"i;ascii-casemap\" \"X-HeaderA\" \"Sample\\\\\\\\Pattern\";" + + "deleteheader :comparator \"i;ascii-casemap\" \"X-HeaderB\" \"Sample\\\\Pattern\";" + + "deleteheader :comparator \"i;ascii-casemap\" \"X-HeaderC\" \"Sample\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Pattern\";"; + String pattern = "Sample\\\\\\\\Pattern"; + String msg = "X-Header: " + pattern + "\n" + + "X-HeaderA: " + pattern + "\n" + + "X-HeaderB: " + pattern + "\n" + + "X-HeaderC: " + pattern + "\n"; + boolean result = testBackslash(script, pattern, msg); + Assert.assertTrue(result); + } + + @Test + public void testBackslashAsciiCasemap5bs() throws Exception { + // Matches five backslashes + String script = "require [\"editheader\"];\n" + + "deleteheader :comparator \"i;ascii-casemap\" \"X-Header\" \"Sample\\\\\\\\\\\\\\\\\\\\Pattern\";" + + "deleteheader :comparator \"i;ascii-casemap\" \"X-HeaderA\" \"Sample\\\\\\\\\\Pattern\";" + + "deleteheader :comparator \"i;ascii-casemap\" \"X-HeaderB\" \"Sample\\\\\\Pattern\";" + + "deleteheader :comparator \"i;ascii-casemap\" \"X-HeaderC\" \"Sample\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Pattern\";"; + String pattern = "Sample\\\\\\\\\\Pattern"; + String msg = "X-Header: " + pattern + "\n" + + "X-HeaderA: " + pattern + "\n" + + "X-HeaderB: " + pattern + "\n" + + "X-HeaderC: " + pattern + "\n"; + boolean result = testBackslash(script, pattern, msg); + Assert.assertTrue(result); + } + + @Test + public void testBackslashOctet() throws Exception { + String script = "require [\"editheader\"];\n" + + "deleteheader :comparator \"i;octet\" \"X-Header\" \"Sample\\\\\\\\\\\\\\\\Pattern\";" + + "deleteheader :comparator \"i;octet\" \"X-HeaderA\" \"Sample\\\\\\\\Pattern\";" + + "deleteheader :comparator \"i;octet\" \"X-HeaderB\" \"Sample\\\\Pattern\";" + + "deleteheader :comparator \"i;octet\" \"X-HeaderC\" \"Sample\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Pattern\";"; + String pattern = "Sample\\\\\\\\Pattern"; + String msg = "X-Header: " + pattern + "\n" + + "X-HeaderA: " + pattern + "\n" + + "X-HeaderB: " + pattern + "\n" + + "X-HeaderC: " + pattern + "\n"; + boolean result = testBackslash(script, pattern, msg); + Assert.assertTrue(result); + } + + private boolean testBackslash(String script, String pattern, String msg) { + try { + Account account = Provisioning.getInstance().get(Key.AccountBy.name, "test@zimbra.com"); + Mailbox mbox = MailboxManager.getInstance().getMailboxByAccount(account); + RuleManager.clearCachedRules(account); + account.unsetAdminSieveScriptBefore(); + account.unsetMailSieveScript(); + account.unsetAdminSieveScriptAfter(); + account.setSieveEditHeaderEnabled(true); + account.setAdminSieveScriptBefore(script); + List ids = RuleManager.applyRulesToIncomingMessage(new OperationContext(mbox), + mbox, new ParsedMessage(msg.getBytes(), false), 0, + account.getName(), new DeliveryContext(), Mailbox.ID_FOLDER_INBOX, true); + Message message = mbox.getMessageById(null, ids.get(0).getId()); + String[] headers = message.getMimeMessage().getHeader("X-Header"); + Assert.assertNull(headers); + headers = message.getMimeMessage().getHeader("X-HeaderA"); + Assert.assertNotNull(headers); + Assert.assertNotSame(0, headers.length); + Assert.assertEquals(pattern, headers[0]); + headers = message.getMimeMessage().getHeader("X-HeaderB"); + Assert.assertNotNull(headers); + Assert.assertNotSame(0, headers.length); + Assert.assertEquals(pattern, headers[0]); + headers = message.getMimeMessage().getHeader("X-HeaderC"); + Assert.assertNotNull(headers); + Assert.assertNotSame(0, headers.length); + Assert.assertEquals(pattern, headers[0]); + return true; + } catch (Exception e) { + fail("No exception should be thrown" + e); + return false; + } + } } \ No newline at end of file diff --git a/store/src/java-test/com/zimbra/cs/filter/EnvelopeTest.java b/store/src/java-test/com/zimbra/cs/filter/EnvelopeTest.java index 549c64cb90e..14f4b42a419 100644 --- a/store/src/java-test/com/zimbra/cs/filter/EnvelopeTest.java +++ b/store/src/java-test/com/zimbra/cs/filter/EnvelopeTest.java @@ -1,7 +1,7 @@ /* * ***** BEGIN LICENSE BLOCK ***** * Zimbra Collaboration Suite Server - * Copyright (C) 2016 Synacor, Inc. + * Copyright (C) 2016, 2017 Synacor, Inc. * * This program is free software: you can redistribute it and/or modify it under * the terms of the GNU General Public License as published by the Free Software Foundation, @@ -611,7 +611,7 @@ public void testOutgoingFilter() { @Test public void testCompareEmptyStringWithAsciiNumeric() { - String filterScript = "require \"envelope\";\n" + String filterScript = "require [\"envelope\", \"comparator-i;ascii-numeric\"];\n" + "if envelope :comparator \"i;ascii-numeric\" :all :is \"from\" \"\" {\n" + " tag \"testCompareEmptyStringWithAsciiNumeric envelope\";" + "}" @@ -652,7 +652,7 @@ public void testCompareEmptyStringWithAsciiNumeric() { @Test public void testTo_Alias() { - String filterScript = "require [\"variables\", \"envelope\"];\n" + String filterScript = "require [\"variables\", \"envelope\", \"relational\", \"comparator-i;ascii-numeric\"];\n" + "set \"rcptto\" \"unknown\";\n" + "if envelope :all :matches \"to\" \"*\" {\n" + " set \"rcptto\" \"${1}\";\n" @@ -707,7 +707,7 @@ public void testTo_Alias() { @Test public void testCountForEmptyFromHeader() { - String filterScript = "require \"envelope\";\n" + String filterScript = "require [\"envelope\", \"relational\", \"comparator-i;ascii-numeric\"];\n" + "if envelope :count \"eq\" :comparator \"i;ascii-numeric\" :all \"FROM\" \"0\" {\n" + "tag \"0\";\n" + "}\n" @@ -750,7 +750,7 @@ public void testCountForEmptyFromHeader() { @Test public void testNumericNegativeValueCount() { - String filterScript = "require [\"envelope\", \"tag\", \"relational\"];\n" + String filterScript = "require [\"envelope\", \"tag\", \"relational\", \"comparator-i;ascii-numeric\"];\n" + "if envelope :all :count \"lt\" :comparator \"i;ascii-numeric\" \"to\" \"-1\" {\n" + " tag \"To\";\n" + "}"; @@ -1007,4 +1007,47 @@ public void testAllDomainLocalIs() { fail("No exception should be thrown: " + e); } } + + /* + * The ascii-numeric comparator should be looked up in the list of the "require". + */ + @Test + public void testMissingComparatorNumericDeclaration() throws Exception { + // Default match type :is is used. + // No "comparator-i;ascii-numeric" capability text in the require command + String filterScript = "require [\"envelope\"];" + + "if envelope :comparator \"i;ascii-numeric\" \"To\" \"xyz@zimbra.com\" {\n" + + " tag \"is\";\n" + + "} else {\n" + + " tag \"not is\";\n" + + "}"; + LmtpEnvelope env = new LmtpEnvelope(); + LmtpAddress sender = new LmtpAddress("", new String[] { "BODY", "SIZE" }, null); + LmtpAddress recipient = new LmtpAddress("", null, null); + env.setSender(sender); + env.addLocalRecipient(recipient); + + try { + Account account = Provisioning.getInstance().getAccount( + MockProvisioning.DEFAULT_ACCOUNT_ID); + RuleManager.clearCachedRules(account); + Mailbox mbox = MailboxManager.getInstance().getMailboxByAccount(account); + + account.unsetAdminSieveScriptBefore(); + account.unsetMailSieveScript(); + account.unsetAdminSieveScriptAfter(); + account.setMailSieveScript(filterScript); + List ids = RuleManager.applyRulesToIncomingMessage( + new OperationContext(mbox), mbox, + new ParsedMessage(sampleMsg.getBytes(), false), 0, + account.getName(), env, + new DeliveryContext(), + Mailbox.ID_FOLDER_INBOX, true); + Assert.assertEquals(1, ids.size()); + Message msg = mbox.getMessageById(null, ids.get(0).getId()); + Assert.assertEquals(null, ArrayUtil.getFirstElement(msg.getTags())); + } catch (Exception e) { + fail("No exception should be thrown" + e); + } + } } diff --git a/store/src/java-test/com/zimbra/cs/filter/ErejectTest.java b/store/src/java-test/com/zimbra/cs/filter/ErejectTest.java index 91af3dbb02c..2ad000e5251 100644 --- a/store/src/java-test/com/zimbra/cs/filter/ErejectTest.java +++ b/store/src/java-test/com/zimbra/cs/filter/ErejectTest.java @@ -106,7 +106,7 @@ public void setUp() throws Exception { public void test() { Account acct1 = null; Mailbox mbox1 = null; - + boolean isPassed = false; try { acct1 = Provisioning.getInstance().get(Key.AccountBy.name, "test@zimbra.com"); mbox1 = MailboxManager.getInstance().getMailboxByAccount(acct1); @@ -130,6 +130,7 @@ env, new DeliveryContext(), List items = mbox1.getItemIds(null, Mailbox.ID_FOLDER_INBOX) .getIds(MailItem.Type.MESSAGE); Assert.assertEquals(null, items); + isPassed = true; } catch (Exception ex) { fail("No exception should be thrown: " + ex.getMessage()); } @@ -139,6 +140,9 @@ env, new DeliveryContext(), } catch (Exception e) { fail("No exception should be thrown: " + e.getMessage()); } + if (!isPassed) { + fail("DeliveryServiceException/ErejectException should have been thrown, but no exception is thrown"); + } } /* diff --git a/store/src/java-test/com/zimbra/cs/filter/HeaderTest.java b/store/src/java-test/com/zimbra/cs/filter/HeaderTest.java index 67068414f20..8eac937c06a 100644 --- a/store/src/java-test/com/zimbra/cs/filter/HeaderTest.java +++ b/store/src/java-test/com/zimbra/cs/filter/HeaderTest.java @@ -116,7 +116,7 @@ public void testEmptyHeaderEmptyKey() { // and none of tag commands should be executed. @Test public void testNumericNegativeValueValue() { - String filterScript = "require [\"fileinto\", \"tag\", \"flag\", \"log\", \"relational\"];\n" + String filterScript = "require [\"fileinto\", \"tag\", \"flag\", \"log\", \"relational\", \"comparator-i;ascii-numeric\"];\n" + "if header :value \"ge\" :comparator \"i;ascii-numeric\" " + "[\"X-Spam-score\"] [\"500\"] { tag \"XSpamScore\";}" + "tag \"Negative\";"; @@ -127,8 +127,8 @@ public void testNumericNegativeValueValue() { // and none of tag commands should be executed. @Test public void testNumericNegativeValueCounts() { - String filterScript = "require [\"fileinto\", \"tag\", \"flag\", \"log\", \"relational\"];\n" - + "if header :counts \"ge\" :comparator \"i;ascii-numeric\" " + String filterScript = "require [\"fileinto\", \"tag\", \"flag\", \"log\", \"relational\", \"comparator-i;ascii-numeric\"];\n" + + "if header :count \"ge\" :comparator \"i;ascii-numeric\" " + "[\"Received\"] [\"-1\"] { tag \"Received\";}" + "tag \"Negative\";"; doTest(filterScript, null); @@ -138,7 +138,7 @@ public void testNumericNegativeValueCounts() { // and none of tag commands should be executed. @Test public void testNumericNegativeValueIs() { - String filterScript = "require [\"fileinto\", \"tag\", \"flag\", \"log\", \"relational\"];\n" + String filterScript = "require [\"fileinto\", \"tag\", \"flag\", \"log\", \"relational\", \"comparator-i;ascii-numeric\"];\n" + "if header :is :comparator \"i;ascii-numeric\" " + "[\"X-Spam-score\"] [\"-5\"] { tag \"XSpamScore\";}" + "tag \"Negative\";"; @@ -148,7 +148,7 @@ public void testNumericNegativeValueIs() { // The "X-Minus: -abc" is not a negative value, but positive infinity as it is just a string. @Test public void testNumericMinusCharacterValueIs() { - String filterScript = "require [\"fileinto\", \"tag\", \"flag\", \"log\", \"relational\"];\n" + String filterScript = "require [\"fileinto\", \"tag\", \"flag\", \"log\", \"relational\", \"comparator-i;ascii-numeric\"];\n" + "if header :is :comparator \"i;ascii-numeric\" " + "[\"X-Minus\"] [\"\"] { tag \"Xminus\";}"; doTest(filterScript, "Xminus"); @@ -159,7 +159,7 @@ public void testNumericMinusCharacterValueIs() { // Hence the Subject text is treated as positive infinity, and so is an empty string @Test public void testNumericEmptyIs() { - String filterScript = "require [\"fileinto\", \"tag\", \"flag\", \"log\", \"relational\"];\n" + String filterScript = "require [\"fileinto\", \"tag\", \"flag\", \"log\", \"relational\", \"comparator-i;ascii-numeric\"];\n" + "if header :is :comparator \"i;ascii-numeric\" " + "[\"Subject\"] [\"\"] { tag \"subject\";}"; doTest(filterScript, "subject"); @@ -443,4 +443,42 @@ public void testMalencodedHeader() throws Exception { fail("No exception should be thrown" + e); } } + + /* + * The ascii-numeric comparator should be looked up in the list of the "require". + */ + @Test + public void testMissingComparatorNumericDeclaration() throws Exception { + // Default match type :is is used. + // No "comparator-i;ascii-numeric" capability text in the require command + String filterScript = "require [\"tag\"];" + + "if header :comparator \"i;ascii-numeric\" \"Subject\" \"こんにちは\" {\n" + + " tag \"is\";\n" + + "} else {\n" + + " tag \"not is\";\n" + + "}"; + try { + LmtpEnvelope env = setEnvelopeInfo(); + Account account = Provisioning.getInstance().getAccount( + MockProvisioning.DEFAULT_ACCOUNT_ID); + RuleManager.clearCachedRules(account); + Mailbox mbox = MailboxManager.getInstance().getMailboxByAccount(account); + + account.unsetAdminSieveScriptBefore(); + account.unsetMailSieveScript(); + account.unsetAdminSieveScriptAfter(); + account.setMailSieveScript(filterScript); + List ids = RuleManager.applyRulesToIncomingMessage( + new OperationContext(mbox), mbox, + new ParsedMessage(sampleMsg.getBytes(), false), 0, + account.getName(), env, + new DeliveryContext(), + Mailbox.ID_FOLDER_INBOX, true); + Assert.assertEquals(1, ids.size()); + Message msg = mbox.getMessageById(null, ids.get(0).getId()); + Assert.assertEquals(null, ArrayUtil.getFirstElement(msg.getTags())); + } catch (Exception e) { + fail("No exception should be thrown" + e); + } + } } diff --git a/store/src/java-test/com/zimbra/cs/filter/MimeHeaderTest.java b/store/src/java-test/com/zimbra/cs/filter/MimeHeaderTest.java new file mode 100644 index 00000000000..4449264198e --- /dev/null +++ b/store/src/java-test/com/zimbra/cs/filter/MimeHeaderTest.java @@ -0,0 +1,147 @@ +/* + * ***** BEGIN LICENSE BLOCK ***** + * Zimbra Collaboration Suite Server + * Copyright (C) 2017 Synacor, Inc. + * + * This program is free software: you can redistribute it and/or modify it under + * the terms of the GNU General Public License as published by the Free Software Foundation, + * version 2 of the License. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * You should have received a copy of the GNU General Public License along with this program. + * If not, see . + * ***** END LICENSE BLOCK ***** + */ +package com.zimbra.cs.filter; + +import static org.junit.Assert.fail; + +import java.util.HashMap; +import java.util.List; + +import org.junit.Assert; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; + +import com.zimbra.common.util.ArrayUtil; +import com.zimbra.cs.account.Account; +import com.zimbra.cs.account.MockProvisioning; +import com.zimbra.cs.account.Provisioning; +import com.zimbra.cs.mailbox.DeliveryContext; +import com.zimbra.cs.mailbox.Mailbox; +import com.zimbra.cs.mailbox.MailboxManager; +import com.zimbra.cs.mailbox.MailboxTestUtil; +import com.zimbra.cs.mailbox.Message; +import com.zimbra.cs.mailbox.OperationContext; +import com.zimbra.cs.mime.ParsedMessage; +import com.zimbra.cs.service.util.ItemId; + +public class MimeHeaderTest { + private static String sampleMsg = "from: xyz@example.com\n" + + "Subject: test message\n" + + "to: foo@example.com, baz@example.com\n" + + "cc: qux@example.com\n" + + "Subject: Bonjour\n" + + "MIME-Version: 1.0\n" + + "Content-Type: multipart/mixed; boundary=\"----=_Part_64_1822363563.1505482033554\"\n" + + "\n" + + "------=_Part_64_1822363563.1505482033554\n" + + "Content-Type: text/plain; charset=utf-8\n" + + "Content-Transfer-Encoding: 7bit\n" + + "\n" + + "Test message 2\n" + + "------=_Part_64_1822363563.1505482033554\n" + + "Content-Type: message/rfc822\n" + + "Content-Disposition: attachment\n" + + "\n" + + "Date: Fri, 15 Sep 2017 22:26:43 +0900 (JST)\n" + + "From: admin@synacorjapan.com\n" + + "To: user1 \n" + + "Message-ID: <523389747.44.1505482003470.JavaMail.zimbra@synacorjapan.com>\n" + + "Subject: Hello\n" + + "MIME-Version: 1.0\n" + + "Content-Type: multipart/alternative; boundary=\"=_37c6ca38-873e-4a06-ad29-25a254075e83\"\n" + + "\n" + + "--=_37c6ca38-873e-4a06-ad29-25a254075e83\n" + + "Content-Type: text/plain; charset=utf-8\n" + + "Content-Transfer-Encoding: 7bit\n" + + "\n" + + "This is a sample email\n" + + "\n" + + "--=_37c6ca38-873e-4a06-ad29-25a254075e83\n" + + "Content-Type: text/html; charset=utf-8\n" + + "Content-Transfer-Encoding: 7bit\n" + + "\n" + + "
Test message
\n" + + "--=_37c6ca38-873e-4a06-ad29-25a254075e83--\n" + + "\n" + + "------=_Part_64_1822363563.1505482033554--\n"; + + @BeforeClass + public static void init() throws Exception { + MailboxTestUtil.initServer(); + Provisioning prov = Provisioning.getInstance(); + prov.createAccount("test@zimbra.com", "secret", new HashMap()); + } + + @Before + public void setUp() throws Exception { + MailboxTestUtil.clearData(); + } + + @Test + public void test() throws Exception { + // Default match type :is is used. + String filterScript = "require [\"tag\", \"comparator-i;ascii-numeric\"];" + + "if mime_header :comparator \"i;ascii-numeric\" \"Subject\" \"Hello\" {\n" + + " tag \"is\";\n" + + "} else {\n" + + " tag \"not is\";\n" + + "}"; + doTest(filterScript, "is"); + } + + /* + * The ascii-numeric comparator should be looked up in the list of the "require". + */ + @Test + public void testMissingComparatorNumericDeclaration() throws Exception { + // Default match type :is is used. + // No "comparator-i;ascii-numeric" capability text in the require command + String filterScript = "require [\"tag\"];" + + "if mime_header :comparator \"i;ascii-numeric\" \"Subject\" \"Hello\" {\n" + + " tag \"is\";\n" + + "} else {\n" + + " tag \"not is\";\n" + + "}"; + doTest(filterScript, null); + } + + private void doTest(String filterScript, String expected) throws Exception { + try { + Account account = Provisioning.getInstance().getAccount( + MockProvisioning.DEFAULT_ACCOUNT_ID); + RuleManager.clearCachedRules(account); + Mailbox mbox = MailboxManager.getInstance().getMailboxByAccount(account); + + account.unsetAdminSieveScriptBefore(); + account.unsetMailSieveScript(); + account.unsetAdminSieveScriptAfter(); + account.setMailSieveScript(filterScript); + List ids = RuleManager.applyRulesToIncomingMessage( + new OperationContext(mbox), mbox, + new ParsedMessage(sampleMsg.getBytes(), false), 0, + account.getName(), + new DeliveryContext(), + Mailbox.ID_FOLDER_INBOX, true); + Assert.assertEquals(1, ids.size()); + Message msg = mbox.getMessageById(null, ids.get(0).getId()); + Assert.assertEquals(expected, ArrayUtil.getFirstElement(msg.getTags())); + } catch (Exception e) { + fail("No exception should be thrown" + e); + } + } +} diff --git a/store/src/java-test/com/zimbra/cs/filter/NotifyMailtoTest.java b/store/src/java-test/com/zimbra/cs/filter/NotifyMailtoTest.java index 1e6e85d681d..cb442f7b19f 100644 --- a/store/src/java-test/com/zimbra/cs/filter/NotifyMailtoTest.java +++ b/store/src/java-test/com/zimbra/cs/filter/NotifyMailtoTest.java @@ -18,11 +18,13 @@ import static org.junit.Assert.fail; +import java.util.Enumeration; import java.util.List; import java.util.Map; import java.util.Set; import java.util.UUID; +import javax.mail.Header; import javax.mail.internet.MimeMessage; import org.junit.Assert; @@ -107,7 +109,7 @@ public void setUp() throws Exception { + " notify :message \"${subject}\"\n" + " :from \"test1@zimbra.com\"\n" + " :importance \"3\"\n" - + " \"mailto:test2@zimbra.com?to=test3@zimbra.com&Importance=High&X-Priority=1&From=notifyfrom@example.com&body=${contents}\";" + + " \"mailto:test2@zimbra.com?to=test3@zimbra.com&Importance=High&X-Priority=1&X-HEADER1=value1&x-header2=value2&x-HeAdEr3=value3&x-hEader4=value4A&x-heAder4=value4B&From=notifyfrom@example.com&body=${contents}\";" + " keep;\n" + "}\n"; @@ -287,6 +289,30 @@ public void test() { Assert.assertFalse(notifyMsg.getSender() == null); Assert.assertEquals("test1@zimbra.com", notifyMsg.getSender()); + boolean header1 = false; + boolean header2 = false; + boolean header3 = false; + boolean header4 = false; + for (Enumeration
e = notifyMsg.getMimeMessage().getAllHeaders(); e.hasMoreElements();) { + Header temp = e.nextElement(); + if ("X-HEADER1".equals(temp.getName())) { + header1 = true; + } + if ("X-header2".equals(temp.getName())) { + header2 = true; + } + if ("X-HeAdEr3".equals(temp.getName())) { + header3 = true; + } + if ("X-hEader4".equals(temp.getName())) { + header4 = true; + } + } + Assert.assertTrue(header1); + Assert.assertTrue(header2); + Assert.assertTrue(header3); + Assert.assertTrue(header4); + notifyMsg = mbox3.getMessageById(null, item); Assert.assertEquals("おしらせ", notifyMsg.getSubject()); } catch (Exception e) { @@ -942,10 +968,10 @@ public void testNotifyMethodCapability_OnlineMaybe() { public void testNotifyMethodCapability_OnlineYes() { String filterScript = "require [\"enotify\", \"tag\"];\n" - + "if not notify_method_capability\n" + + "if notify_method_capability\n" + " \"mailto:test2@zimbra.com\"\n" + " \"Online\"\n" - + " \"YES\"] { \n" + + " [\"YES\"] { \n" + " tag \"notify_method_capability\";\n" + "}"; @@ -962,7 +988,7 @@ public void testNotifyMethodCapability_OnlineYes() { Mailbox.ID_FOLDER_INBOX, true); // ZCS implements the RFC 5436 so that it returns true when 'notify_method_capability' - // checkes whether the "Online" status is "maybe". Otherwise it returns false. + // checks whether the "Online" status is "maybe". Otherwise it returns false. Assert.assertEquals(1, ids.size()); Message msg = mbox1.getMessageById(null, ids.get(0).getId()); Assert.assertEquals(null, ArrayUtil.getFirstElement(msg.getTags())); @@ -977,7 +1003,7 @@ public void testNotifyMethodCapability_OnlineYes() { @Test public void testNotifyMethodCapability_Relational() { String filterScript = - "require [\"enotify\", \"tag\", \"relational\"];\n" + "require [\"enotify\", \"tag\", \"relational\", \"comparator-i;ascii-numeric\"];\n" + "if notify_method_capability :count \"eq\"\n" + " \"mailto:test2@zimbra.com\"\n" + " \"Online\"\n" @@ -1013,7 +1039,7 @@ public void testNotifyMethodCapability_Relational() { @Test public void testNotify_variable() { String filterScript = - "require [\"enotify\", \"tag\", \"variables\"];\n" + "require [\"enotify\", \"tag\", \"variables\", \"envelope\"];\n" + "if envelope :matches [\"To\"] \"*\" {set \"rcptto\" \"${1}\";}\n" + "if envelope :matches [\"From\"] \"*\" {set \"mailfrom\" \"${1}\";}\n" + "if header :matches \"Date\" \"*\" {set \"dateheader\" \"${1}\";}\n" @@ -1241,7 +1267,7 @@ public void testNotifyMailtoWithSpaceInHeaderName() { @Test public void testNotify_mimeVariables() { String filterScript = - "require [\"enotify\", \"tag\", \"variables\"];\n" + "require [\"enotify\", \"tag\", \"variables\", \"envelope\"];\n" + "if envelope :matches [\"To\"] \"*\" {set \"rcptto\" \"${1}\";}\n" + "if envelope :matches [\"From\"] \"*\" {set \"mailfrom\" \"${1}\";}\n" + "if anyof(not envelope :is [\"From\"] \"\") {\n" diff --git a/store/src/java-test/com/zimbra/cs/filter/RedirectCopyTest.java b/store/src/java-test/com/zimbra/cs/filter/RedirectCopyTest.java index d19ae5737e9..9b13222ce30 100644 --- a/store/src/java-test/com/zimbra/cs/filter/RedirectCopyTest.java +++ b/store/src/java-test/com/zimbra/cs/filter/RedirectCopyTest.java @@ -1,7 +1,7 @@ /* * ***** BEGIN LICENSE BLOCK ***** * Zimbra Collaboration Suite Server - * Copyright (C) 2016 Synacor, Inc. + * Copyright (C) 2016, 2017 Synacor, Inc. * * This program is free software: you can redistribute it and/or modify it under * the terms of the GNU General Public License as published by the Free Software Foundation, @@ -16,10 +16,11 @@ */ package com.zimbra.cs.filter; -import static org.junit.Assert.*; import java.util.List; import java.util.Map; import java.util.UUID; + +import org.apache.commons.lang.StringUtils; import org.junit.Assert; import org.junit.Before; import org.junit.BeforeClass; @@ -93,7 +94,7 @@ mbox, new ParsedMessage(raw.getBytes(), false), 0, account.getName(), Assert.assertEquals(2, notifyMsg.getFolderId()); } catch (Exception e) { e.printStackTrace(); - fail("No exception should be thrown"); + Assert.fail("No exception should be thrown"); } } @@ -123,7 +124,7 @@ mbox, new ParsedMessage(raw.getBytes(), false), 0, account.getName(), Assert.assertEquals(2, msg.getFolderId()); } catch (Exception e) { e.printStackTrace(); - fail("No exception should be thrown"); + Assert.fail("No exception should be thrown"); } } @@ -147,7 +148,46 @@ mbox, new ParsedMessage(rawReal.getBytes(), false), 0, account.getName(), mbox.getItemIds(null, Mailbox.ID_FOLDER_INBOX).getIds(MailItem.Type.MESSAGE)); } catch (Exception e) { e.printStackTrace(); - fail("No exception should be thrown"); + Assert.fail("No exception should be thrown"); + } + } + + /* + * Redirect a message whose body text consists of some non-ascii characters, + * but it does not have a proper Content-Transfer-Encoding header. + */ + @Test + public void testPlainRedirectMimeMsg1() { + String filterScript = "require [\"copy\", \"fileinto\"];\n" + + "redirect \"test3@zimbra.com\";\n"; + try { + Account account2 = Provisioning.getInstance().get(Key.AccountBy.name, + "test2@zimbra.com"); + Account account3 = Provisioning.getInstance().get(Key.AccountBy.name, + "test3@zimbra.com"); + RuleManager.clearCachedRules(account2); + Mailbox mbox2 = MailboxManager.getInstance().getMailboxByAccount(account2); + Mailbox mbox3 = MailboxManager.getInstance().getMailboxByAccount(account3); + account2.setMailSieveScript(filterScript); + String body = StringUtils.leftPad("", 999, "あ"); + + String rawReal = "From: test1@zimbra.com\n" + "To: test2@zimbra.com\n" + + "Subject: Test\n" + "\n" + body; + RuleManager.applyRulesToIncomingMessage(new OperationContext(mbox2), + mbox2, new ParsedMessage(rawReal.getBytes("Shift_JIS"), false), 0, account2.getName(), + new DeliveryContext(), Mailbox.ID_FOLDER_INBOX, true); + + // verify the redirected message + Integer item = mbox3.getItemIds(null, Mailbox.ID_FOLDER_INBOX).getIds(MailItem.Type.MESSAGE).get(0); + Message redirectMsg = mbox3.getMessageById(null, item); + Assert.assertEquals(body.substring(0, 150), redirectMsg.getFragment().substring(0, 150)); + String[] headers = redirectMsg.getMimeMessage().getHeader("Content-Transfer-Encoding"); + Assert.assertNotNull(headers); + Assert.assertNotSame(0, headers.length); + Assert.assertEquals("8bit", headers[0]); + } catch (Exception e) { + e.printStackTrace(); + Assert.fail("No exception should be thrown"); } } } \ No newline at end of file diff --git a/store/src/java-test/com/zimbra/cs/filter/RelationalExtensionTest.java b/store/src/java-test/com/zimbra/cs/filter/RelationalExtensionTest.java index 901c4e45f8d..7a5e12d0665 100644 --- a/store/src/java-test/com/zimbra/cs/filter/RelationalExtensionTest.java +++ b/store/src/java-test/com/zimbra/cs/filter/RelationalExtensionTest.java @@ -72,47 +72,49 @@ public void setUp() throws Exception { @Test public void testCountAddressNumericGE1() { // RFC 5231 6. Example first example would evaluate to true - String filterScript = "if address :count \"ge\" :comparator \"i;ascii-numeric\" " - + "[\"to\", \"cc\"] [\"3\"] {" + "tag \"Priority\";}"; + String filterScript = "require [\"relational\", \"comparator-i;ascii-numeric\"];" + + "if address :count \"ge\" :comparator \"i;ascii-numeric\" " + + "[\"to\", \"cc\"] [\"3\"] { tag \"Priority\";}"; doTest(filterScript, "Priority"); } @Test public void testCountAddressNumericGE2() { // RFC 5231 6. Example second example would evaluate to false - String filterScript = "if anyof (address :count \"ge\" :comparator \"i;ascii-numeric\"\n" + String filterScript = "require [\"relational\", \"tag\", \"comparator-i;ascii-numeric\"];\n" + + "if anyof (address :count \"ge\" :comparator \"i;ascii-numeric\"\n" + "[\"to\"] [\"3\"],\n" + "address :count \"ge\" :comparator \"i;ascii-numeric\"\n" + "[\"cc\"] [\"3\"] )\n" - + "{" + "tag \"Priority\";}"; - doTest(filterScript, null); + + "{ tag \"Priority\";} else { tag \"No Priority\";}"; + doTest(filterScript, "No Priority"); } @Test public void testCountHeaderNumericGE1() { // RFC 5231 6. Example third example would evaluate to false - String filterScript = "require [\"fileinto\", \"tag\", \"flag\", \"log\", \"relational\"];\n" + String filterScript = "require [\"fileinto\", \"tag\", \"flag\", \"log\", \"relational\", \"comparator-i;ascii-numeric\"];\n" + "if header :count \"ge\" :comparator \"i;ascii-numeric\" " - + "[\"received\"] [\"3\"] {" + "tag \"Priority\";}"; - doTest(filterScript, null); + + "[\"received\"] [\"3\"] { tag \"Priority\";} else { tag \"No Priority\";}"; + doTest(filterScript, "No Priority"); } @Test public void testCountHeaderNumericGE2() { // RFC 5231 6. Example fourth example would evaluate to true - String filterScript = "require [\"fileinto\", \"tag\", \"flag\", \"log\", \"relational\"];\n" + String filterScript = "require [\"fileinto\", \"tag\", \"flag\", \"log\", \"relational\", \"comparator-i;ascii-numeric\"];\n" + "if header :count \"ge\" :comparator \"i;ascii-numeric\" " - + "[\"received\", \"subject\"] [\"3\"] {" + "tag \"Priority\";}"; + + "[\"received\", \"subject\"] [\"3\"] { tag \"Priority\";}"; doTest(filterScript, "Priority"); } @Test public void testCountHeaderNumericGE3() { // RFC 5231 6. Example fifth example would evaluate to false - String filterScript = "require [\"fileinto\", \"tag\", \"flag\", \"log\", \"relational\"];\n" + String filterScript = "require [\"fileinto\", \"tag\", \"flag\", \"log\", \"relational\", \"comparator-i;ascii-numeric\"];\n" + "if header :count \"ge\" :comparator \"i;ascii-numeric\" " - + "[\"to\", \"cc\"] [\"3\"] {" + "tag \"Priority\";}"; - doTest(filterScript, null); + + "[\"to\", \"cc\"] [\"3\"] { tag \"Priority\";} else { tag \"No Priority\";}"; + doTest(filterScript, "No Priority"); } @Test @@ -126,7 +128,7 @@ public void testValueAddressNumericGT() { // So the email address string of the To address (foo@example.com, baz@example.com) // will be treated as an empty string "", and it represents positive infinity. // Positive infinity is definitely grater than 1, so this test should return TRUE. - String filterScript = "require [\"fileinto\", \"tag\", \"flag\", \"log\", \"relational\"];\n" + String filterScript = "require [\"fileinto\", \"tag\", \"flag\", \"log\", \"relational\", \"comparator-i;ascii-numeric\"];\n" + "if address :value \"gt\" :comparator \"i;ascii-numeric\" " + "[\"to\"] [\"0\"] {" + "tag \"Priority\";}"; doTest(filterScript, "Priority"); @@ -135,7 +137,8 @@ public void testValueAddressNumericGT() { @Test public void testValueAddressCasemapGT() { // "from" address starts with 'N'-'Z' - String filterScript = "if address :value \"gt\" :comparator \"i;ascii-casemap\" " + String filterScript = "require [\"relational\", \"tag\", \"comparator-i;ascii-numeric\"];\n" + + "if address :value \"gt\" :comparator \"i;ascii-casemap\" " + "[\"from\"] [\"M\"] {" + "tag \"Priority\";}"; doTest(filterScript, "Priority"); } @@ -143,16 +146,16 @@ public void testValueAddressCasemapGT() { @Test public void testValueHeaderNumericGT() { // RFC 5231 7. Extended Example (modified) - String filterScript = "require [\"fileinto\", \"tag\", \"flag\", \"log\", \"relational\"];\n" + String filterScript = "require [\"fileinto\", \"tag\", \"flag\", \"log\", \"relational\", \"comparator-i;ascii-numeric\"];\n" + "if header :value \"gt\" :comparator \"i;ascii-numeric\" " - + "[\"x-priority\"] [\"3\"] {" + "tag \"Priority\";}"; - doTest(filterScript, null); + + "[\"x-priority\"] [\"3\"] { tag \"Priority\";} else { tag \"No Priority\";}"; + doTest(filterScript, "No Priority"); } @Test public void testValueHeadeNumericrLT() { // RFC 5231 7. Extended Example - String filterScript = "require [\"fileinto\", \"tag\", \"flag\", \"log\", \"relational\"];\n" + String filterScript = "require [\"fileinto\", \"tag\", \"flag\", \"log\", \"relational\", \"comparator-i;ascii-numeric\"];\n" + "if header :value \"lt\" :comparator \"i;ascii-numeric\" " + "[\"x-priority\"] [\"3\"] {" + "tag \"Priority\";}"; doTest(filterScript, "Priority"); @@ -161,7 +164,7 @@ public void testValueHeadeNumericrLT() { @Test public void testValueHeaderNumericLE() { // RFC 5231 7. Extended Example (modified) - String filterScript = "require [\"fileinto\", \"tag\", \"flag\", \"log\", \"relational\"];\n" + String filterScript = "require [\"fileinto\", \"tag\", \"flag\", \"log\", \"relational\", \"comparator-i;ascii-numeric\"];\n" + "if header :value \"le\" :comparator \"i;ascii-numeric\" " + "[\"x-priority\"] [\"3\"] {" + "tag \"Priority\";}"; doTest(filterScript, "Priority"); @@ -170,7 +173,7 @@ public void testValueHeaderNumericLE() { @Test public void testValueHeaderNumericEQ() { // RFC 5231 7. Extended Example (modified) - String filterScript = "require [\"fileinto\", \"tag\", \"flag\", \"log\", \"relational\"];\n" + String filterScript = "require [\"fileinto\", \"tag\", \"flag\", \"log\", \"relational\", \"comparator-i;ascii-numeric\"];\n" + "if header :value \"eq\" :comparator \"i;ascii-numeric\" " + "[\"x-priority\"] [\"1\"] {" + "tag \"Priority\";}"; doTest(filterScript, "Priority"); @@ -179,10 +182,10 @@ public void testValueHeaderNumericEQ() { @Test public void testValueHeaderNumericNE() { // RFC 5231 7. Extended Example (modified) - String filterScript = "require [\"fileinto\", \"tag\", \"flag\", \"log\", \"relational\"];\n" + String filterScript = "require [\"fileinto\", \"tag\", \"flag\", \"log\", \"relational\", \"comparator-i;ascii-numeric\"];\n" + "if header :value \"ne\" :comparator \"i;ascii-numeric\" " - + "[\"x-priority\"] [\"1\"] {" + "tag \"Priority\";}"; - doTest(filterScript, null); + + "[\"x-priority\"] [\"1\"] { tag \"Priority\";} else { tag \"No Priority\";}"; + doTest(filterScript, "No Priority"); } @Test @@ -207,7 +210,7 @@ public void testValueHeaderCasemapI18NGT() { public void testBadFormat_nokeys() { String filterScript = "require [\"fileinto\", \"tag\", \"flag\", \"log\", \"relational\"];\n" + "if header :value \"gt\" :comparator \"i;ascii-casemap\" " - + "[\"subject\"] :foo {" + "tag \"Priority\";}"; + + "[\"subject\"] :foo { tag \"Priority\";} else { tag \"No Priority\";}"; /* * The following error will occur: * org.apache.jsieve.exception.SyntaxException: Expecting a StringList of keys Line 2 column 1. @@ -219,7 +222,7 @@ public void testBadFormat_nokeys() { public void testBadFormat_noTestName() { String filterScript = "require [\"fileinto\", \"tag\", \"flag\", \"log\", \"relational\"];\n" + "if relational :value \"gt\" :comparator \"i;ascii-casemap\" " - + "[\"subject\"] [\"test\"] {" + "tag \"Priority\";}"; + + "[\"subject\"] [\"test\"] { tag \"Priority\";} else { tag \"No Priority\";}"; /* * The following error will occur: * org.apache.jsieve.exception.SyntaxException: Found unexpected arguments. Line 2 column 1. @@ -232,7 +235,8 @@ public void testCountEnvelopeToNumericEQ() { // RFC 5231 10. Security Considerations // An implementation MUST ensure that the test for envelope "to" only // reflects the delivery to the current user. - String filterScript = "if envelope :count \"eq\" :comparator \"i;ascii-numeric\" " + String filterScript = "require [\"envelope\", \"relational\", \"comparator-i;ascii-numeric\"];" + + "if envelope :count \"eq\" :comparator \"i;ascii-numeric\" " + "[\"TO\"] [\"1\"] {" + "tag \"Priority\";}"; doTest(filterScript, "Priority"); } @@ -246,16 +250,18 @@ public void testCountEnvelopeToNumericGT() { // to someone else. // The number of sample LMTP RCPT TO addresses is 2, but the number of // address to be evaluated for the "envelope" test should be 1. - String filterScript = "if envelope :count \"gt\" :comparator \"i;ascii-numeric\" " - + "[\"to\"] [\"1\"] {" + "tag \"Priority\";}"; - doTest(filterScript, null); + String filterScript = "require [\"envelope\", \"relational\", \"comparator-i;ascii-numeric\"];" + + "if envelope :count \"gt\" :comparator \"i;ascii-numeric\" " + + "[\"to\"] [\"1\"] { tag \"Priority\";} else { tag \"No Priority\";}"; + doTest(filterScript, "No Priority"); } @Test public void testValueEnvelopeFromNumericGT() { // invalid comparison // See the comment on testValueAddressNumericGT. - String filterScript = "if envelope :value \"gt\" :comparator \"i;ascii-numeric\" " + String filterScript = "require [\"envelope\", \"relational\", \"comparator-i;ascii-numeric\"];" + + "if envelope :value \"gt\" :comparator \"i;ascii-numeric\" " + "[\"from\"] [\"1\"] {" + "tag \"Priority\";}"; doTest(filterScript, "Priority"); } @@ -263,9 +269,10 @@ public void testValueEnvelopeFromNumericGT() { @Test public void testValueEnvelopeCasemapGT() { // LMTP MAIL FROM envelope () does not start 'N'-'Z' - String filterScript = "if envelope :value \"gt\" :comparator \"i;ascii-casemap\" " - + "[\"from\"] [\"M\"] {" + "tag \"Priority\";}"; - doTest(filterScript, null); + String filterScript = "require [\"envelope\", \"relational\"];" + + "if envelope :value \"gt\" :comparator \"i;ascii-casemap\" " + + "[\"from\"] [\"M\"] { tag \"Priority\";} else { tag \"No Priority\";}"; + doTest(filterScript, "No Priority"); } @Test @@ -273,7 +280,7 @@ public void testValueEnvelopeCasemap() { // LMTP MAIL FROM envelope () matchs the all upper case string // RFC 5228 Section 2.7.3. "i;ascii-casemap" comparator which treats uppercase and lowercase // characters in the US-ASCII subset of UTF-8 as the same - String filterScript = "require [\"fileinto\", \"tag\", \"flag\", \"log\", \"relational\"];\n" + String filterScript = "require [\"fileinto\", \"tag\", \"flag\", \"log\", \"relational\", \"envelope\"];\n" + "if envelope :value \"eq\" :comparator \"i;ascii-casemap\" " + "[\"from\"] \"ABC@ZIMBRA.COM\" {" + "tag \"Priority\";}"; doTest(filterScript, "Priority"); @@ -283,16 +290,17 @@ public void testValueEnvelopeCasemap() { public void testValueEnvelopeOctet() { // LMTP MAIL FROM envelope () does not match the all upper case string // RFC 5228 Section 2.7.3. i;octet comparator simply compares octets - String filterScript = "require [\"fileinto\", \"tag\", \"flag\", \"log\", \"relational\"];\n" + String filterScript = "require [\"fileinto\", \"tag\", \"flag\", \"log\", \"relational\", \"envelope\"];\n" + "if envelope :value \"eq\" :comparator \"i;octet\" " - + "[\"from\"] \"ABC@ZIMBRA.COM\" {" + "tag \"Priority\";}"; - doTest(filterScript, null); + + "[\"from\"] \"ABC@ZIMBRA.COM\" { tag \"Priority\";} else { tag \"No Priority\";}"; + doTest(filterScript, "No Priority"); } @Test public void testBadFormat_invalidEnvHeaderName() { - String filterScript = "if envelope :value \"gt\" :comparator \"i;ascii-casemap\" " - + "[\"AUTH\"] [\"M\"] {" + "tag \"Priority\";}"; + String filterScript = "require [\"envelope\", \"relational\"];" + + "if envelope :value \"gt\" :comparator \"i;ascii-casemap\" " + + "[\"AUTH\"] [\"M\"] {" + "tag \"Priority\";} else { tag \"No Priority\";}"; // The following error will occur: // org.apache.jsieve.exception.SyntaxException: Unexpected header name as a value for : 'AUTH' doTest(filterScript, null); @@ -300,7 +308,8 @@ public void testBadFormat_invalidEnvHeaderName() { @Test public void testValueEnvelopeFromNumeric_AllUpperCase() { - String filterScript = "IF ENVELOPE :COUNT \"EQ\" :COMPARATOR \"I;ASCII-NUMERIC\" " + String filterScript = "REQUIRE [\"envelope\", \"relational\", \"tag\", \"comparator-i;ascii-numeric\"];\n" + + "IF ENVELOPE :COUNT \"EQ\" :COMPARATOR \"I;ASCII-NUMERIC\" " + "[\"TO\"] [\"1\"] {" + "TAG \"Priority\";}"; doTest(filterScript, "Priority"); } @@ -309,10 +318,10 @@ public void testValueEnvelopeFromNumeric_AllUpperCase() { // and none of tag commands should be executed. @Test public void testValueHeaderNumericNegativeValue() { - String filterScript = "require [\"tag\", \"relational\"];\n" + String filterScript = "require [\"tag\", \"relational\", \"comparator-i;ascii-numeric\"];\n" + "if header :value \"lt\" :comparator \"i;ascii-numeric\" " - + "[\"x-priority\"] [\"-1\"] { tag \"Priority\"; }" - + "tag \"Negative\""; + + "[\"x-priority\"] [\"-1\"] { tag \"Priority\"; } else { tag \"No Priority\";}" + + "tag \"Negative\";"; doTest(filterScript, null); } @@ -320,9 +329,9 @@ public void testValueHeaderNumericNegativeValue() { // and none of tag commands should be executed. @Test public void testValueAddressNumericNegativeValue() { - String filterScript = "require [\"fileinto\", \"tag\", \"flag\", \"log\", \"relational\"];\n" + String filterScript = "require [\"fileinto\", \"tag\", \"flag\", \"log\", \"relational\", \"comparator-i;ascii-numeric\"];\n" + "if address :value \"lt\" :comparator \"i;ascii-numeric\" " - + "[\"to\"] [\"-1\"] { tag \"to\"; }" + + "[\"to\"] [\"-1\"] { tag \"to\"; } else { tag \"No To\";}" + "tag \"Negative\";"; doTest(filterScript, null); } @@ -331,9 +340,9 @@ public void testValueAddressNumericNegativeValue() { // and none of tag commands should be executed. @Test public void testValueEnvelopeFromNumericNegativeValue() { - String filterScript = "require [\"fileinto\", \"tag\", \"flag\", \"log\", \"relational\"];\n" + String filterScript = "require [\"fileinto\", \"tag\", \"flag\", \"log\", \"relational\", \"comparator-i;ascii-numeric\"];\n" + "if envelope :value \"lt\" :comparator \"i;ascii-numeric\" " - + "[\"from\"] [\"-1\"] { tag \"from\"; }" + + "[\"from\"] [\"-1\"] { tag \"from\"; } else { tag \"No From\";}" + "tag \"Negative\";"; doTest(filterScript, null); } @@ -342,9 +351,9 @@ public void testValueEnvelopeFromNumericNegativeValue() { // and none of tag commands should be executed. @Test public void testCountHeaderNumericNegativeValue() { - String filterScript = "require [\"fileinto\", \"tag\", \"flag\", \"log\", \"relational\"];\n" + String filterScript = "require [\"fileinto\", \"tag\", \"flag\", \"log\", \"relational\", \"comparator-i;ascii-numeric\"];\n" + "if header :count \"le\" :comparator \"i;ascii-numeric\" " - + "[\"received\"] [\"-3\"] { tag \"received\";}" + + "[\"received\"] [\"-3\"] { tag \"received\";} else { tag \"No Received\";}" + "tag \"Negative\";"; doTest(filterScript, null); } @@ -353,7 +362,7 @@ public void testCountHeaderNumericNegativeValue() { // and the tag command should not be executed. @Test public void testCountAddressNumericNegativeValue() { - String filterScript = "require [\"tag\", \"relational\"];\n" + String filterScript = "require [\"tag\", \"relational\", \"comparator-i;ascii-numeric\"];\n" + "if address :count \"le\" :comparator \"i;ascii-numeric\" " + "[\"to\", \"cc\"] [\"-1\"] { tag \"Priority\"; }"; doTest(filterScript, null); @@ -363,7 +372,7 @@ public void testCountAddressNumericNegativeValue() { // and the tag command should not be executed. @Test public void testCountEnvelopeToNumericNegativeValue() { - String filterScript = "require [\"tag\", \"relational\"];\n" + String filterScript = "require [\"tag\", \"relational\", \"comparator-i;ascii-numeric\"];\n" + "if envelope :count \"gt\" :comparator \"i;ascii-numeric\" " + "[\"to\"] [\"-1\"] { }" + "tag \"Priority\";"; diff --git a/store/src/java-test/com/zimbra/cs/filter/ReplaceHeaderTest.java b/store/src/java-test/com/zimbra/cs/filter/ReplaceHeaderTest.java index ec4da45d70e..6a2306d31c8 100644 --- a/store/src/java-test/com/zimbra/cs/filter/ReplaceHeaderTest.java +++ b/store/src/java-test/com/zimbra/cs/filter/ReplaceHeaderTest.java @@ -1,7 +1,7 @@ /* * ***** BEGIN LICENSE BLOCK ***** * Zimbra Collaboration Suite Server - * Copyright (C) 2016 Synacor, Inc. + * Copyright (C) 2016, 2017 Synacor, Inc. * * This program is free software: you can redistribute it and/or modify it under * the terms of the GNU General Public License as published by the Free Software Foundation, @@ -519,7 +519,7 @@ null, new DeliveryContext(), @Test public void testReplaceHeaderWithNumericComparisionUsingValue() { try { - String filterScript = "require [\"editheader\"];\n" + String filterScript = "require [\"editheader\", \"relational\", \"comparator-i;ascii-numeric\"];\n" + " replaceheader :newname \"X-Numeric2-Header\" :newvalue \"0\" :value \"lt\" :comparator \"i;ascii-numeric\" \"X-Numeric-Header\" \"3\" \r\n" + " ;\n"; Account acct1 = Provisioning.getInstance().get(Key.AccountBy.name, "test@zimbra.com"); @@ -554,7 +554,7 @@ null, new DeliveryContext(), @Test public void testReplaceHeaderWithNumericComparisionUsingCount() { try { - String filterScript = "require [\"editheader\"];\n" + String filterScript = "require [\"editheader\", \"relational\", \"comparator-i;ascii-numeric\"];\n" + " replaceheader :newname \"X-Numeric2-Header\" :count \"ge\" :comparator \"i;ascii-numeric\" \"X-Numeric-Header\" \"3\" \r\n" + " ;\n"; Account acct1 = Provisioning.getInstance().get(Key.AccountBy.name, "test@zimbra.com"); @@ -590,7 +590,7 @@ null, new DeliveryContext(), @Test public void testReplaceHeaderWithXSpamScore() { try { - String filterScript = "require [\"editheader\", \"variables\"];\n" + String filterScript = "require [\"editheader\", \"variables\", \"relational\", \"comparator-i;ascii-numeric\"];\n" + "if anyof(header :value \"ge\" :comparator \"i;ascii-numeric\" [\"X-Spam-Score\"] [\"80\"]) {" +" if exists \"Subject\" {" +" replaceheader :newvalue \"[SPAM]${1}\" :matches \"Subject\" \"*\";" @@ -1021,7 +1021,7 @@ null, new DeliveryContext(), @Test public void testReplaceHeaderWithCaseMapComparatorUsingValue() { try { - String filterScript = "require [\"editheader\"];\n" + String filterScript = "require [\"editheader\", \"relational\", \"comparator-i;ascii-numeric\"];\n" + " replaceheader :newname \"X-Test2-Header\" :newvalue \"0\" :value \"lt\" :comparator \"i;ascii-casemap\" \"X-Test-Header\" \"test2\" \r\n" + " ;\n"; Account acct1 = Provisioning.getInstance().get(Key.AccountBy.name, "test@zimbra.com"); @@ -1095,7 +1095,7 @@ null, new DeliveryContext(), @Test public void testReplaceHeaderWithEmptyHeaderNewName() { try { - String filterScript = "require [\"editheader\"];\n" + String filterScript = "require [\"editheader\", \"relational\"];\n" + " replaceheader :newname \"\" :newvalue \"0\" :value \"lt\" :comparator \"i;ascii-casemap\" \"X-Test-Header\" \"test2\" \r\n" + " ;\n"; Account acct1 = Provisioning.getInstance().get(Key.AccountBy.name, "test@zimbra.com"); @@ -1132,7 +1132,7 @@ null, new DeliveryContext(), @Test public void testReplaceHeaderWithSinlgeSpaceAsHeaderNewName() { try { - String filterScript = "require [\"editheader\"];\n" + String filterScript = "require [\"editheader\", \"relational\"];\n" + " replaceheader :newname \" \" :newvalue \"0\" :value \"lt\" :comparator \"i;ascii-casemap\" \"X-Test-Header\" \"test2\" \r\n" + " ;\n"; Account acct1 = Provisioning.getInstance().get(Key.AccountBy.name, "test@zimbra.com"); @@ -1168,7 +1168,7 @@ null, new DeliveryContext(), @Test public void testReplaceHeaderWithMultipleSpacesAsHeaderNewName() { try { - String filterScript = "require [\"editheader\"];\n" + String filterScript = "require [\"editheader\", \"relational\"];\n" + " replaceheader :newname \" \" :newvalue \"0\" :value \"lt\" :comparator \"i;ascii-casemap\" \"X-Test-Header\" \"test2\" \r\n" + " ;\n"; Account acct1 = Provisioning.getInstance().get(Key.AccountBy.name, "test@zimbra.com"); @@ -1204,7 +1204,7 @@ null, new DeliveryContext(), @Test public void testReplaceHeaderStartingWithSpacesAsHeaderNewName() { try { - String filterScript = "require [\"editheader\"];\n" + String filterScript = "require [\"editheader\", \"relational\"];\n" + " replaceheader :newname \" asdf\" :newvalue \"0\" :value \"lt\" :comparator \"i;ascii-casemap\" \"X-Test-Header\" \"test2\" \r\n" + " ;\n"; Account acct1 = Provisioning.getInstance().get(Key.AccountBy.name, "test@zimbra.com"); @@ -1276,7 +1276,7 @@ null, new DeliveryContext(), @Test public void testReplaceHeaderValueNegative() { try { - String filterScript = "require [\"editheader\"];\n" + String filterScript = "require [\"editheader\", \"relational\", \"comparator-i;ascii-numeric\"];\n" + "replaceheader :newname \"X-Numeric2-Header\" :newvalue \"0\" :value \"lt\" :comparator \"i;ascii-numeric\" \"X-Numeric-Header\" \"-3\";\n"; Account acct1 = Provisioning.getInstance().get(Key.AccountBy.name, "test@zimbra.com"); Mailbox mbox1 = MailboxManager.getInstance().getMailboxByAccount(acct1); @@ -1460,7 +1460,7 @@ public void testReplaceHeaderAsciiNumbericIsComparator() { + "\tby edge01e.zimbra.com (Postfix) with ESMTP id 9245B13575C;\n" + "\tFri, 24 Jun 2016 01:45:31 -0400 (EDT)\n" + "Subject: 1\n" + "to: test@zimbra.com\n"; - String filterScript = "require [\"editheader\"];\n" + String filterScript = "require [\"editheader\", \"comparator-i;ascii-numeric\"];\n" + "replaceheader :newvalue \"New Value\" :is :comparator \"i;ascii-numeric\" \"Subject\" \"1\";\n"; Account acct1 = Provisioning.getInstance().get(Key.AccountBy.name, "test@zimbra.com"); Mailbox mbox1 = MailboxManager.getInstance().getMailboxByAccount(acct1); @@ -1552,7 +1552,7 @@ public void replaceHeaderSieveEditHeaderEnabledTrue() { public void replaceHeaderSieveEditHeaderEnabledFalse() { try { String filterScript = "require [\"editheader\"];\n" - + "replaceheader :newvalue \"my subject\" :contains \"Subject\" \"example\""; + + "replaceheader :newvalue \"my subject\" :contains \"Subject\" \"example\";"; Account acct1 = Provisioning.getInstance().get(Key.AccountBy.name, "test@zimbra.com"); Mailbox mbox1 = MailboxManager.getInstance().getMailboxByAccount(acct1); RuleManager.clearCachedRules(acct1); @@ -1591,6 +1591,9 @@ public void replaceHeaderUserSieveScript() { Mailbox mbox1 = MailboxManager.getInstance().getMailboxByAccount(acct1); RuleManager.clearCachedRules(acct1); acct1.setSieveEditHeaderEnabled(true); + acct1.unsetAdminSieveScriptBefore(); + acct1.unsetMailSieveScript(); + acct1.unsetAdminSieveScriptAfter(); acct1.setMailSieveScript(filterScript); RuleManager.applyRulesToIncomingMessage(new OperationContext(mbox1), mbox1, new ParsedMessage(sampleBaseMsg.getBytes(), false), 0, acct1.getName(), null, @@ -1698,4 +1701,119 @@ mbox, new ParsedMessage("X-Mal-Encoded-Header: =?ABC?A?GyRCJFskMhsoQg==?=".getBy fail("No exception should be thrown" + e); } } + + /* + * The ascii-numeric comparator should be looked up in the list of the "require". + */ + @Test + public void testMissingComparatorNumericDeclaration() throws Exception { + // Default match type :is is used. + // No "comparator-i;ascii-numeric" capability text in the require command + String script = "require [\"editheader\"];\n" + + "replaceheader :newvalue \"20\" :comparator \"i;ascii-numeric\" \"X-Numeric-Header\" \"2\";"; + try { + Account account = Provisioning.getInstance().get(Key.AccountBy.name, "test@zimbra.com"); + Mailbox mbox = MailboxManager.getInstance().getMailboxByAccount(account); + RuleManager.clearCachedRules(account); + account.setSieveEditHeaderEnabled(true); + account.setAdminSieveScriptBefore(script); + account.setMailSieveScript(script); + List ids = RuleManager.applyRulesToIncomingMessage(new OperationContext(mbox), + mbox, new ParsedMessage(sampleBaseMsg.getBytes(), false), 0, + account.getName(), new DeliveryContext(), Mailbox.ID_FOLDER_INBOX, true); + Message message = mbox.getMessageById(null, ids.get(0).getId()); + String[] headers = message.getMimeMessage().getHeader("X-Numeric-Header"); + Assert.assertNotNull(headers); + Assert.assertNotSame(0, headers.length); + Assert.assertEquals("2", headers[0]); + } catch (Exception e) { + fail("No exception should be thrown" + e); + } + } + + @Test + public void testBackslashAsciiCasemap4bs() throws Exception { + // Matches four backslashes + String script = "require [\"editheader\"];\n" + + "replaceheader :newvalue \"replaced\" :comparator \"i;ascii-casemap\" \"X-Header\" \"Sample\\\\\\\\\\\\\\\\Pattern\";" + + "replaceheader :newvalue \"replaced\" :comparator \"i;ascii-casemap\" \"X-HeaderA\" \"Sample\\\\\\\\Pattern\";" + + "replaceheader :newvalue \"replaced\" :comparator \"i;ascii-casemap\" \"X-HeaderB\" \"Sample\\\\Pattern\";" + + "replaceheader :newvalue \"replaced\" :comparator \"i;ascii-casemap\" \"X-HeaderC\" \"Sample\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Pattern\";"; + String pattern = "Sample\\\\\\\\Pattern"; + String msg = "X-Header: " + pattern + "\n" + + "X-HeaderA: " + pattern + "\n" + + "X-HeaderB: " + pattern + "\n" + + "X-HeaderC: " + pattern + "\n"; + boolean result = testBackslash(script, pattern, msg); + Assert.assertTrue(result); + } + + @Test + public void testBackslashAsciiCasemap5bs() throws Exception { + // Matches five backslashes + String script = "require [\"editheader\"];\n" + + "replaceheader :newvalue \"replaced\" :comparator \"i;ascii-casemap\" \"X-Header\" \"Sample\\\\\\\\\\\\\\\\\\\\Pattern\";" + + "replaceheader :newvalue \"replaced\" :comparator \"i;ascii-casemap\" \"X-HeaderA\" \"Sample\\\\\\\\\\Pattern\";" + + "replaceheader :newvalue \"replaced\" :comparator \"i;ascii-casemap\" \"X-HeaderB\" \"Sample\\\\\\Pattern\";" + + "replaceheader :newvalue \"replaced\" :comparator \"i;ascii-casemap\" \"X-HeaderC\" \"Sample\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Pattern\";"; + String pattern = "Sample\\\\\\\\\\Pattern"; + String msg = "X-Header: " + pattern + "\n" + + "X-HeaderA: " + pattern + "\n" + + "X-HeaderB: " + pattern + "\n" + + "X-HeaderC: " + pattern + "\n"; + boolean result = testBackslash(script, pattern, msg); + Assert.assertTrue(result); + } + @Test + public void testBackslashOctet() throws Exception { + String script = "require [\"editheader\"];\n" + + "replaceheader :newvalue \"replaced\" :comparator \"i;octet\" \"X-Header\" \"Sample\\\\\\\\\\\\\\\\Pattern\";" + + "replaceheader :newvalue \"replaced\" :comparator \"i;octet\" \"X-HeaderA\" \"Sample\\\\\\\\Pattern\";" + + "replaceheader :newvalue \"replaced\" :comparator \"i;octet\" \"X-HeaderB\" \"Sample\\\\Pattern\";" + + "replaceheader :newvalue \"replaced\" :comparator \"i;octet\" \"X-HeaderC\" \"Sample\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Pattern\";"; + String pattern = "Sample\\\\\\\\Pattern"; + String msg = "X-Header: " + pattern + "\n" + + "X-HeaderA: " + pattern + "\n" + + "X-HeaderB: " + pattern + "\n" + + "X-HeaderC: " + pattern + "\n"; + boolean result = testBackslash(script, pattern, msg); + Assert.assertTrue(result); + } + + private boolean testBackslash(String script, String pattern, String msg) { + try { + Account account = Provisioning.getInstance().get(Key.AccountBy.name, "test@zimbra.com"); + Mailbox mbox = MailboxManager.getInstance().getMailboxByAccount(account); + RuleManager.clearCachedRules(account); + account.unsetAdminSieveScriptBefore(); + account.unsetMailSieveScript(); + account.unsetAdminSieveScriptAfter(); + account.setSieveEditHeaderEnabled(true); + account.setAdminSieveScriptBefore(script); + List ids = RuleManager.applyRulesToIncomingMessage(new OperationContext(mbox), + mbox, new ParsedMessage(msg.getBytes(), false), 0, + account.getName(), new DeliveryContext(), Mailbox.ID_FOLDER_INBOX, true); + Message message = mbox.getMessageById(null, ids.get(0).getId()); + String[] headers = message.getMimeMessage().getHeader("X-Header"); + Assert.assertNotNull(headers); + Assert.assertNotSame(0, headers.length); + Assert.assertEquals("replaced", headers[0]); + headers = message.getMimeMessage().getHeader("X-HeaderA"); + Assert.assertNotNull(headers); + Assert.assertNotSame(0, headers.length); + Assert.assertEquals(pattern, headers[0]); + headers = message.getMimeMessage().getHeader("X-HeaderB"); + Assert.assertNotNull(headers); + Assert.assertNotSame(0, headers.length); + Assert.assertEquals(pattern, headers[0]); + headers = message.getMimeMessage().getHeader("X-HeaderC"); + Assert.assertNotNull(headers); + Assert.assertNotSame(0, headers.length); + Assert.assertEquals(pattern, headers[0]); + return true; + } catch (Exception e) { + fail("No exception should be thrown" + e); + return false; + } + } } \ No newline at end of file diff --git a/store/src/java-test/com/zimbra/cs/filter/SetVariableTest.java b/store/src/java-test/com/zimbra/cs/filter/SetVariableTest.java index 77c8f046959..2d540a6b91d 100644 --- a/store/src/java-test/com/zimbra/cs/filter/SetVariableTest.java +++ b/store/src/java-test/com/zimbra/cs/filter/SetVariableTest.java @@ -644,7 +644,7 @@ public void testStringTest() { + "\n" + "Hello World."; RuleManager.clearCachedRules(account); - filterScript = "require [\"variables\"];\n" + filterScript = "require [\"variables\", \"comparator-i;ascii-numeric\"];\n" + "set :lower :upperfirst \"name\" \"Joe\";\n" + "if string :is :comparator \"i;ascii-numeric\" \"${name}\" [ \"Joe\", \"Hello\", \"User\" ]{\n" + " tag \"sales-1\";\n" @@ -1564,7 +1564,7 @@ public void testDollar2() { Account account = Provisioning.getInstance().getAccount(MockProvisioning.DEFAULT_ACCOUNT_ID); RuleManager.clearCachedRules(account); Mailbox mbox = MailboxManager.getInstance().getMailboxByAccount(account); - filterScript = "require [\"variables\"];\n" + filterScript = "require [\"variables\", \"envelope\"];\n" + "set \"dollar\" \"$\";\n" + "set \"val\" \"xyz\";\n" + "if header :matches :comparator \"i;ascii-casemap\" \"Subject\" \"${dollar}${val}\" {\n" @@ -2059,7 +2059,8 @@ public void testNegativeVarIndex() { RuleManager.clearCachedRules(account); Mailbox mbox = MailboxManager.getInstance().getMailboxByAccount(account); - filterScript = "if header :matches :comparator \"i;ascii-casemap\" \"Subject\" \"*C*a*c*ple*oo *ge*yo 123 *56*89 sie*e*t\" { " + filterScript = "require \"variables\";" + + "if header :matches :comparator \"i;ascii-casemap\" \"Subject\" \"*C*a*c*ple*oo *ge*yo 123 *56*89 sie*e*t\" { " + "tag \"${-1}\";}"; account.setMailSieveScript(filterScript); @@ -2091,7 +2092,8 @@ public void testOutofRangeVarIndex() { RuleManager.clearCachedRules(account); Mailbox mbox = MailboxManager.getInstance().getMailboxByAccount(account); - filterScript = "if header :matches :comparator \"i;ascii-casemap\" \"Subject\" \"*C*a*c*ple*oo *ge*yo 123 *56*89 sie*e*t\" { " + filterScript = "require \"variables\";" + + "if header :matches :comparator \"i;ascii-casemap\" \"Subject\" \"*C*a*c*ple*oo *ge*yo 123 *56*89 sie*e*t\" { " + "tag \"${10}\";}"; account.setMailSieveScript(filterScript); @@ -2123,7 +2125,8 @@ public void testOutofRangeVarIndexWithLeadingZeroes() { RuleManager.clearCachedRules(account); Mailbox mbox = MailboxManager.getInstance().getMailboxByAccount(account); - filterScript = "if header :matches :comparator \"i;ascii-casemap\" \"Subject\" \"*C*a*c*ple*oo *ge*yo 123 *56*89 sie*e*t\" { " + filterScript = "require \"variables\";" + + "if header :matches :comparator \"i;ascii-casemap\" \"Subject\" \"*C*a*c*ple*oo *ge*yo 123 *56*89 sie*e*t\" { " + "tag \"${0010}\";}"; account.setMailSieveScript(filterScript); @@ -2272,7 +2275,7 @@ public void testStringNumericNegativeTest() { Mailbox mbox = MailboxManager.getInstance().getMailboxByAccount(account); - filterScript = "require [\"variables\", \"tag\"];\n" + filterScript = "require [\"variables\", \"tag\", \"comparator-i;ascii-numeric\"];\n" + "set \"negative\" \"-123\";\n" + "if string :is :comparator \"i;ascii-numeric\" \"${negative}\" \"-123\" {\n" + " tag \"negative\";\n" @@ -2411,4 +2414,41 @@ public void testNoRequireDeclaration() { fail("No exception should be thrown: " + e); } } + + /* + * The ascii-numeric comparator should be looked up in the list of the "require". + */ + @Test + public void testMissingComparatorNumericDeclaration() { + try { + Account account = Provisioning.getInstance().getAccount(MockProvisioning.DEFAULT_ACCOUNT_ID); + RuleManager.clearCachedRules(account); + Mailbox mbox = MailboxManager.getInstance().getMailboxByAccount(account); + + // Default match type :is is used. + // No "comparator-i;ascii-numeric" capability text in the require command + filterScript = "require \"variables\";" + + "set \"state\" \"1\";\n" + + "if string :comparator \"i;ascii-numeric\" \"${state}\" \"1\" {\n" + + " tag \"is\";\n" + + "} else {\n" + + " tag \"not is\";\n" + + "}\n"; + + account.setMailSieveScript(filterScript); + String raw = "From: sender@zimbra.com\n" + + "To: test1@zimbra.com\n" + + "Subject: test"; + + List ids = RuleManager.applyRulesToIncomingMessage(new OperationContext(mbox), mbox, + new ParsedMessage(raw.getBytes(), false), 0, account.getName(), new DeliveryContext(), + Mailbox.ID_FOLDER_INBOX, true); + Assert.assertEquals(1, ids.size()); + Message msg = mbox.getMessageById(null, ids.get(0).getId()); + Assert.assertEquals(null, ArrayUtil.getFirstElement(msg.getTags())); + } catch (Exception e) { + e.printStackTrace(); + fail("No exception should be thrown"); + } + } } diff --git a/store/src/java-test/com/zimbra/cs/filter/ValueComparisonTest.java b/store/src/java-test/com/zimbra/cs/filter/ValueComparisonTest.java new file mode 100644 index 00000000000..7b1e625bce6 --- /dev/null +++ b/store/src/java-test/com/zimbra/cs/filter/ValueComparisonTest.java @@ -0,0 +1,586 @@ +/* + * ***** BEGIN LICENSE BLOCK ***** + * Zimbra Collaboration Suite Server + * Copyright (C) 2017 Synacor, Inc. + * + * This program is free software: you can redistribute it and/or modify it under + * the terms of the GNU General Public License as published by the Free Software Foundation, + * version 2 of the License. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * You should have received a copy of the GNU General Public License along with this program. + * If not, see . + * ***** END LICENSE BLOCK ***** + */ +package com.zimbra.cs.filter; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; + +import java.util.HashMap; + +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; + +import com.zimbra.common.account.Key; +import com.zimbra.common.service.ServiceException; +import com.zimbra.common.soap.Element; +import com.zimbra.common.soap.MailConstants; +import com.zimbra.common.util.ZimbraLog; +import com.zimbra.cs.account.Account; +import com.zimbra.cs.account.MockProvisioning; +import com.zimbra.cs.account.Provisioning; +import com.zimbra.cs.mailbox.MailboxTestUtil; +import com.zimbra.cs.service.mail.GetFilterRules; +import com.zimbra.cs.service.mail.ModifyFilterRules; +import com.zimbra.cs.service.mail.ServiceTestUtil; +import com.zimbra.cs.util.XMLDiffChecker; + +public class ValueComparisonTest { + + @BeforeClass + public static void init() throws Exception { + MailboxTestUtil.initServer(); + Provisioning prov = Provisioning.getInstance(); + prov.createAccount("test@zimbra.com", "secret", new HashMap()); + } + + @Before + public void setUp() throws Exception { + MailboxTestUtil.clearData(); + } + + /** + * Tests rule creation through ModifyFilterRulesRequest for header test having caseSensitive attribute with + * string comparison + * @throws Exception + */ + @Test + public void testHeaderTestStringComparisonCaseSensitivity() throws Exception { + Account acct = Provisioning.getInstance().get(Key.AccountBy.name, "test@zimbra.com"); + Element request = new Element.XMLElement(MailConstants.MODIFY_FILTER_RULES_REQUEST); + + Element rules = request.addNonUniqueElement(MailConstants.E_FILTER_RULES); + Element rule = rules.addNonUniqueElement(MailConstants.E_FILTER_RULE); + rule.addAttribute(MailConstants.A_ACTIVE, true); + rule.addAttribute(MailConstants.A_NAME, "Test1"); + Element filteraction = rule.addNonUniqueElement(MailConstants.E_FILTER_ACTIONS); + Element actionInto = filteraction.addNonUniqueElement(MailConstants.E_ACTION_FILE_INTO); + actionInto.addAttribute(MailConstants.A_FOLDER_PATH, "Junk"); + filteraction.addNonUniqueElement(MailConstants.E_ACTION_STOP); + Element filterTests = rule.addNonUniqueElement(MailConstants.E_FILTER_TESTS); + filterTests.addAttribute(MailConstants.A_CONDITION, "anyof"); + Element headerTest = filterTests.addNonUniqueElement(MailConstants.E_HEADER_TEST); + headerTest.addAttribute(MailConstants.A_HEADER, "subject"); + headerTest.addAttribute(MailConstants.A_STRING_COMPARISON, "contains"); + headerTest.addAttribute(MailConstants.A_CASE_SENSITIVE, "true"); + headerTest.addAttribute(MailConstants.A_VALUE, "Important"); + + try { + new ModifyFilterRules().handle(request, ServiceTestUtil.getRequestContext(acct)); + } catch (ServiceException e) { + fail("This test is expected not to throw exception. "); + } + + String expectedScript = "require [\"fileinto\", \"copy\", \"reject\", \"tag\", \"flag\", \"variables\", \"log\", \"enotify\", \"envelope\", \"body\", \"ereject\", \"reject\", \"relational\", \"comparator-i;ascii-numeric\"];\n\n"; + expectedScript += "# Test1\n"; + expectedScript += "if anyof (header :contains :comparator \"i;octet\" [\"subject\"] \"Important\") {\n"; + expectedScript += " fileinto \"Junk\";\n"; + expectedScript += " stop;\n"; + expectedScript += "}\n"; + + ZimbraLog.filter.info(acct.getMailSieveScript()); + ZimbraLog.filter.info(expectedScript); + assertEquals(expectedScript, acct.getMailSieveScript()); + } + + /** + * Tests rule creation through ModifyFilterRulesRequest for address test having caseSensitive attribute with + * string comparison + * @throws Exception + */ + @Test + public void testAddressTestStringComparisonCaseSensitivity() throws Exception { + Account acct = Provisioning.getInstance().get(Key.AccountBy.name, "test@zimbra.com"); + Element request = new Element.XMLElement(MailConstants.MODIFY_FILTER_RULES_REQUEST); + + Element rules = request.addNonUniqueElement(MailConstants.E_FILTER_RULES); + Element rule = rules.addNonUniqueElement(MailConstants.E_FILTER_RULE); + rule.addAttribute(MailConstants.A_ACTIVE, true); + rule.addAttribute(MailConstants.A_NAME, "Test1"); + Element filteraction = rule.addNonUniqueElement(MailConstants.E_FILTER_ACTIONS); + Element actionInto = filteraction.addNonUniqueElement(MailConstants.E_ACTION_FILE_INTO); + actionInto.addAttribute(MailConstants.A_FOLDER_PATH, "Junk"); + filteraction.addNonUniqueElement(MailConstants.E_ACTION_STOP); + Element filterTests = rule.addNonUniqueElement(MailConstants.E_FILTER_TESTS); + filterTests.addAttribute(MailConstants.A_CONDITION, "anyof"); + Element addressTest = filterTests.addNonUniqueElement(MailConstants.E_ADDRESS_TEST); + addressTest.addAttribute(MailConstants.A_HEADER, "from"); + addressTest.addAttribute(MailConstants.A_STRING_COMPARISON, "contains"); + addressTest.addAttribute(MailConstants.A_CASE_SENSITIVE, "true"); + addressTest.addAttribute(MailConstants.A_VALUE, "abCD"); + + try { + new ModifyFilterRules().handle(request, ServiceTestUtil.getRequestContext(acct)); + } catch (ServiceException e) { + fail("This test is expected not to throw exception. "); + } + + String expectedScript = "require [\"fileinto\", \"copy\", \"reject\", \"tag\", \"flag\", \"variables\", \"log\", \"enotify\", \"envelope\", \"body\", \"ereject\", \"reject\", \"relational\", \"comparator-i;ascii-numeric\"];\n\n"; + expectedScript += "# Test1\n"; + expectedScript += "if anyof (address :all :contains :comparator \"i;octet\" [\"from\"] \"abCD\") {\n"; + expectedScript += " fileinto \"Junk\";\n"; + expectedScript += " stop;\n"; + expectedScript += "}\n"; + + ZimbraLog.filter.info(acct.getMailSieveScript()); + ZimbraLog.filter.info(expectedScript); + assertEquals(expectedScript, acct.getMailSieveScript()); + } + + /** + * Tests rule creation through ModifyFilterRulesRequest for envelope test having caseSensitive attribute with + * string comparison + * @throws Exception + */ + @Test + public void testEnvelopeTestStringComparisonCaseSensitivity() throws Exception { + Account acct = Provisioning.getInstance().get(Key.AccountBy.name, "test@zimbra.com"); + Element request = new Element.XMLElement(MailConstants.MODIFY_FILTER_RULES_REQUEST); + + Element rules = request.addNonUniqueElement(MailConstants.E_FILTER_RULES); + Element rule = rules.addNonUniqueElement(MailConstants.E_FILTER_RULE); + rule.addAttribute(MailConstants.A_ACTIVE, true); + rule.addAttribute(MailConstants.A_NAME, "Test1"); + Element filteraction = rule.addNonUniqueElement(MailConstants.E_FILTER_ACTIONS); + Element actionInto = filteraction.addNonUniqueElement(MailConstants.E_ACTION_FILE_INTO); + actionInto.addAttribute(MailConstants.A_FOLDER_PATH, "Junk"); + filteraction.addNonUniqueElement(MailConstants.E_ACTION_STOP); + Element filterTests = rule.addNonUniqueElement(MailConstants.E_FILTER_TESTS); + filterTests.addAttribute(MailConstants.A_CONDITION, "anyof"); + Element envelopeTest = filterTests.addNonUniqueElement(MailConstants.E_ENVELOPE_TEST); + envelopeTest.addAttribute(MailConstants.A_HEADER, "from"); + envelopeTest.addAttribute(MailConstants.A_STRING_COMPARISON, "contains"); + envelopeTest.addAttribute(MailConstants.A_CASE_SENSITIVE, "true"); + envelopeTest.addAttribute(MailConstants.A_VALUE, "abCD"); + + try { + new ModifyFilterRules().handle(request, ServiceTestUtil.getRequestContext(acct)); + } catch (ServiceException e) { + fail("This test is expected not to throw exception. "); + } + + String expectedScript = "require [\"fileinto\", \"copy\", \"reject\", \"tag\", \"flag\", \"variables\", \"log\", \"enotify\", \"envelope\", \"body\", \"ereject\", \"reject\", \"relational\", \"comparator-i;ascii-numeric\"];\n\n"; + expectedScript += "# Test1\n"; + expectedScript += "if anyof (envelope :all :contains :comparator \"i;octet\" [\"from\"] \"abCD\") {\n"; + expectedScript += " fileinto \"Junk\";\n"; + expectedScript += " stop;\n"; + expectedScript += "}\n"; + + ZimbraLog.filter.info(acct.getMailSieveScript()); + ZimbraLog.filter.info(expectedScript); + assertEquals(expectedScript, acct.getMailSieveScript()); + } + + /** + * Tests rule creation through ModifyFilterRulesRequest for header test having caseSensitive attribute with + * value comparison + * @throws Exception + */ + @Test + public void testHeaderTestValueComparisonCaseSensitivity() throws Exception { + Account acct = Provisioning.getInstance().get(Key.AccountBy.name, "test@zimbra.com"); + Element request = new Element.XMLElement(MailConstants.MODIFY_FILTER_RULES_REQUEST); + + Element rules = request.addNonUniqueElement(MailConstants.E_FILTER_RULES); + Element rule = rules.addNonUniqueElement(MailConstants.E_FILTER_RULE); + rule.addAttribute(MailConstants.A_ACTIVE, true); + rule.addAttribute(MailConstants.A_NAME, "Test1"); + Element filteraction = rule.addNonUniqueElement(MailConstants.E_FILTER_ACTIONS); + Element actionInto = filteraction.addNonUniqueElement(MailConstants.E_ACTION_FILE_INTO); + actionInto.addAttribute(MailConstants.A_FOLDER_PATH, "Junk"); + filteraction.addNonUniqueElement(MailConstants.E_ACTION_STOP); + Element filterTests = rule.addNonUniqueElement(MailConstants.E_FILTER_TESTS); + filterTests.addAttribute(MailConstants.A_CONDITION, "anyof"); + Element headerTest = filterTests.addNonUniqueElement(MailConstants.E_HEADER_TEST); + headerTest.addAttribute(MailConstants.A_HEADER, "subject"); + headerTest.addAttribute(MailConstants.A_VALUE_COMPARISON, "eq"); + headerTest.addAttribute(MailConstants.A_CASE_SENSITIVE, "true"); + headerTest.addAttribute(MailConstants.A_VALUE, "Important"); + + try { + new ModifyFilterRules().handle(request, ServiceTestUtil.getRequestContext(acct)); + } catch (ServiceException e) { + fail("This test is expected not to throw exception. "); + } + + String expectedScript = "require [\"fileinto\", \"copy\", \"reject\", \"tag\", \"flag\", \"variables\", \"log\", \"enotify\", \"envelope\", \"body\", \"ereject\", \"reject\", \"relational\", \"comparator-i;ascii-numeric\"];\n\n"; + expectedScript += "# Test1\n"; + expectedScript += "if anyof (header :value \"eq\" :comparator \"i;octet\" [\"subject\"] \"Important\") {\n"; + expectedScript += " fileinto \"Junk\";\n"; + expectedScript += " stop;\n"; + expectedScript += "}\n"; + + ZimbraLog.filter.info(acct.getMailSieveScript()); + ZimbraLog.filter.info(expectedScript); + assertEquals(expectedScript, acct.getMailSieveScript()); + } + + /** + * Tests rule creation through ModifyFilterRulesRequest for address test having caseSensitive attribute with + * value comparison + * @throws Exception + */ + @Test + public void testAddressTestValueComparisonCaseSensitivity() throws Exception { + Account acct = Provisioning.getInstance().get(Key.AccountBy.name, "test@zimbra.com"); + Element request = new Element.XMLElement(MailConstants.MODIFY_FILTER_RULES_REQUEST); + + Element rules = request.addNonUniqueElement(MailConstants.E_FILTER_RULES); + Element rule = rules.addNonUniqueElement(MailConstants.E_FILTER_RULE); + rule.addAttribute(MailConstants.A_ACTIVE, true); + rule.addAttribute(MailConstants.A_NAME, "Test1"); + Element filteraction = rule.addNonUniqueElement(MailConstants.E_FILTER_ACTIONS); + Element actionInto = filteraction.addNonUniqueElement(MailConstants.E_ACTION_FILE_INTO); + actionInto.addAttribute(MailConstants.A_FOLDER_PATH, "Junk"); + filteraction.addNonUniqueElement(MailConstants.E_ACTION_STOP); + Element filterTests = rule.addNonUniqueElement(MailConstants.E_FILTER_TESTS); + filterTests.addAttribute(MailConstants.A_CONDITION, "anyof"); + Element addressTest = filterTests.addNonUniqueElement(MailConstants.E_ADDRESS_TEST); + addressTest.addAttribute(MailConstants.A_HEADER, "from"); + addressTest.addAttribute(MailConstants.A_VALUE_COMPARISON, "eq"); + addressTest.addAttribute(MailConstants.A_CASE_SENSITIVE, "true"); + addressTest.addAttribute(MailConstants.A_VALUE, "abCD"); + + try { + new ModifyFilterRules().handle(request, ServiceTestUtil.getRequestContext(acct)); + } catch (ServiceException e) { + fail("This test is expected not to throw exception. "); + } + + String expectedScript = "require [\"fileinto\", \"copy\", \"reject\", \"tag\", \"flag\", \"variables\", \"log\", \"enotify\", \"envelope\", \"body\", \"ereject\", \"reject\", \"relational\", \"comparator-i;ascii-numeric\"];\n\n"; + expectedScript += "# Test1\n"; + expectedScript += "if anyof (address :value \"eq\" :all :comparator \"i;octet\" [\"from\"] \"abCD\") {\n"; + expectedScript += " fileinto \"Junk\";\n"; + expectedScript += " stop;\n"; + expectedScript += "}\n"; + + ZimbraLog.filter.info(acct.getMailSieveScript()); + ZimbraLog.filter.info(expectedScript); + assertEquals(expectedScript, acct.getMailSieveScript()); + } + + /** + * Tests rule creation through ModifyFilterRulesRequest for envelope test having caseSensitive attribute with + * value comparison + * @throws Exception + */ + @Test + public void testEnvelopeTestValueComparisonCaseSensitivity() throws Exception { + Account acct = Provisioning.getInstance().get(Key.AccountBy.name, "test@zimbra.com"); + Element request = new Element.XMLElement(MailConstants.MODIFY_FILTER_RULES_REQUEST); + + Element rules = request.addNonUniqueElement(MailConstants.E_FILTER_RULES); + Element rule = rules.addNonUniqueElement(MailConstants.E_FILTER_RULE); + rule.addAttribute(MailConstants.A_ACTIVE, true); + rule.addAttribute(MailConstants.A_NAME, "Test1"); + Element filteraction = rule.addNonUniqueElement(MailConstants.E_FILTER_ACTIONS); + Element actionInto = filteraction.addNonUniqueElement(MailConstants.E_ACTION_FILE_INTO); + actionInto.addAttribute(MailConstants.A_FOLDER_PATH, "Junk"); + filteraction.addNonUniqueElement(MailConstants.E_ACTION_STOP); + Element filterTests = rule.addNonUniqueElement(MailConstants.E_FILTER_TESTS); + filterTests.addAttribute(MailConstants.A_CONDITION, "anyof"); + Element envelopeTest = filterTests.addNonUniqueElement(MailConstants.E_ENVELOPE_TEST); + envelopeTest.addAttribute(MailConstants.A_HEADER, "from"); + envelopeTest.addAttribute(MailConstants.A_VALUE_COMPARISON, "eq"); + envelopeTest.addAttribute(MailConstants.A_CASE_SENSITIVE, "true"); + envelopeTest.addAttribute(MailConstants.A_VALUE, "abCD"); + + try { + new ModifyFilterRules().handle(request, ServiceTestUtil.getRequestContext(acct)); + } catch (ServiceException e) { + fail("This test is expected not to throw exception. "); + } + + String expectedScript = "require [\"fileinto\", \"copy\", \"reject\", \"tag\", \"flag\", \"variables\", \"log\", \"enotify\", \"envelope\", \"body\", \"ereject\", \"reject\", \"relational\", \"comparator-i;ascii-numeric\"];\n\n"; + expectedScript += "# Test1\n"; + expectedScript += "if anyof (envelope :value \"eq\" :all :comparator \"i;octet\" [\"from\"] \"abCD\") {\n"; + expectedScript += " fileinto \"Junk\";\n"; + expectedScript += " stop;\n"; + expectedScript += "}\n"; + + ZimbraLog.filter.info(acct.getMailSieveScript()); + ZimbraLog.filter.info(expectedScript); + assertEquals(expectedScript, acct.getMailSieveScript()); + } + + /** + * Tests rule creation through ModifyFilterRulesRequest for header test having value comparison with + * valueComparisonComparator + * @throws Exception + */ + @Test + public void testHeaderTestValueComparisonComparator() throws Exception { + Account acct = Provisioning.getInstance().get(Key.AccountBy.name, "test@zimbra.com"); + Element request = new Element.XMLElement(MailConstants.MODIFY_FILTER_RULES_REQUEST); + + Element rules = request.addNonUniqueElement(MailConstants.E_FILTER_RULES); + Element rule = rules.addNonUniqueElement(MailConstants.E_FILTER_RULE); + rule.addAttribute(MailConstants.A_ACTIVE, true); + rule.addAttribute(MailConstants.A_NAME, "Test1"); + Element filteraction = rule.addNonUniqueElement(MailConstants.E_FILTER_ACTIONS); + Element actionInto = filteraction.addNonUniqueElement(MailConstants.E_ACTION_FILE_INTO); + actionInto.addAttribute(MailConstants.A_FOLDER_PATH, "Junk"); + filteraction.addNonUniqueElement(MailConstants.E_ACTION_STOP); + Element filterTests = rule.addNonUniqueElement(MailConstants.E_FILTER_TESTS); + filterTests.addAttribute(MailConstants.A_CONDITION, "anyof"); + Element headerTest = filterTests.addNonUniqueElement(MailConstants.E_HEADER_TEST); + headerTest.addAttribute(MailConstants.A_HEADER, "subject"); + headerTest.addAttribute(MailConstants.A_VALUE_COMPARISON, "eq"); + headerTest.addAttribute(MailConstants.A_VALUE_COMPARISON_COMPARATOR, "i;octet"); + headerTest.addAttribute(MailConstants.A_VALUE, "Important"); + + try { + new ModifyFilterRules().handle(request, ServiceTestUtil.getRequestContext(acct)); + } catch (ServiceException e) { + fail("This test is expected not to throw exception. "); + } + + String expectedScript = "require [\"fileinto\", \"copy\", \"reject\", \"tag\", \"flag\", \"variables\", \"log\", \"enotify\", \"envelope\", \"body\", \"ereject\", \"reject\", \"relational\", \"comparator-i;ascii-numeric\"];\n\n"; + expectedScript += "# Test1\n"; + expectedScript += "if anyof (header :value \"eq\" :comparator \"i;octet\" [\"subject\"] \"Important\") {\n"; + expectedScript += " fileinto \"Junk\";\n"; + expectedScript += " stop;\n"; + expectedScript += "}\n"; + + ZimbraLog.filter.info(acct.getMailSieveScript()); + ZimbraLog.filter.info(expectedScript); + assertEquals(expectedScript, acct.getMailSieveScript()); + } + + /** + * Tests rule creation through ModifyFilterRulesRequest for address test having value comparison with + * valueComparisonComparator + * @throws Exception + */ + @Test + public void testAddressTestValueComparisonComparator() throws Exception { + Account acct = Provisioning.getInstance().get(Key.AccountBy.name, "test@zimbra.com"); + Element request = new Element.XMLElement(MailConstants.MODIFY_FILTER_RULES_REQUEST); + + Element rules = request.addNonUniqueElement(MailConstants.E_FILTER_RULES); + Element rule = rules.addNonUniqueElement(MailConstants.E_FILTER_RULE); + rule.addAttribute(MailConstants.A_ACTIVE, true); + rule.addAttribute(MailConstants.A_NAME, "Test1"); + Element filteraction = rule.addNonUniqueElement(MailConstants.E_FILTER_ACTIONS); + Element actionInto = filteraction.addNonUniqueElement(MailConstants.E_ACTION_FILE_INTO); + actionInto.addAttribute(MailConstants.A_FOLDER_PATH, "Junk"); + filteraction.addNonUniqueElement(MailConstants.E_ACTION_STOP); + Element filterTests = rule.addNonUniqueElement(MailConstants.E_FILTER_TESTS); + filterTests.addAttribute(MailConstants.A_CONDITION, "anyof"); + Element addressTest = filterTests.addNonUniqueElement(MailConstants.E_ADDRESS_TEST); + addressTest.addAttribute(MailConstants.A_HEADER, "from"); + addressTest.addAttribute(MailConstants.A_VALUE_COMPARISON, "eq"); + addressTest.addAttribute(MailConstants.A_VALUE_COMPARISON_COMPARATOR, "i;octet"); + addressTest.addAttribute(MailConstants.A_VALUE, "abCD"); + + try { + new ModifyFilterRules().handle(request, ServiceTestUtil.getRequestContext(acct)); + } catch (ServiceException e) { + fail("This test is expected not to throw exception. "); + } + + String expectedScript = "require [\"fileinto\", \"copy\", \"reject\", \"tag\", \"flag\", \"variables\", \"log\", \"enotify\", \"envelope\", \"body\", \"ereject\", \"reject\", \"relational\", \"comparator-i;ascii-numeric\"];\n\n"; + expectedScript += "# Test1\n"; + expectedScript += "if anyof (address :value \"eq\" :all :comparator \"i;octet\" [\"from\"] \"abCD\") {\n"; + expectedScript += " fileinto \"Junk\";\n"; + expectedScript += " stop;\n"; + expectedScript += "}\n"; + + ZimbraLog.filter.info(acct.getMailSieveScript()); + ZimbraLog.filter.info(expectedScript); + assertEquals(expectedScript, acct.getMailSieveScript()); + } + + /** + * Tests rule creation through ModifyFilterRulesRequest for envelope test having value comparison with + * valueComparisonComparator + * @throws Exception + */ + @Test + public void testEnvelopeTestValueComparisonComparator() throws Exception { + Account acct = Provisioning.getInstance().get(Key.AccountBy.name, "test@zimbra.com"); + Element request = new Element.XMLElement(MailConstants.MODIFY_FILTER_RULES_REQUEST); + + Element rules = request.addNonUniqueElement(MailConstants.E_FILTER_RULES); + Element rule = rules.addNonUniqueElement(MailConstants.E_FILTER_RULE); + rule.addAttribute(MailConstants.A_ACTIVE, true); + rule.addAttribute(MailConstants.A_NAME, "Test1"); + Element filteraction = rule.addNonUniqueElement(MailConstants.E_FILTER_ACTIONS); + Element actionInto = filteraction.addNonUniqueElement(MailConstants.E_ACTION_FILE_INTO); + actionInto.addAttribute(MailConstants.A_FOLDER_PATH, "Junk"); + filteraction.addNonUniqueElement(MailConstants.E_ACTION_STOP); + Element filterTests = rule.addNonUniqueElement(MailConstants.E_FILTER_TESTS); + filterTests.addAttribute(MailConstants.A_CONDITION, "anyof"); + Element envelopeTest = filterTests.addNonUniqueElement(MailConstants.E_ENVELOPE_TEST); + envelopeTest.addAttribute(MailConstants.A_HEADER, "from"); + envelopeTest.addAttribute(MailConstants.A_VALUE_COMPARISON, "eq"); + envelopeTest.addAttribute(MailConstants.A_VALUE_COMPARISON_COMPARATOR, "i;octet"); + envelopeTest.addAttribute(MailConstants.A_VALUE, "abCD"); + + try { + new ModifyFilterRules().handle(request, ServiceTestUtil.getRequestContext(acct)); + } catch (ServiceException e) { + fail("This test is expected not to throw exception. "); + } + + String expectedScript = "require [\"fileinto\", \"copy\", \"reject\", \"tag\", \"flag\", \"variables\", \"log\", \"enotify\", \"envelope\", \"body\", \"ereject\", \"reject\", \"relational\", \"comparator-i;ascii-numeric\"];\n\n"; + expectedScript += "# Test1\n"; + expectedScript += "if anyof (envelope :value \"eq\" :all :comparator \"i;octet\" [\"from\"] \"abCD\") {\n"; + expectedScript += " fileinto \"Junk\";\n"; + expectedScript += " stop;\n"; + expectedScript += "}\n"; + + ZimbraLog.filter.info(acct.getMailSieveScript()); + ZimbraLog.filter.info(expectedScript); + assertEquals(expectedScript, acct.getMailSieveScript()); + } + + /** + * Tests GetFilterRulesRequest for rule containing header test and caseSensitive attribute with + * value comparison + * @throws Exception + */ + @Test + public void testGetRuleHeaderTestValueComparisonCaseSensitivity() throws Exception { + try { + String filterScript = "require [\"fileinto\", \"copy\", \"reject\", \"tag\", \"flag\", \"variables\", \"log\", \"enotify\", \"envelope\", \"body\", \"ereject\", \"reject\", \"relational\", \"comparator-i;ascii-numeric\"];\n\n"; + filterScript += "if anyof (header :value \"eq\" :comparator \"i;octet\" [\"subject\"] \"Important\") {\n"; + filterScript += " fileinto \"Junk\";\n"; + filterScript += " stop;\n"; + filterScript += "}\n"; + + ZimbraLog.filter.info(filterScript); + + Account account = Provisioning.getInstance().getAccount( + MockProvisioning.DEFAULT_ACCOUNT_ID); + RuleManager.clearCachedRules(account); + account.setMailSieveScript(filterScript); + + Element request = new Element.XMLElement(MailConstants.GET_FILTER_RULES_REQUEST); + Element response = new GetFilterRules().handle(request, ServiceTestUtil.getRequestContext(account)); + + String expectedSoapResponse = + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + +""; + ZimbraLog.filter.info(response.prettyPrint()); + XMLDiffChecker.assertXMLEquals(expectedSoapResponse, response.prettyPrint()); + } catch (Exception e) { + fail("No exception should be thrown" + e); + } + } + + /** + * Tests GetFilterRulesRequest for rule containing address test and caseSensitive attribute with + * value comparison + * @throws Exception + */ + @Test + public void testGetRuleAddressTestValueComparisonCaseSensitivity() throws Exception { + try { + String filterScript = "require [\"fileinto\", \"copy\", \"reject\", \"tag\", \"flag\", \"variables\", \"log\", \"enotify\", \"envelope\", \"body\", \"ereject\", \"reject\", \"relational\", \"comparator-i;ascii-numeric\"];\n\n"; + filterScript += "if anyof (address :value \"eq\" :all :comparator \"i;octet\" [\"from\"] \"abCD\") {\n"; + filterScript += " fileinto \"Junk\";\n"; + filterScript += " stop;\n"; + filterScript += "}\n"; + + ZimbraLog.filter.info(filterScript); + + Account account = Provisioning.getInstance().getAccount( + MockProvisioning.DEFAULT_ACCOUNT_ID); + RuleManager.clearCachedRules(account); + account.setMailSieveScript(filterScript); + + Element request = new Element.XMLElement(MailConstants.GET_FILTER_RULES_REQUEST); + Element response = new GetFilterRules().handle(request, ServiceTestUtil.getRequestContext(account)); + + String expectedSoapResponse = + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + +""; + ZimbraLog.filter.info(response.prettyPrint()); + XMLDiffChecker.assertXMLEquals(expectedSoapResponse, response.prettyPrint()); + } catch (Exception e) { + fail("No exception should be thrown" + e); + } + } + + /** + * Tests GetFilterRulesRequest for rule containing envelope test and caseSensitive attribute with + * value comparison + * @throws Exception + */ + @Test + public void testGetRuleEnvelopeTestValueComparisonCaseSensitivity() throws Exception { + try { + String filterScript = "require [\"fileinto\", \"copy\", \"reject\", \"tag\", \"flag\", \"variables\", \"log\", \"enotify\", \"envelope\", \"body\", \"ereject\", \"reject\", \"relational\", \"comparator-i;ascii-numeric\"];\n\n"; + filterScript += "if anyof (envelope :value \"eq\" :all :comparator \"i;octet\" [\"from\"] \"abCD\") {\n"; + filterScript += " fileinto \"Junk\";\n"; + filterScript += " stop;\n"; + filterScript += "}\n"; + + ZimbraLog.filter.info(filterScript); + + Account account = Provisioning.getInstance().getAccount( + MockProvisioning.DEFAULT_ACCOUNT_ID); + RuleManager.clearCachedRules(account); + account.setMailSieveScript(filterScript); + + Element request = new Element.XMLElement(MailConstants.GET_FILTER_RULES_REQUEST); + Element response = new GetFilterRules().handle(request, ServiceTestUtil.getRequestContext(account)); + + String expectedSoapResponse = + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + +""; + ZimbraLog.filter.info(response.prettyPrint()); + XMLDiffChecker.assertXMLEquals(expectedSoapResponse, response.prettyPrint()); + } catch (Exception e) { + fail("No exception should be thrown" + e); + } + } +} diff --git a/store/src/java-test/com/zimbra/cs/mailbox/ContactTest.java b/store/src/java-test/com/zimbra/cs/mailbox/ContactTest.java index d3db6ccffef..b9c7eb5a64a 100644 --- a/store/src/java-test/com/zimbra/cs/mailbox/ContactTest.java +++ b/store/src/java-test/com/zimbra/cs/mailbox/ContactTest.java @@ -16,12 +16,17 @@ */ package com.zimbra.cs.mailbox; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.zip.GZIPInputStream; import javax.mail.internet.MimeMessage; import javax.mail.internet.MimePart; @@ -52,6 +57,10 @@ import com.zimbra.cs.mailbox.Contact.Attachment; import com.zimbra.cs.mime.Mime; import com.zimbra.cs.mime.ParsedContact; +import com.zimbra.cs.service.formatter.ArchiveFormatter; +import com.zimbra.cs.service.formatter.ArchiveFormatter.ArchiveInputEntry; +import com.zimbra.cs.service.formatter.ArchiveFormatter.ArchiveInputStream; +import com.zimbra.cs.service.formatter.TarArchiveInputStream; import com.zimbra.cs.service.formatter.VCard; import com.zimbra.cs.service.mail.ToXML; import com.zimbra.cs.service.util.ItemIdFormatter; @@ -293,4 +302,22 @@ public void testEncodeContact() throws Exception { ToXML.encodeContact(response, new ItemIdFormatter(), new OperationContext(acct), contact, true, null); Assert.assertEquals(response.getElement("cn").getElement("a").getText(), "Cert1149638887753217"); } + + @Test + public void testTruncatedContactsTgzImport() throws IOException { + File file = new File("src/java-test/Truncated.tgz"); + InputStream is = new FileInputStream(file); + ArchiveInputStream ais = new TarArchiveInputStream(new GZIPInputStream(is), "UTF-8"); + ArchiveInputEntry aie; + boolean errorCaught = false; + while ((aie = ais.getNextEntry()) != null) { + try { + ArchiveFormatter.readArchiveEntry(ais, aie); + } catch (IOException e) { + errorCaught = true; + break; + } + } + Assert.assertTrue(errorCaught); + } } diff --git a/store/src/java-test/com/zimbra/cs/mailbox/MailboxTest.java b/store/src/java-test/com/zimbra/cs/mailbox/MailboxTest.java index 88521c39f60..3c9a79c8836 100644 --- a/store/src/java-test/com/zimbra/cs/mailbox/MailboxTest.java +++ b/store/src/java-test/com/zimbra/cs/mailbox/MailboxTest.java @@ -580,6 +580,40 @@ public void getVisibleFolders() throws Exception { Mailbox mbox = MailboxManager.getInstance().getMailboxByAccountId(MockProvisioning.DEFAULT_ACCOUNT_ID); mbox.getVisibleFolders(new OperationContext(mbox)); } + + + @Test + public void testLocalMsgReadStatusForForMailForwards() throws Exception { + Provisioning prov = Provisioning.getInstance(); + Map attrs = new HashMap(); + attrs.put(Provisioning.A_zimbraFeatureMailForwardingEnabled, "TRUE"); + attrs.put(Provisioning.A_zimbraPrefMailForwardingAddress, "user@zimbra.com"); + attrs.put(Provisioning.A_zimbraFeatureMarkMailForwardedAsRead, "TRUE"); + Account acct = prov.createAccount("user@zimbra.com", "secret", attrs); + Mailbox mbox = MailboxManager.getInstance().getMailboxByAccountId(acct.getId()); + + DeliveryOptions dopt = new DeliveryOptions().setFolderId(Mailbox.ID_FOLDER_INBOX); + dopt.setFlags(Flag.BITMASK_UNREAD); + Message message = mbox.addMessage(null, new ParsedMessage("From: test1-1@sub1.zimbra.com".getBytes(), false), dopt, null); + Assert.assertEquals(false, message.isUnread()); + } + + @Test + public void testLocalMsgReadStatusForForMailForwardsWhenMarkAsReadIsFalse() throws Exception { + Provisioning prov = Provisioning.getInstance(); + Map attrs = new HashMap(); + attrs.put(Provisioning.A_zimbraFeatureMailForwardingEnabled, "TRUE"); + attrs.put(Provisioning.A_zimbraPrefMailForwardingAddress, "user2@zimbra.com"); + attrs.put(Provisioning.A_zimbraFeatureMarkMailForwardedAsRead, "FALSE"); + Account acct = prov.createAccount("user@zimbra.com", "secret", attrs); + Mailbox mbox = MailboxManager.getInstance().getMailboxByAccountId(acct.getId()); + + DeliveryOptions dopt = new DeliveryOptions().setFolderId(Mailbox.ID_FOLDER_INBOX); + dopt.setFlags(Flag.BITMASK_UNREAD); + Message message = mbox.addMessage(null, new ParsedMessage("From: test1-1@sub1.zimbra.com".getBytes(), false), dopt, null); + Assert.assertEquals(true, message.isUnread()); + + } /** * @throws java.lang.Exception diff --git a/store/src/java-test/com/zimbra/cs/mailbox/MailboxTestUtil.java b/store/src/java-test/com/zimbra/cs/mailbox/MailboxTestUtil.java index 7d6cc2ec005..498972928a6 100644 --- a/store/src/java-test/com/zimbra/cs/mailbox/MailboxTestUtil.java +++ b/store/src/java-test/com/zimbra/cs/mailbox/MailboxTestUtil.java @@ -91,16 +91,23 @@ public static void initProvisioning(String zimbraServerDir) throws Exception { System.setProperty("log4j.configuration", "log4j-test.properties"); System.setProperty("zimbra.config", zimbraServerDir + "src/java-test/localconfig-test.xml"); LC.reload(); - + //substitute test TZ file String timezonefilePath = zimbraServerDir + "src/java-test/timezones-test.ics"; File d = new File(timezonefilePath); if (!d.exists()) { - throw new FileNotFoundException("timezones.ics not found."); + throw new FileNotFoundException("timezones-test.ics not found in " + timezonefilePath); } LC.timezone_file.setDefault(timezonefilePath); LC.zimbra_rights_directory.setDefault(StringUtils.removeEnd(zimbraServerDir, "/") +"-conf" + "/conf/rights"); LC.zimbra_attrs_directory.setDefault(zimbraServerDir + "conf/attrs"); LC.zimbra_tmp_directory.setDefault(zimbraServerDir + "tmp"); + //substitute test DS config file + String dsfilePath = zimbraServerDir + "src/java-test/datasource-test.xml"; + d = new File(dsfilePath); + if (!d.exists()) { + throw new FileNotFoundException("datasource-test.xml not found in " + dsfilePath); + } + LC.data_source_config.setDefault(dsfilePath); // default MIME handlers are now set up in MockProvisioning constructor Provisioning.setInstance(new MockProvisioning()); } diff --git a/store/src/java-test/com/zimbra/cs/service/ExternalUserProvServletTest.java b/store/src/java-test/com/zimbra/cs/service/ExternalUserProvServletTest.java new file mode 100644 index 00000000000..9cc1f62d5d2 --- /dev/null +++ b/store/src/java-test/com/zimbra/cs/service/ExternalUserProvServletTest.java @@ -0,0 +1,77 @@ +/* + * ***** BEGIN LICENSE BLOCK ***** + * Zimbra Collaboration Suite Server + * Copyright (C) 2017 Synacor, Inc. + * + * This program is free software: you can redistribute it and/or modify it under + * the terms of the GNU General Public License as published by the Free Software Foundation, + * version 2 of the License. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * You should have received a copy of the GNU General Public License along with this program. + * If not, see . + * ***** END LICENSE BLOCK ***** + */ +package com.zimbra.cs.service; + +import java.util.HashMap; +import java.util.Map; + +import javax.servlet.http.HttpServletRequest; + +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; + +import com.google.common.collect.Maps; +import com.zimbra.common.account.Key; +import com.zimbra.common.account.ZAttrProvisioning.FeatureAddressVerificationStatus; +import com.zimbra.cs.account.Account; +import com.zimbra.cs.account.Provisioning; +import com.zimbra.cs.mailbox.MailboxTestUtil; + +import junit.framework.Assert; + +public class ExternalUserProvServletTest { + + @BeforeClass + public static void init() throws Exception { + MailboxTestUtil.initServer(); + Provisioning prov = Provisioning.getInstance(); + + Map attrs = Maps.newHashMap(); + attrs = Maps.newHashMap(); + prov.createAccount("test@zimbra.com", "secret", attrs); + } + + @Before + public void setUp() throws Exception { + MailboxTestUtil.clearData(); + } + + @Test + public void testHandleAddressVerificationExpired() throws Exception { + Account acct1 = Provisioning.getInstance().get(Key.AccountBy.name, "test@zimbra.com"); + HashMap headers = new HashMap(); + HttpServletRequest req = new MockHttpServletRequest(null, null, null, 123, "127.0.0.1", headers); + MockHttpServletResponse resp = new MockHttpServletResponse(); + ExternalUserProvServlet servlet = new ExternalUserProvServlet(); + servlet.handleAddressVerification(req, resp, acct1.getId(), "test2@zimbra.com", true); + Assert.assertNull(acct1.getPrefMailForwardingAddress()); + Assert.assertEquals(FeatureAddressVerificationStatus.expired, acct1.getFeatureAddressVerificationStatus()); + } + + @Test + public void testHandleAddressVerificationSuccess() throws Exception { + Account acct1 = Provisioning.getInstance().get(Key.AccountBy.name, "test@zimbra.com"); + HashMap headers = new HashMap(); + HttpServletRequest req = new MockHttpServletRequest(null, null, null, 123, "127.0.0.1", headers); + MockHttpServletResponse resp = new MockHttpServletResponse(); + ExternalUserProvServlet servlet = new ExternalUserProvServlet(); + servlet.handleAddressVerification(req, resp, acct1.getId(), "test2@zimbra.com", false); + Assert.assertEquals("test2@zimbra.com", acct1.getPrefMailForwardingAddress()); + Assert.assertEquals(FeatureAddressVerificationStatus.verified, acct1.getFeatureAddressVerificationStatus()); + } +} diff --git a/store/src/java-test/com/zimbra/cs/service/ModifyPrefsTest.java b/store/src/java-test/com/zimbra/cs/service/ModifyPrefsTest.java new file mode 100644 index 00000000000..e5111e53379 --- /dev/null +++ b/store/src/java-test/com/zimbra/cs/service/ModifyPrefsTest.java @@ -0,0 +1,137 @@ +/* + * ***** BEGIN LICENSE BLOCK ***** + * Zimbra Collaboration Suite Server + * Copyright (C) 2017 Synacor, Inc. + * + * This program is free software: you can redistribute it and/or modify it under + * the terms of the GNU General Public License as published by the Free Software Foundation, + * version 2 of the License. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * You should have received a copy of the GNU General Public License along with this program. + * If not, see . + * ***** END LICENSE BLOCK ***** + */ + +package com.zimbra.cs.service; + +import java.io.IOException; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.Map; + +import javax.mail.Address; +import javax.mail.internet.MimeMessage; + +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; + +import com.google.common.collect.Maps; +import com.zimbra.common.account.Key; +import com.zimbra.common.account.ZAttrProvisioning.FeatureAddressVerificationStatus; +import com.zimbra.common.soap.Element; +import com.zimbra.common.util.L10nUtil; +import com.zimbra.cs.account.Account; +import com.zimbra.cs.account.Provisioning; +import com.zimbra.cs.mailbox.MailSender; +import com.zimbra.cs.mailbox.Mailbox; +import com.zimbra.cs.mailbox.Mailbox.MailboxData; +import com.zimbra.cs.mailbox.MailboxManager; +import com.zimbra.cs.mailbox.MailboxTestUtil; +import com.zimbra.cs.service.account.ModifyPrefs; +import com.zimbra.cs.service.mail.ServiceTestUtil; +import com.zimbra.soap.JaxbUtil; +import com.zimbra.soap.account.message.ModifyPrefsRequest; +import com.zimbra.soap.account.type.Pref; + +import junit.framework.Assert; + +public class ModifyPrefsTest { + + public static String zimbraServerDir = ""; + + @BeforeClass + public static void init() throws Exception { + MailboxTestUtil.initServer(); + Provisioning prov = Provisioning.getInstance(); + + Map attrs = Maps.newHashMap(); + prov.createDomain("zimbra.com", attrs); + + attrs = Maps.newHashMap(); + prov.createAccount("test@zimbra.com", "secret", attrs); + + MailboxManager.setInstance(new MailboxManager() { + + @Override + protected Mailbox instantiateMailbox(MailboxData data) { + return new Mailbox(data) { + + @Override + public MailSender getMailSender() { + return new MailSender() { + + @Override + protected Collection
sendMessage(Mailbox mbox, MimeMessage mm, + Collection rollbacks) + throws SafeMessagingException, IOException { + try { + return Arrays.asList(getRecipients(mm)); + } catch (Exception e) { + return Collections.emptyList(); + } + } + }; + } + }; + } + }); + + L10nUtil.setMsgClassLoader("../store-conf/conf/msgs"); + } + + @Before + public void setUp() throws Exception { + MailboxTestUtil.clearData(); + } + + @Test + public void testMsgMaxAttr() throws Exception { + Account acct1 = Provisioning.getInstance().get(Key.AccountBy.name, "test@zimbra.com"); + Mailbox mbox = MailboxManager.getInstance().getMailboxByAccount(acct1); + acct1.setFeatureMailForwardingEnabled(true); + acct1.setFeatureAddressVerificationEnabled(true); + Assert.assertNull(acct1.getPrefMailForwardingAddress()); + Assert.assertNull(acct1.getFeatureAddressUnderVerification()); + ModifyPrefsRequest request = new ModifyPrefsRequest(); + Pref pref = new Pref(Provisioning.A_zimbraPrefMailForwardingAddress, + "test1@somedomain.com"); + request.addPref(pref); + Element req = JaxbUtil.jaxbToElement(request); + new ModifyPrefs().handle(req, ServiceTestUtil.getRequestContext(mbox.getAccount())); + /* + * Verify that the forwarding address is not directly stored into + * 'zimbraPrefMailForwardingAddress' Instead, it is stored in + * 'zimbraFeatureAddressUnderVerification' till the time it + * gets verification + */ + Assert.assertNull(acct1.getPrefMailForwardingAddress()); + Assert.assertEquals("test1@somedomain.com", + acct1.getFeatureAddressUnderVerification()); + /* + * disable the verification feature and check that the forwarding + * address is directly stored into 'zimbraPrefMailForwardingAddress' + */ + acct1.setPrefMailForwardingAddress(null); + acct1.setFeatureAddressUnderVerification(null); + acct1.setFeatureAddressVerificationEnabled(false); + new ModifyPrefs().handle(req, ServiceTestUtil.getRequestContext(mbox.getAccount())); + Assert.assertNull(acct1.getFeatureAddressUnderVerification()); + Assert.assertEquals("test1@somedomain.com", acct1.getPrefMailForwardingAddress()); + Assert.assertEquals(FeatureAddressVerificationStatus.pending, acct1.getFeatureAddressVerificationStatus()); + } +} \ No newline at end of file diff --git a/store/src/java-test/com/zimbra/cs/service/admin/ModifyFilterRulesAdminTest.java b/store/src/java-test/com/zimbra/cs/service/admin/ModifyFilterRulesAdminTest.java index d214d30beba..8b265f7746e 100644 --- a/store/src/java-test/com/zimbra/cs/service/admin/ModifyFilterRulesAdminTest.java +++ b/store/src/java-test/com/zimbra/cs/service/admin/ModifyFilterRulesAdminTest.java @@ -19,6 +19,7 @@ import com.zimbra.cs.filter.RuleManager; import com.zimbra.cs.filter.RuleManager.AdminFilterType; import com.zimbra.cs.filter.RuleManager.FilterType; +import com.zimbra.cs.filter.SoapToSieve; import com.zimbra.cs.mailbox.MailboxTestUtil; import com.zimbra.cs.service.admin.AdminDocumentHandler.AccountHarvestingCheckerBase; import com.zimbra.soap.ZimbraSoapContext; @@ -62,7 +63,7 @@ public void testSoapToSieveAddheaderActionWithoutLast() throws ServiceException, RuleManager.clearCachedRules(account); RuleManager.setAdminRulesFromXML(account, filterRules, FilterType.INCOMING, AdminFilterType.BEFORE); - String script = "require [\"fileinto\", \"copy\", \"reject\", \"tag\", \"flag\", \"variables\", \"log\", \"enotify\", \"editheader\"];\n\n" + String script = "require [" + SoapToSieve.requireCommon + ", \"editheader\"];\n\n" + "# rule1\n" + "addheader \"X-New-Header\" \"Test vallue\";\n"; @@ -80,7 +81,7 @@ public void testSoapToSieveAddheaderActionWithLast() throws ServiceException, Ex RuleManager.clearCachedRules(account); RuleManager.setAdminRulesFromXML(account, filterRules, FilterType.INCOMING, AdminFilterType.BEFORE); - String script = "require [\"fileinto\", \"copy\", \"reject\", \"tag\", \"flag\", \"variables\", \"log\", \"enotify\", \"editheader\"];\n\n" + String script = "require [" + SoapToSieve.requireCommon + ", \"editheader\"];\n\n" + "# rule1\n" + "addheader :last \"X-New-Header\" \"Test vallue\";\n"; @@ -100,7 +101,7 @@ public void testSoapToSieveDeleteheaderActionBasic() throws ServiceException, Ex RuleManager.clearCachedRules(account); RuleManager.setAdminRulesFromXML(account, filterRules, FilterType.INCOMING, AdminFilterType.BEFORE); - String script = "require [\"fileinto\", \"copy\", \"reject\", \"tag\", \"flag\", \"variables\", \"log\", \"enotify\", \"editheader\"];\n\n" + String script = "require [" + SoapToSieve.requireCommon + ", \"editheader\"];\n\n" + "# rule1\n" + "deleteheader \"X-Test-Header\";\n"; @@ -123,7 +124,7 @@ public void testSoapToSieveDeleteheaderAction2() throws ServiceException, Except RuleManager.clearCachedRules(account); RuleManager.setAdminRulesFromXML(account, filterRules, FilterType.INCOMING, AdminFilterType.BEFORE); - String script = "require [\"fileinto\", \"copy\", \"reject\", \"tag\", \"flag\", \"variables\", \"log\", \"enotify\", \"editheader\"];\n\n" + String script = "require [" + SoapToSieve.requireCommon + ", \"editheader\"];\n\n" + "# rule1\n" + "deleteheader :comparator \"i;ascii-casemap\" :is \"X-Test-Header\" \"Test value\";\n"; @@ -148,7 +149,7 @@ public void testSoapToSieveDeleteheaderAction3() throws ServiceException, Except RuleManager.clearCachedRules(account); RuleManager.setAdminRulesFromXML(account, filterRules, FilterType.INCOMING, AdminFilterType.BEFORE); - String script = "require [\"fileinto\", \"copy\", \"reject\", \"tag\", \"flag\", \"variables\", \"log\", \"enotify\", \"editheader\"];\n\n" + String script = "require [" + SoapToSieve.requireCommon + ", \"editheader\"];\n\n" + "# rule1\n" + "deleteheader :comparator \"i;ascii-casemap\" :is \"X-Test-Header\" [ \"Value1\", \"Value2\", \"Value3\" ];\n"; @@ -172,7 +173,7 @@ public void testSoapToSieveDeleteheaderAction4() throws ServiceException, Except RuleManager.clearCachedRules(account); RuleManager.setAdminRulesFromXML(account, filterRules, FilterType.INCOMING, AdminFilterType.BEFORE); - String script = "require [\"fileinto\", \"copy\", \"reject\", \"tag\", \"flag\", \"variables\", \"log\", \"enotify\", \"editheader\"];\n\n" + String script = "require [" + SoapToSieve.requireCommon + ", \"editheader\"];\n\n" + "# rule1\n" + "deleteheader :comparator \"i;octet\" :contains \"X-Test-Header\" [ \"Value1\", \"Value2\", \"Value3\" ];\n"; @@ -194,7 +195,7 @@ public void testSoapToSieveDeleteheaderAction5() throws ServiceException, Except RuleManager.clearCachedRules(account); RuleManager.setAdminRulesFromXML(account, filterRules, FilterType.INCOMING, AdminFilterType.BEFORE); - String script = "require [\"fileinto\", \"copy\", \"reject\", \"tag\", \"flag\", \"variables\", \"log\", \"enotify\", \"editheader\"];\n\n" + String script = "require [" + SoapToSieve.requireCommon + ", \"editheader\"];\n\n" + "# rule1\n" + "deleteheader :value \"ge\" :comparator \"i;ascii-numeric\" \"X-Test-Header\" \"2\";\n"; @@ -216,7 +217,7 @@ public void testSoapToSieveDeleteheaderAction6() throws ServiceException, Except RuleManager.clearCachedRules(account); RuleManager.setAdminRulesFromXML(account, filterRules, FilterType.INCOMING, AdminFilterType.BEFORE); - String script = "require [\"fileinto\", \"copy\", \"reject\", \"tag\", \"flag\", \"variables\", \"log\", \"enotify\", \"editheader\"];\n\n" + String script = "require [" + SoapToSieve.requireCommon + ", \"editheader\"];\n\n" + "# rule1\n" + "deleteheader :value \"ge\" :comparator \"i;ascii-numeric\" \"X-Test-Header\" \"2\";\n"; @@ -238,33 +239,13 @@ public void testSoapToSieveDeleteheaderAction7() throws ServiceException, Except RuleManager.clearCachedRules(account); RuleManager.setAdminRulesFromXML(account, filterRules, FilterType.INCOMING, AdminFilterType.BEFORE); - String script = "require [\"fileinto\", \"copy\", \"reject\", \"tag\", \"flag\", \"variables\", \"log\", \"enotify\", \"editheader\"];\n\n" + String script = "require [" + SoapToSieve.requireCommon + ", \"editheader\"];\n\n" + "# rule1\n" + "deleteheader :count \"ge\" :comparator \"i;ascii-numeric\" \"X-Test-Header\" \"2\";\n"; Assert.assertEquals(script, account.getAdminSieveScriptBefore()); } - // negative test case - @Test - public void testNegativeSoapToSieveDeleteheaderAction() throws ServiceException, Exception { - List values = new ArrayList(); - values.add("2"); - DeleteheaderAction action = new DeleteheaderAction(null, null, null); - FilterRule filterRule = new FilterRule("rule1", true); - filterRule.addFilterAction(action); - List filterRules = new ArrayList(); - filterRules.add(filterRule); - - RuleManager.clearCachedRules(account); - try { - RuleManager.setAdminRulesFromXML(account, filterRules, FilterType.INCOMING, AdminFilterType.BEFORE); - } catch (ServiceException se) { - Assert.assertEquals("service.PARSE_ERROR", se.getCode()); - Assert.assertTrue(se.getMessage().contains(" is mandatory in action")); - } - } - /******************replaceheader*********************/ @Test public void testSoapToSieveReplaceheaderActionBasic() throws ServiceException, Exception { @@ -278,7 +259,7 @@ public void testSoapToSieveReplaceheaderActionBasic() throws ServiceException, E RuleManager.clearCachedRules(account); RuleManager.setAdminRulesFromXML(account, filterRules, FilterType.INCOMING, AdminFilterType.BEFORE); - String script = "require [\"fileinto\", \"copy\", \"reject\", \"tag\", \"flag\", \"variables\", \"log\", \"enotify\", \"editheader\"];\n\n" + String script = "require [" + SoapToSieve.requireCommon + ", \"editheader\"];\n\n" + "# rule1\n" + "replaceheader :newvalue \"[test] ${1}\" \"X-Test-Header\";\n"; @@ -301,7 +282,7 @@ public void testSoapToSieveReplaceheaderAction2() throws ServiceException, Excep RuleManager.clearCachedRules(account); RuleManager.setAdminRulesFromXML(account, filterRules, FilterType.INCOMING, AdminFilterType.BEFORE); - String script = "require [\"fileinto\", \"copy\", \"reject\", \"tag\", \"flag\", \"variables\", \"log\", \"enotify\", \"editheader\"];\n\n" + String script ="require [" + SoapToSieve.requireCommon + ", \"editheader\"];\n\n" + "# rule1\n" + "replaceheader :newvalue \"[test] ${1}\" :comparator \"i;ascii-casemap\" :is \"X-Test-Header\" \"Test value\";\n"; @@ -326,7 +307,7 @@ public void testSoapToSieveReplaceheaderAction3() throws ServiceException, Excep RuleManager.clearCachedRules(account); RuleManager.setAdminRulesFromXML(account, filterRules, FilterType.INCOMING, AdminFilterType.BEFORE); - String script = "require [\"fileinto\", \"copy\", \"reject\", \"tag\", \"flag\", \"variables\", \"log\", \"enotify\", \"editheader\"];\n\n" + String script = "require [" + SoapToSieve.requireCommon + ", \"editheader\"];\n\n" + "# rule1\n" + "replaceheader :newvalue \"[test] ${1}\" :comparator \"i;ascii-casemap\" :is \"X-Test-Header\" [ \"Value1\", \"Value2\", \"Value3\" ];\n"; @@ -350,7 +331,7 @@ public void testSoapToSieveReplaceheaderAction4() throws ServiceException, Excep RuleManager.clearCachedRules(account); RuleManager.setAdminRulesFromXML(account, filterRules, FilterType.INCOMING, AdminFilterType.BEFORE); - String script = "require [\"fileinto\", \"copy\", \"reject\", \"tag\", \"flag\", \"variables\", \"log\", \"enotify\", \"editheader\"];\n\n" + String script = "require [" + SoapToSieve.requireCommon + ", \"editheader\"];\n\n" + "# rule1\n" + "replaceheader :newvalue \"[test] ${1}\" :comparator \"i;octet\" :contains \"X-Test-Header\" [ \"Value1\", \"Value2\", \"Value3\" ];\n"; @@ -372,7 +353,7 @@ public void testSoapToSieveReplaceheaderAction5() throws ServiceException, Excep RuleManager.clearCachedRules(account); RuleManager.setAdminRulesFromXML(account, filterRules, FilterType.INCOMING, AdminFilterType.BEFORE); - String script = "require [\"fileinto\", \"copy\", \"reject\", \"tag\", \"flag\", \"variables\", \"log\", \"enotify\", \"editheader\"];\n\n" + String script = "require [" + SoapToSieve.requireCommon + ", \"editheader\"];\n\n" + "# rule1\n" + "replaceheader :newvalue \"[test] ${1}\" :value \"ge\" :comparator \"i;ascii-numeric\" \"X-Test-Header\" \"2\";\n"; @@ -394,7 +375,7 @@ public void testSoapToSieveReplaceheaderAction6() throws ServiceException, Excep RuleManager.clearCachedRules(account); RuleManager.setAdminRulesFromXML(account, filterRules, FilterType.INCOMING, AdminFilterType.BEFORE); - String script = "require [\"fileinto\", \"copy\", \"reject\", \"tag\", \"flag\", \"variables\", \"log\", \"enotify\", \"editheader\"];\n\n" + String script = "require [" + SoapToSieve.requireCommon + ", \"editheader\"];\n\n" + "# rule1\n" + "replaceheader :newvalue \"[test] ${1}\" :value \"ge\" :comparator \"i;ascii-numeric\" \"X-Test-Header\" \"2\";\n"; @@ -416,10 +397,10 @@ public void testSoapToSieveReplaceheaderAction7() throws ServiceException, Excep RuleManager.clearCachedRules(account); RuleManager.setAdminRulesFromXML(account, filterRules, FilterType.INCOMING, AdminFilterType.BEFORE); - String script = "require [\"fileinto\", \"copy\", \"reject\", \"tag\", \"flag\", \"variables\", \"log\", \"enotify\", \"editheader\"];\n\n" + String script = "require [" + SoapToSieve.requireCommon + ", \"editheader\"];\n\n" + "# rule1\n" + "replaceheader :newname \"X-Test-Header-New\" :newvalue \"[test] ${1}\" :count \"ge\" :comparator \"i;ascii-numeric\" \"X-Test-Header\" \"2\";\n"; Assert.assertEquals(script, account.getAdminSieveScriptBefore()); } -} \ No newline at end of file +} diff --git a/store/src/java-test/com/zimbra/cs/service/mail/GetContactBackupListTest.java b/store/src/java-test/com/zimbra/cs/service/mail/GetContactBackupListTest.java new file mode 100644 index 00000000000..3847c213b03 --- /dev/null +++ b/store/src/java-test/com/zimbra/cs/service/mail/GetContactBackupListTest.java @@ -0,0 +1,110 @@ +/* + * ***** BEGIN LICENSE BLOCK ***** Zimbra Collaboration Suite Server Copyright + * (C) 2017 Synacor, Inc. + * + * This program is free software: you can redistribute it and/or modify it under + * the terms of the GNU General Public License as published by the Free Software + * Foundation, version 2 of the License. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. You should have received a copy of the GNU General Public License + * along with this program. If not, see . ***** + * END LICENSE BLOCK ***** + */ +package com.zimbra.cs.service.mail; + +import java.net.URL; +import java.util.HashMap; +import java.util.Map; + +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; + +import com.google.common.collect.Maps; +import com.zimbra.common.account.Key; +import com.zimbra.common.soap.Element; +import com.zimbra.common.soap.MailConstants; +import com.zimbra.common.soap.SoapProtocol; +import com.zimbra.cs.account.Account; +import com.zimbra.cs.account.Provisioning; +import com.zimbra.cs.mailbox.MailboxTestUtil; +import com.zimbra.cs.service.AuthProvider; +import com.zimbra.cs.service.MockHttpServletRequest; +import com.zimbra.soap.MockSoapEngine; +import com.zimbra.soap.SoapEngine; +import com.zimbra.soap.SoapServlet; +import com.zimbra.soap.ZimbraSoapContext; + +import junit.framework.Assert; + +public class GetContactBackupListTest { + @BeforeClass + public static void init() throws Exception { + MailboxTestUtil.initServer(); + Provisioning prov = Provisioning.getInstance(); + Map attrs = Maps.newHashMap(); + prov.createAccount("test@zimbra.com", "secret", attrs); + } + + @Before + public void setUp() throws Exception { + MailboxTestUtil.clearData(); + } + + @Test + public void testGetContactBackupListXML() throws Exception { + Account acct = Provisioning.getInstance().get(Key.AccountBy.name, "test@zimbra.com"); + + Element request = new Element.XMLElement(MailConstants.E_GET_CONTACT_BACKUP_LIST_REQUEST); + Element response = new GetContactBackupList().handle(request, ServiceTestUtil.getRequestContext(acct)); + + String expectedResponse = "\n" + + " \n" + + " file1.tgz\n" + + " file2.tgz\n" + + " file3.tgz\n" + + " file4.tgz\n" + + " \n" + + ""; + + Assert.assertEquals("GetContactBackupListResponse is not as expected", expectedResponse, response.prettyPrint()); + } + + @Test + public void testGetContactBackupListJSON() throws Exception { + Account acct = Provisioning.getInstance().get(Key.AccountBy.name, "test@zimbra.com"); + + Map context = new HashMap(); + context.put(SoapEngine.ZIMBRA_CONTEXT, new ZimbraSoapContext(AuthProvider.getAuthToken(acct), acct.getId(), SoapProtocol.Soap12, SoapProtocol.SoapJS)); + context.put(SoapServlet.SERVLET_REQUEST, new MockHttpServletRequest("test".getBytes("UTF-8"), new URL("http://localhost:7070/service/FooRequest"), "")); + context.put(SoapEngine.ZIMBRA_ENGINE, new MockSoapEngine(new MailService())); + + + Element request = new Element.JSONElement(MailConstants.E_GET_CONTACT_BACKUP_LIST_REQUEST); + Element response = new GetContactBackupList().handle(request, context); + + String expectedResponse = "{\n" + + " \"backups\": [{\n" + + " \"backup\": [\n" + + " {\n" + + " \"_content\": \"file1.tgz\"\n" + + " },\n" + + " {\n" + + " \"_content\": \"file2.tgz\"\n" + + " },\n" + + " {\n" + + " \"_content\": \"file3.tgz\"\n" + + " },\n" + + " {\n" + + " \"_content\": \"file4.tgz\"\n" + + " }]\n" + + " }],\n" + + " \"_jsns\": \"urn:zimbraMail\"\n" + + "}"; + + Assert.assertEquals("GetContactBackupListResponse is not as expected", expectedResponse, response.prettyPrint()); + } +} diff --git a/store/src/java-test/com/zimbra/cs/service/mail/ModifyFilterRulesTest.java b/store/src/java-test/com/zimbra/cs/service/mail/ModifyFilterRulesTest.java index a34472448e9..149b9b570b8 100644 --- a/store/src/java-test/com/zimbra/cs/service/mail/ModifyFilterRulesTest.java +++ b/store/src/java-test/com/zimbra/cs/service/mail/ModifyFilterRulesTest.java @@ -40,6 +40,7 @@ import com.zimbra.cs.account.MockProvisioning; import com.zimbra.cs.account.Provisioning; import com.zimbra.cs.filter.RuleManager; +import com.zimbra.cs.filter.SoapToSieve; import com.zimbra.cs.mailbox.MailboxTestUtil; import com.zimbra.soap.mail.type.FilterAction; import com.zimbra.soap.mail.type.FilterRule; @@ -126,7 +127,7 @@ public void testBug71036_NonNestedIfSingleRule() throws Exception { fail("This test is expected not to throw exception. "); } - String expectedScript = "require [\"fileinto\", \"copy\", \"reject\", \"tag\", \"flag\", \"variables\", \"log\", \"enotify\"];\n\n"; + String expectedScript = "require [" + SoapToSieve.requireCommon + "];\n\n"; expectedScript += "# Test1\n"; expectedScript += "if anyof (header :contains [\"subject\"] \"important\") {\n"; expectedScript += " fileinto \"Junk\";\n"; @@ -186,7 +187,7 @@ public void testBug71036_MultiNestedIfSingleRule() throws Exception { fail("This test is expected not to throw exception. "); } - String expectedScript = "require [\"fileinto\", \"copy\", \"reject\", \"tag\", \"flag\", \"variables\", \"log\", \"enotify\"];\n\n"; + String expectedScript = "require [" + SoapToSieve.requireCommon + "];\n\n"; expectedScript += "# Test1\n"; expectedScript += "if anyof (header :contains [\"subject\"] \"important\") {\n"; expectedScript += " if anyof (header :is [\"subject\"] \"confifential\") {\n"; @@ -281,7 +282,7 @@ public void testBug71036_NestedIfMultiRulesWithMultiConditions() throws Exceptio fail("This test is expected not to throw exception. "); } - String expectedScript = "require [\"fileinto\", \"copy\", \"reject\", \"tag\", \"flag\", \"variables\", \"log\", \"enotify\"];\n\n"; + String expectedScript = "require [" + SoapToSieve.requireCommon + "];\n\n"; expectedScript += "# Test1\n"; expectedScript += "if anyof (header :contains [\"Subject\"] \"important\") {\n"; expectedScript += " if allof (header :is [\"Subject\"] \"confifential\",\n"; @@ -504,7 +505,7 @@ public void testFilterVariables() { Element request = Element.parseXML(xml); new ModifyFilterRules().handle(request, ServiceTestUtil.getRequestContext(account)); - String expectedScript = "require [\"fileinto\", \"copy\", \"reject\", \"tag\", \"flag\", \"variables\", \"log\", \"enotify\"];\n" + + String expectedScript = "require [" + SoapToSieve.requireCommon + "];\n" + "\n" + "# t60\n" + "set \"var\" \"testTag\";\n" + @@ -566,7 +567,7 @@ public void testFilterVariablesForMatchVariables() { Element request = Element.parseXML(xml); new ModifyFilterRules().handle(request, ServiceTestUtil.getRequestContext(account)); - String expectedScript = "require [\"fileinto\", \"copy\", \"reject\", \"tag\", \"flag\", \"variables\", \"log\", \"enotify\"];\n" + + String expectedScript = "require [" + SoapToSieve.requireCommon + "];\n" + "\n" + "# t60\n" + "set \"var\" \"testTag\";\n" + @@ -697,7 +698,7 @@ public void testZCS1173SingleIfNoAllofAnyofRule() throws Exception { fail("This test is expected not to throw exception. " + e); } - String expectedScript = "require [\"fileinto\", \"copy\", \"reject\", \"tag\", \"flag\", \"variables\", \"log\", \"enotify\"];\n\n"; + String expectedScript = "require [" + SoapToSieve.requireCommon + "];\n\n"; expectedScript += "# null\n"; expectedScript += "if allof (header :contains [\"subject\"] \"123\",\n"; expectedScript += " header :contains [\"X-Header\"] \"456\") {\n"; diff --git a/store/src/java-test/com/zimbra/cs/service/mail/RestoreContactsTest.java b/store/src/java-test/com/zimbra/cs/service/mail/RestoreContactsTest.java new file mode 100644 index 00000000000..0c73c2b0727 --- /dev/null +++ b/store/src/java-test/com/zimbra/cs/service/mail/RestoreContactsTest.java @@ -0,0 +1,112 @@ +/* + * ***** BEGIN LICENSE BLOCK ***** + * Zimbra Collaboration Suite Server + * Copyright (C) 2017 Synacor, Inc. + * + * This program is free software: you can redistribute it and/or modify it under + * the terms of the GNU General Public License as published by the Free Software Foundation, + * version 2 of the License. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * You should have received a copy of the GNU General Public License along with this program. + * If not, see . + * ***** END LICENSE BLOCK ***** + */ +package com.zimbra.cs.service.mail; + +import java.io.ByteArrayInputStream; +import java.util.Map; + +import org.apache.http.StatusLine; +import org.apache.http.HttpResponse; +import org.junit.Assert; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mockito; +import org.powermock.api.mockito.PowerMockito; +import org.powermock.core.classloader.annotations.PowerMockIgnore; +import org.powermock.core.classloader.annotations.PrepareForTest; +import org.powermock.modules.junit4.PowerMockRunner; + +import com.google.common.collect.Maps; +import com.zimbra.common.account.Key; +import com.zimbra.common.mime.MimeConstants; +import com.zimbra.common.service.ServiceException; +import com.zimbra.common.soap.Element; +import com.zimbra.common.soap.MailConstants; +import com.zimbra.cs.account.Account; +import com.zimbra.cs.account.Provisioning; +import com.zimbra.cs.mailbox.Folder; +import com.zimbra.cs.mailbox.MailItem; +import com.zimbra.cs.mailbox.Mailbox; +import com.zimbra.cs.mailbox.MailboxManager; +import com.zimbra.cs.mailbox.MailboxTestUtil; +import com.zimbra.cs.mailbox.OperationContext; +import com.zimbra.cs.store.file.FileBlobStore; + +@RunWith(PowerMockRunner.class) +@PrepareForTest({ RestoreContacts.class, HttpResponse.class, StatusLine.class, + FileBlobStore.class }) +@PowerMockIgnore({ "javax.crypto.*", "javax.xml.bind.annotation.*" }) +public class RestoreContactsTest { + + private Account acct = null; + + @BeforeClass + public static void init() throws Exception { + MailboxTestUtil.initServer(); + Provisioning prov = Provisioning.getInstance(); + + Map attrs = Maps.newHashMap(); + prov.createAccount("test@zimbra.com", "secret", attrs); + } + + @Before + public void setUp() throws Exception { + MailboxTestUtil.clearData(); + acct = Provisioning.getInstance().get(Key.AccountBy.name, "test@zimbra.com"); + } + + @Test + public void testRestore() throws Exception { + Mailbox mbox = MailboxManager.getInstance().getMailboxByAccount(acct); + Folder folder = mbox.createFolder(null, "Briefcase/ContactsBackup", + new Folder.FolderOptions().setDefaultView(MailItem.Type.DOCUMENT)); + OperationContext octxt = new OperationContext(acct); + // Upload the contacts backup file to ContactsBackup folder in briefcase + mbox.createDocument(octxt, folder.getId(), "backup_dummy_test.tgz", + MimeConstants.CT_APPLICATION_ZIMBRA_DOC, "author", "description", + new ByteArrayInputStream("dummy data".getBytes())); + HttpResponse httpResponse = PowerMockito.mock(HttpResponse.class); + StatusLine httpStatusLine = PowerMockito.mock(StatusLine.class); + Mockito.when(httpStatusLine.getStatusCode()).thenReturn(200); + Mockito.when(httpResponse.getStatusLine()).thenReturn(httpStatusLine); + PowerMockito.stub(PowerMockito.method(RestoreContacts.class, "httpPostBackup")) + .toReturn(httpResponse); + PowerMockito.stub(PowerMockito.method(FileBlobStore.class, "getBlobPath", Mailbox.class, + int.class, int.class, short.class)).toReturn("/"); + // RestoreContactRequest with valid backup file name + Element request = new Element.XMLElement(MailConstants.RESTORE_CONTACTS_REQUEST); + request.addAttribute("contactsBackupFileName", "backup_dummy_test.tgz"); + Map context = ServiceTestUtil.getRequestContext(acct); + Element response = new RestoreContacts().handle(request, context); + String expectedResponse = ""; + Assert.assertEquals(expectedResponse, response.prettyPrint()); + try { + // RestoreContactRequest with non-existing backup file name + Element request2 = new Element.XMLElement(MailConstants.RESTORE_CONTACTS_REQUEST); + request2.addAttribute("contactsBackupFileName", "backup_dummy_test_non_existing.tgz"); + new RestoreContacts().handle(request2, context); + Assert.fail("ServiceException was expected"); + } catch (ServiceException e) { + Assert.assertEquals("invalid request: No such file: backup_dummy_test_non_existing.tgz", + e.getMessage()); + Assert.assertEquals("service.INVALID_REQUEST", e.getCode()); + + } + } +} diff --git a/store/src/java-test/com/zimbra/cs/service/mail/SendShareNotificationTest.java b/store/src/java-test/com/zimbra/cs/service/mail/SendShareNotificationTest.java index 055c89f8daa..148ee2e822a 100644 --- a/store/src/java-test/com/zimbra/cs/service/mail/SendShareNotificationTest.java +++ b/store/src/java-test/com/zimbra/cs/service/mail/SendShareNotificationTest.java @@ -128,7 +128,7 @@ protected Collection
sendMessage(Mailbox mbox, MimeMessage mm, Collecti } }); - L10nUtil.setMsgClassLoader("conf/msgs"); + L10nUtil.setMsgClassLoader("../store-conf/conf/msgs"); } @Before diff --git a/store/src/java-test/com/zimbra/cs/zimlet/ZimletUtilTest.java b/store/src/java-test/com/zimbra/cs/zimlet/ZimletUtilTest.java index 4f8d0f30ef6..32bf162b2d3 100644 --- a/store/src/java-test/com/zimbra/cs/zimlet/ZimletUtilTest.java +++ b/store/src/java-test/com/zimbra/cs/zimlet/ZimletUtilTest.java @@ -38,21 +38,21 @@ public void testZimletRootDir() { } try { - assertEquals(LC.zimlet_directory.value() + "/org_my_zimlet", ZimletUtil.getZimletRootDir("org_my_zimlet").getAbsolutePath()); + assertEquals(LC.zimlet_directory.value() + "/org_my_zimlet", ZimletUtil.getZimletRootDir("org_my_zimlet").getPath()); } catch (ZimletException e) { fail("Should not throw ZimletException for good zimlet name"); } try { - assertEquals(LC.zimlet_directory.value() + "/myzimlet", ZimletUtil.getZimletRootDir("myzimlet").getAbsolutePath()); + assertEquals(LC.zimlet_directory.value() + "/myzimlet", ZimletUtil.getZimletRootDir("myzimlet").getPath()); } catch (ZimletException e) { fail("Should not throw ZimletException for good zimlet name"); } try { - assertEquals(LC.zimlet_directory.value() + "/my.zimlet", ZimletUtil.getZimletRootDir("my.zimlet").getAbsolutePath()); + assertEquals(LC.zimlet_directory.value() + "/my.zimlet", ZimletUtil.getZimletRootDir("my.zimlet").getPath()); } catch (ZimletException e) { fail("Should not throw ZimletException for good zimlet name"); } } -} \ No newline at end of file +} diff --git a/store/src/java-test/datasource-test.xml b/store/src/java-test/datasource-test.xml new file mode 100644 index 00000000000..c6fb101dbc6 --- /dev/null +++ b/store/src/java-test/datasource-test.xml @@ -0,0 +1,68 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/store/src/java/com/zimbra/cs/account/DataSource.java b/store/src/java/com/zimbra/cs/account/DataSource.java index e89e72ffcce..e2e2f49b4db 100644 --- a/store/src/java/com/zimbra/cs/account/DataSource.java +++ b/store/src/java/com/zimbra/cs/account/DataSource.java @@ -154,6 +154,8 @@ public boolean isSslEnabled() { public String getOauthRefreshToken() { return getAttr(Provisioning.A_zimbraDataSourceOAuthRefreshToken); } + public String getDataSourceImportClassName() { return getAttr(Provisioning.A_zimbraDataSourceImportClassName); } + public String getDomain() { String domain = getAttr(Provisioning.A_zimbraDataSourceDomain, null); if (domain == null) { diff --git a/store/src/java/com/zimbra/cs/account/Provisioning.java b/store/src/java/com/zimbra/cs/account/Provisioning.java index c8f2bba114b..0bd8e08be1f 100644 --- a/store/src/java/com/zimbra/cs/account/Provisioning.java +++ b/store/src/java/com/zimbra/cs/account/Provisioning.java @@ -1514,17 +1514,24 @@ public static boolean canUseLocalIMAP(Account account) throws ServiceException { } } - public static List getPreferredIMAPServers(Account account) throws ServiceException { + public static List getPreferredIMAPServers(Account account) throws ServiceException { + Provisioning prov = getInstance(); + Server homeServer = account.getServer(); if(homeServer == null) { return Collections.emptyList(); } String[] upstreamIMAPServers = homeServer.getReverseProxyUpstreamImapServers(); + List imapServers = new ArrayList(); if(upstreamIMAPServers != null && upstreamIMAPServers.length > 0) { - return Arrays.asList(upstreamIMAPServers); + for (String server: upstreamIMAPServers) { + imapServers.add(prov.getServerByServiceHostname(server)); + } } else { - return Arrays.asList(account.getMailHost()); + imapServers.add(prov.getServerByServiceHostname(account.getMailHost())); } + + return imapServers; } public static List getIMAPDaemonServersForLocalServer() throws ServiceException { diff --git a/store/src/java/com/zimbra/cs/account/ShareInfo.java b/store/src/java/com/zimbra/cs/account/ShareInfo.java index e3c1390987b..20b67fd6c34 100644 --- a/store/src/java/com/zimbra/cs/account/ShareInfo.java +++ b/store/src/java/com/zimbra/cs/account/ShareInfo.java @@ -16,12 +16,6 @@ */ package com.zimbra.cs.account; -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.io.OutputStreamWriter; import java.util.ArrayList; import java.util.Date; import java.util.HashMap; @@ -33,7 +27,6 @@ import java.util.Set; import javax.activation.DataHandler; -import javax.activation.DataSource; import javax.mail.Address; import javax.mail.MessagingException; import javax.mail.Transport; @@ -42,7 +35,6 @@ import javax.mail.internet.MimeBodyPart; import javax.mail.internet.MimeMultipart; -import org.apache.commons.codec.binary.Hex; import com.google.common.base.Strings; import com.sun.mail.smtp.SMTPMessage; @@ -55,7 +47,6 @@ import com.zimbra.common.soap.Element; import com.zimbra.common.soap.MailConstants.ShareConstants; import com.zimbra.common.soap.SoapProtocol; -import com.zimbra.common.util.BlobMetaData; import com.zimbra.common.util.L10nUtil; import com.zimbra.common.util.L10nUtil.MsgKey; import com.zimbra.common.util.Pair; @@ -76,10 +67,11 @@ import com.zimbra.cs.mailbox.Mountpoint; import com.zimbra.cs.mailbox.OperationContext; import com.zimbra.cs.mailbox.acl.AclPushSerializer; -import com.zimbra.cs.servlet.ZimbraServlet; import com.zimbra.cs.util.AccountUtil; import com.zimbra.cs.util.JMSession; import com.zimbra.soap.mail.message.SendShareNotificationRequest.Action; +import com.zimbra.cs.util.AccountUtil.HtmlPartDataSource; +import com.zimbra.cs.util.AccountUtil.XmlPartDataSource; public class ShareInfo { @@ -555,10 +547,9 @@ public static class NotificationSender { private static final String HTML_LINE_BREAK = "
"; private static final String NEWLINE = "\n"; - - + public static MimeMultipart genNotifBody(ShareInfoData sid, String notes, - Locale locale, Action action, String externalGroupMember) + Locale locale, Action action, String externalGroupMember) throws MessagingException, ServiceException { // Body @@ -576,8 +567,8 @@ public static MimeMultipart genNotifBody(ShareInfoData sid, String notes, boolean goesToExternalAddr = (externalGranteeName != null); if (action == null && goesToExternalAddr) { Account owner = Provisioning.getInstance().getAccountById(sid.getOwnerAcctId()); - extUserShareAcceptUrl = getShareAcceptURL(owner, sid.getItemId(), externalGranteeName); - extUserLoginUrl = getExtUserLoginURL(owner); + extUserShareAcceptUrl = AccountUtil.getShareAcceptURL(owner, sid.getItemId(), externalGranteeName); + extUserLoginUrl = AccountUtil.getExtUserLoginURL(owner); } // TEXT part (add me first!) @@ -618,35 +609,37 @@ public static MimeMultipart genNotifBody(ShareInfoData sid, String notes, return mmp; } - private static String getExtUserLoginURL(Account owner) throws ServiceException { - return ZimbraServlet.getServiceUrl( - owner.getServer(), - Provisioning.getInstance().getDomain(owner), - "?virtualacctdomain=" + owner.getDomainName()); - } + public static String getMimePartHtml(ShareInfoData sid, String notes, Locale locale, + Action action, String extUserShareAcceptUrl, String extUserLoginUrl) throws MessagingException, ServiceException { - private static String getShareAcceptURL(Account account, int folderId, String externalUserEmail) - throws ServiceException { - StringBuilder encodedBuff = new StringBuilder(); - BlobMetaData.encodeMetaData("aid", account.getId(), encodedBuff); - BlobMetaData.encodeMetaData("fid", folderId, encodedBuff); - BlobMetaData.encodeMetaData("email", externalUserEmail, encodedBuff); - Domain domain = Provisioning.getInstance().getDomain(account); - if (domain != null) { - long urlExpiration = domain.getExternalShareInvitationUrlExpiration(); - if (urlExpiration != 0) { - BlobMetaData.encodeMetaData("exp", System.currentTimeMillis() + urlExpiration, encodedBuff); - } + String mimePartHtml; + if (action == Action.revoke) { + mimePartHtml = genRevokePart(sid, locale, true); + } else if (action == Action.expire) { + mimePartHtml = genExpirePart(sid, locale, true); + } else { + mimePartHtml = genPart(sid, action == Action.edit, notes, extUserShareAcceptUrl, + extUserLoginUrl, locale, null, true); } - String data = new String(Hex.encodeHex(encodedBuff.toString().getBytes())); - ExtAuthTokenKey key = ExtAuthTokenKey.getCurrentKey(); - String hmac = TokenUtil.getHmac(data, key.getKey()); - String encoded = key.getVersion() + "_" + hmac + "_" + data; - String path = "/service/extuserprov/?p=" + encoded; - return ZimbraServlet.getServiceUrl( - account.getServer(), Provisioning.getInstance().getDomain(account), path); - } + return mimePartHtml; + } + + + + public static String getMimePartText(ShareInfoData sid, String notes, Locale locale, + Action action, String extUserShareAcceptUrl, String extUserLoginUrl) throws MessagingException, ServiceException { + String mimePartText; + if (action == Action.revoke) { + mimePartText = genRevokePart(sid, locale, false); + } else if (action == Action.expire) { + mimePartText = genExpirePart(sid, locale, false); + } else { + mimePartText = genPart(sid, action == Action.edit, notes, extUserShareAcceptUrl, + extUserLoginUrl, locale, null, false); + } + return mimePartText; + } private static String genPart(ShareInfoData sid, boolean shareModified, String senderNotes, String extUserShareAcceptUrl, String extUserLoginUrl, Locale locale, StringBuilder sb, boolean html) { @@ -703,7 +696,7 @@ private static String genExpirePart(ShareInfoData sid, Locale locale, boolean ht sid.getOwnerNotifName()); } - private static String genXmlPart(ShareInfoData sid, String senderNotes, StringBuilder sb, Action action) + public static String genXmlPart(ShareInfoData sid, String senderNotes, StringBuilder sb, Action action) throws ServiceException { if (sb == null) { sb = new StringBuilder(); @@ -1063,78 +1056,6 @@ private static void sendMessage(Provisioning prov, buildContentAndSend(out, dl, visitor, locale, null); } } - - private static abstract class MimePartDataSource implements DataSource { - - private final String mText; - private byte[] mBuf = null; - - public MimePartDataSource(String text) { - mText = text; - } - - @Override - public InputStream getInputStream() throws IOException { - synchronized(this) { - if (mBuf == null) { - ByteArrayOutputStream buf = new ByteArrayOutputStream(); - OutputStreamWriter wout = - new OutputStreamWriter(buf, MimeConstants.P_CHARSET_UTF8); - String text = mText; - wout.write(text); - wout.flush(); - mBuf = buf.toByteArray(); - } - } - return new ByteArrayInputStream(mBuf); - } - - @Override - public OutputStream getOutputStream() { - throw new UnsupportedOperationException(); - } - } - - private static class HtmlPartDataSource extends MimePartDataSource { - private static final String CONTENT_TYPE = - MimeConstants.CT_TEXT_HTML + "; " + MimeConstants.P_CHARSET + "=" + MimeConstants.P_CHARSET_UTF8; - private static final String NAME = "HtmlDataSource"; - - HtmlPartDataSource(String text) { - super(text); - } - - @Override - public String getContentType() { - return CONTENT_TYPE; - } - - @Override - public String getName() { - return NAME; - } - } - - private static class XmlPartDataSource extends MimePartDataSource { - private static final String CONTENT_TYPE = - MimeConstants.CT_XML_ZIMBRA_SHARE + "; " + MimeConstants.P_CHARSET + "=" + MimeConstants.P_CHARSET_UTF8; - private static final String NAME = "XmlDataSource"; - - XmlPartDataSource(String text) { - super(text); - } - - @Override - public String getContentType() { - return CONTENT_TYPE; - } - - @Override - public String getName() { - return NAME; - } - } - } } diff --git a/store/src/java/com/zimbra/cs/account/ZAttrAccount.java b/store/src/java/com/zimbra/cs/account/ZAttrAccount.java index a6894d5572d..10de72146b1 100644 --- a/store/src/java/com/zimbra/cs/account/ZAttrAccount.java +++ b/store/src/java/com/zimbra/cs/account/ZAttrAccount.java @@ -12088,6 +12088,393 @@ public Map unsetExternalUserMailAddress(Map attrs) return attrs; } + /** + * RFC822 email address under verification for an account + * + * @return zimbraFeatureAddressUnderVerification, or null if unset + * + * @since ZCS 8.8.5 + */ + @ZAttr(id=2128) + public String getFeatureAddressUnderVerification() { + return getAttr(Provisioning.A_zimbraFeatureAddressUnderVerification, null, true); + } + + /** + * RFC822 email address under verification for an account + * + * @param zimbraFeatureAddressUnderVerification new value + * @throws com.zimbra.common.service.ServiceException if error during update + * + * @since ZCS 8.8.5 + */ + @ZAttr(id=2128) + public void setFeatureAddressUnderVerification(String zimbraFeatureAddressUnderVerification) throws com.zimbra.common.service.ServiceException { + HashMap attrs = new HashMap(); + attrs.put(Provisioning.A_zimbraFeatureAddressUnderVerification, zimbraFeatureAddressUnderVerification); + getProvisioning().modifyAttrs(this, attrs); + } + + /** + * RFC822 email address under verification for an account + * + * @param zimbraFeatureAddressUnderVerification new value + * @param attrs existing map to populate, or null to create a new map + * @return populated map to pass into Provisioning.modifyAttrs + * + * @since ZCS 8.8.5 + */ + @ZAttr(id=2128) + public Map setFeatureAddressUnderVerification(String zimbraFeatureAddressUnderVerification, Map attrs) { + if (attrs == null) attrs = new HashMap(); + attrs.put(Provisioning.A_zimbraFeatureAddressUnderVerification, zimbraFeatureAddressUnderVerification); + return attrs; + } + + /** + * RFC822 email address under verification for an account + * + * @throws com.zimbra.common.service.ServiceException if error during update + * + * @since ZCS 8.8.5 + */ + @ZAttr(id=2128) + public void unsetFeatureAddressUnderVerification() throws com.zimbra.common.service.ServiceException { + HashMap attrs = new HashMap(); + attrs.put(Provisioning.A_zimbraFeatureAddressUnderVerification, ""); + getProvisioning().modifyAttrs(this, attrs); + } + + /** + * RFC822 email address under verification for an account + * + * @param attrs existing map to populate, or null to create a new map + * @return populated map to pass into Provisioning.modifyAttrs + * + * @since ZCS 8.8.5 + */ + @ZAttr(id=2128) + public Map unsetFeatureAddressUnderVerification(Map attrs) { + if (attrs == null) attrs = new HashMap(); + attrs.put(Provisioning.A_zimbraFeatureAddressUnderVerification, ""); + return attrs; + } + + /** + * Enable end-user email address verification + * + * @return zimbraFeatureAddressVerificationEnabled, or false if unset + * + * @since ZCS 8.8.5 + */ + @ZAttr(id=2126) + public boolean isFeatureAddressVerificationEnabled() { + return getBooleanAttr(Provisioning.A_zimbraFeatureAddressVerificationEnabled, false, true); + } + + /** + * Enable end-user email address verification + * + * @param zimbraFeatureAddressVerificationEnabled new value + * @throws com.zimbra.common.service.ServiceException if error during update + * + * @since ZCS 8.8.5 + */ + @ZAttr(id=2126) + public void setFeatureAddressVerificationEnabled(boolean zimbraFeatureAddressVerificationEnabled) throws com.zimbra.common.service.ServiceException { + HashMap attrs = new HashMap(); + attrs.put(Provisioning.A_zimbraFeatureAddressVerificationEnabled, zimbraFeatureAddressVerificationEnabled ? Provisioning.TRUE : Provisioning.FALSE); + getProvisioning().modifyAttrs(this, attrs); + } + + /** + * Enable end-user email address verification + * + * @param zimbraFeatureAddressVerificationEnabled new value + * @param attrs existing map to populate, or null to create a new map + * @return populated map to pass into Provisioning.modifyAttrs + * + * @since ZCS 8.8.5 + */ + @ZAttr(id=2126) + public Map setFeatureAddressVerificationEnabled(boolean zimbraFeatureAddressVerificationEnabled, Map attrs) { + if (attrs == null) attrs = new HashMap(); + attrs.put(Provisioning.A_zimbraFeatureAddressVerificationEnabled, zimbraFeatureAddressVerificationEnabled ? Provisioning.TRUE : Provisioning.FALSE); + return attrs; + } + + /** + * Enable end-user email address verification + * + * @throws com.zimbra.common.service.ServiceException if error during update + * + * @since ZCS 8.8.5 + */ + @ZAttr(id=2126) + public void unsetFeatureAddressVerificationEnabled() throws com.zimbra.common.service.ServiceException { + HashMap attrs = new HashMap(); + attrs.put(Provisioning.A_zimbraFeatureAddressVerificationEnabled, ""); + getProvisioning().modifyAttrs(this, attrs); + } + + /** + * Enable end-user email address verification + * + * @param attrs existing map to populate, or null to create a new map + * @return populated map to pass into Provisioning.modifyAttrs + * + * @since ZCS 8.8.5 + */ + @ZAttr(id=2126) + public Map unsetFeatureAddressVerificationEnabled(Map attrs) { + if (attrs == null) attrs = new HashMap(); + attrs.put(Provisioning.A_zimbraFeatureAddressVerificationEnabled, ""); + return attrs; + } + + /** + * Expiry time for end-user email address verification. Must be in valid + * duration format: {digits}{time-unit}. digits: 0-9, time-unit: + * [hmsd]|ms. h - hours, m - minutes, s - seconds, d - days, ms - + * milliseconds. If time unit is not specified, the default is + * s(seconds). + * + *

Use getFeatureAddressVerificationExpiryAsString to access value as a string. + * + * @see #getFeatureAddressVerificationExpiryAsString() + * + * @return zimbraFeatureAddressVerificationExpiry in millseconds, or 86400000 (1d) if unset + * + * @since ZCS 8.8.5 + */ + @ZAttr(id=2127) + public long getFeatureAddressVerificationExpiry() { + return getTimeInterval(Provisioning.A_zimbraFeatureAddressVerificationExpiry, 86400000L, true); + } + + /** + * Expiry time for end-user email address verification. Must be in valid + * duration format: {digits}{time-unit}. digits: 0-9, time-unit: + * [hmsd]|ms. h - hours, m - minutes, s - seconds, d - days, ms - + * milliseconds. If time unit is not specified, the default is + * s(seconds). + * + * @return zimbraFeatureAddressVerificationExpiry, or "1d" if unset + * + * @since ZCS 8.8.5 + */ + @ZAttr(id=2127) + public String getFeatureAddressVerificationExpiryAsString() { + return getAttr(Provisioning.A_zimbraFeatureAddressVerificationExpiry, "1d", true); + } + + /** + * Expiry time for end-user email address verification. Must be in valid + * duration format: {digits}{time-unit}. digits: 0-9, time-unit: + * [hmsd]|ms. h - hours, m - minutes, s - seconds, d - days, ms - + * milliseconds. If time unit is not specified, the default is + * s(seconds). + * + * @param zimbraFeatureAddressVerificationExpiry new value + * @throws com.zimbra.common.service.ServiceException if error during update + * + * @since ZCS 8.8.5 + */ + @ZAttr(id=2127) + public void setFeatureAddressVerificationExpiry(String zimbraFeatureAddressVerificationExpiry) throws com.zimbra.common.service.ServiceException { + HashMap attrs = new HashMap(); + attrs.put(Provisioning.A_zimbraFeatureAddressVerificationExpiry, zimbraFeatureAddressVerificationExpiry); + getProvisioning().modifyAttrs(this, attrs); + } + + /** + * Expiry time for end-user email address verification. Must be in valid + * duration format: {digits}{time-unit}. digits: 0-9, time-unit: + * [hmsd]|ms. h - hours, m - minutes, s - seconds, d - days, ms - + * milliseconds. If time unit is not specified, the default is + * s(seconds). + * + * @param zimbraFeatureAddressVerificationExpiry new value + * @param attrs existing map to populate, or null to create a new map + * @return populated map to pass into Provisioning.modifyAttrs + * + * @since ZCS 8.8.5 + */ + @ZAttr(id=2127) + public Map setFeatureAddressVerificationExpiry(String zimbraFeatureAddressVerificationExpiry, Map attrs) { + if (attrs == null) attrs = new HashMap(); + attrs.put(Provisioning.A_zimbraFeatureAddressVerificationExpiry, zimbraFeatureAddressVerificationExpiry); + return attrs; + } + + /** + * Expiry time for end-user email address verification. Must be in valid + * duration format: {digits}{time-unit}. digits: 0-9, time-unit: + * [hmsd]|ms. h - hours, m - minutes, s - seconds, d - days, ms - + * milliseconds. If time unit is not specified, the default is + * s(seconds). + * + * @throws com.zimbra.common.service.ServiceException if error during update + * + * @since ZCS 8.8.5 + */ + @ZAttr(id=2127) + public void unsetFeatureAddressVerificationExpiry() throws com.zimbra.common.service.ServiceException { + HashMap attrs = new HashMap(); + attrs.put(Provisioning.A_zimbraFeatureAddressVerificationExpiry, ""); + getProvisioning().modifyAttrs(this, attrs); + } + + /** + * Expiry time for end-user email address verification. Must be in valid + * duration format: {digits}{time-unit}. digits: 0-9, time-unit: + * [hmsd]|ms. h - hours, m - minutes, s - seconds, d - days, ms - + * milliseconds. If time unit is not specified, the default is + * s(seconds). + * + * @param attrs existing map to populate, or null to create a new map + * @return populated map to pass into Provisioning.modifyAttrs + * + * @since ZCS 8.8.5 + */ + @ZAttr(id=2127) + public Map unsetFeatureAddressVerificationExpiry(Map attrs) { + if (attrs == null) attrs = new HashMap(); + attrs.put(Provisioning.A_zimbraFeatureAddressVerificationExpiry, ""); + return attrs; + } + + /** + * End-user email address verification status + * + *

Valid values: [verified, pending, failed, expired] + * + * @return zimbraFeatureAddressVerificationStatus, or null if unset and/or has invalid value + * + * @since ZCS 8.8.5 + */ + @ZAttr(id=2129) + public ZAttrProvisioning.FeatureAddressVerificationStatus getFeatureAddressVerificationStatus() { + try { String v = getAttr(Provisioning.A_zimbraFeatureAddressVerificationStatus, true, true); return v == null ? null : ZAttrProvisioning.FeatureAddressVerificationStatus.fromString(v); } catch(com.zimbra.common.service.ServiceException e) { return null; } + } + + /** + * End-user email address verification status + * + *

Valid values: [verified, pending, failed, expired] + * + * @return zimbraFeatureAddressVerificationStatus, or null if unset + * + * @since ZCS 8.8.5 + */ + @ZAttr(id=2129) + public String getFeatureAddressVerificationStatusAsString() { + return getAttr(Provisioning.A_zimbraFeatureAddressVerificationStatus, null, true); + } + + /** + * End-user email address verification status + * + *

Valid values: [verified, pending, failed, expired] + * + * @param zimbraFeatureAddressVerificationStatus new value + * @throws com.zimbra.common.service.ServiceException if error during update + * + * @since ZCS 8.8.5 + */ + @ZAttr(id=2129) + public void setFeatureAddressVerificationStatus(ZAttrProvisioning.FeatureAddressVerificationStatus zimbraFeatureAddressVerificationStatus) throws com.zimbra.common.service.ServiceException { + HashMap attrs = new HashMap(); + attrs.put(Provisioning.A_zimbraFeatureAddressVerificationStatus, zimbraFeatureAddressVerificationStatus.toString()); + getProvisioning().modifyAttrs(this, attrs); + } + + /** + * End-user email address verification status + * + *

Valid values: [verified, pending, failed, expired] + * + * @param zimbraFeatureAddressVerificationStatus new value + * @param attrs existing map to populate, or null to create a new map + * @return populated map to pass into Provisioning.modifyAttrs + * + * @since ZCS 8.8.5 + */ + @ZAttr(id=2129) + public Map setFeatureAddressVerificationStatus(ZAttrProvisioning.FeatureAddressVerificationStatus zimbraFeatureAddressVerificationStatus, Map attrs) { + if (attrs == null) attrs = new HashMap(); + attrs.put(Provisioning.A_zimbraFeatureAddressVerificationStatus, zimbraFeatureAddressVerificationStatus.toString()); + return attrs; + } + + /** + * End-user email address verification status + * + *

Valid values: [verified, pending, failed, expired] + * + * @param zimbraFeatureAddressVerificationStatus new value + * @throws com.zimbra.common.service.ServiceException if error during update + * + * @since ZCS 8.8.5 + */ + @ZAttr(id=2129) + public void setFeatureAddressVerificationStatusAsString(String zimbraFeatureAddressVerificationStatus) throws com.zimbra.common.service.ServiceException { + HashMap attrs = new HashMap(); + attrs.put(Provisioning.A_zimbraFeatureAddressVerificationStatus, zimbraFeatureAddressVerificationStatus); + getProvisioning().modifyAttrs(this, attrs); + } + + /** + * End-user email address verification status + * + *

Valid values: [verified, pending, failed, expired] + * + * @param zimbraFeatureAddressVerificationStatus new value + * @param attrs existing map to populate, or null to create a new map + * @return populated map to pass into Provisioning.modifyAttrs + * + * @since ZCS 8.8.5 + */ + @ZAttr(id=2129) + public Map setFeatureAddressVerificationStatusAsString(String zimbraFeatureAddressVerificationStatus, Map attrs) { + if (attrs == null) attrs = new HashMap(); + attrs.put(Provisioning.A_zimbraFeatureAddressVerificationStatus, zimbraFeatureAddressVerificationStatus); + return attrs; + } + + /** + * End-user email address verification status + * + *

Valid values: [verified, pending, failed, expired] + * + * @throws com.zimbra.common.service.ServiceException if error during update + * + * @since ZCS 8.8.5 + */ + @ZAttr(id=2129) + public void unsetFeatureAddressVerificationStatus() throws com.zimbra.common.service.ServiceException { + HashMap attrs = new HashMap(); + attrs.put(Provisioning.A_zimbraFeatureAddressVerificationStatus, ""); + getProvisioning().modifyAttrs(this, attrs); + } + + /** + * End-user email address verification status + * + *

Valid values: [verified, pending, failed, expired] + * + * @param attrs existing map to populate, or null to create a new map + * @return populated map to pass into Provisioning.modifyAttrs + * + * @since ZCS 8.8.5 + */ + @ZAttr(id=2129) + public Map unsetFeatureAddressVerificationStatus(Map attrs) { + if (attrs == null) attrs = new HashMap(); + attrs.put(Provisioning.A_zimbraFeatureAddressVerificationStatus, ""); + return attrs; + } + /** * whether email features and tabs are enabled in the web client if * accessed from the admin console @@ -16107,6 +16494,78 @@ public Map unsetFeatureManageZimlets(Map attrs) { return attrs; } + /** + * Mark messages sent to a forwarding address as read + * + * @return zimbraFeatureMarkMailForwardedAsRead, or false if unset + * + * @since ZCS 8.8.5 + */ + @ZAttr(id=2123) + public boolean isFeatureMarkMailForwardedAsRead() { + return getBooleanAttr(Provisioning.A_zimbraFeatureMarkMailForwardedAsRead, false, true); + } + + /** + * Mark messages sent to a forwarding address as read + * + * @param zimbraFeatureMarkMailForwardedAsRead new value + * @throws com.zimbra.common.service.ServiceException if error during update + * + * @since ZCS 8.8.5 + */ + @ZAttr(id=2123) + public void setFeatureMarkMailForwardedAsRead(boolean zimbraFeatureMarkMailForwardedAsRead) throws com.zimbra.common.service.ServiceException { + HashMap attrs = new HashMap(); + attrs.put(Provisioning.A_zimbraFeatureMarkMailForwardedAsRead, zimbraFeatureMarkMailForwardedAsRead ? Provisioning.TRUE : Provisioning.FALSE); + getProvisioning().modifyAttrs(this, attrs); + } + + /** + * Mark messages sent to a forwarding address as read + * + * @param zimbraFeatureMarkMailForwardedAsRead new value + * @param attrs existing map to populate, or null to create a new map + * @return populated map to pass into Provisioning.modifyAttrs + * + * @since ZCS 8.8.5 + */ + @ZAttr(id=2123) + public Map setFeatureMarkMailForwardedAsRead(boolean zimbraFeatureMarkMailForwardedAsRead, Map attrs) { + if (attrs == null) attrs = new HashMap(); + attrs.put(Provisioning.A_zimbraFeatureMarkMailForwardedAsRead, zimbraFeatureMarkMailForwardedAsRead ? Provisioning.TRUE : Provisioning.FALSE); + return attrs; + } + + /** + * Mark messages sent to a forwarding address as read + * + * @throws com.zimbra.common.service.ServiceException if error during update + * + * @since ZCS 8.8.5 + */ + @ZAttr(id=2123) + public void unsetFeatureMarkMailForwardedAsRead() throws com.zimbra.common.service.ServiceException { + HashMap attrs = new HashMap(); + attrs.put(Provisioning.A_zimbraFeatureMarkMailForwardedAsRead, ""); + getProvisioning().modifyAttrs(this, attrs); + } + + /** + * Mark messages sent to a forwarding address as read + * + * @param attrs existing map to populate, or null to create a new map + * @return populated map to pass into Provisioning.modifyAttrs + * + * @since ZCS 8.8.5 + */ + @ZAttr(id=2123) + public Map unsetFeatureMarkMailForwardedAsRead(Map attrs) { + if (attrs == null) attrs = new HashMap(); + attrs.put(Provisioning.A_zimbraFeatureMarkMailForwardedAsRead, ""); + return attrs; + } + /** * Whether to enable Zimbra Mobile Gateway feature * diff --git a/store/src/java/com/zimbra/cs/account/ZAttrConfig.java b/store/src/java/com/zimbra/cs/account/ZAttrConfig.java index cd730448c04..fd0288cb98a 100644 --- a/store/src/java/com/zimbra/cs/account/ZAttrConfig.java +++ b/store/src/java/com/zimbra/cs/account/ZAttrConfig.java @@ -15786,6 +15786,230 @@ public Map unsetExternalShareInvitationUrlExpiration(MapUse getFeatureContactBackupFrequencyAsString to access value as a string. + * + * @see #getFeatureContactBackupFrequencyAsString() + * + * @return zimbraFeatureContactBackupFrequency in millseconds, or 86400000 (1d) if unset + * + * @since ZCS 8.8.5 + */ + @ZAttr(id=2124) + public long getFeatureContactBackupFrequency() { + return getTimeInterval(Provisioning.A_zimbraFeatureContactBackupFrequency, 86400000L, true); + } + + /** + * Sleep time between subsequent contact backups. 0 means that contact + * backup is disabled. . Must be in valid duration format: + * {digits}{time-unit}. digits: 0-9, time-unit: [hmsd]|ms. h - hours, m - + * minutes, s - seconds, d - days, ms - milliseconds. If time unit is not + * specified, the default is s(seconds). + * + * @return zimbraFeatureContactBackupFrequency, or "1d" if unset + * + * @since ZCS 8.8.5 + */ + @ZAttr(id=2124) + public String getFeatureContactBackupFrequencyAsString() { + return getAttr(Provisioning.A_zimbraFeatureContactBackupFrequency, "1d", true); + } + + /** + * Sleep time between subsequent contact backups. 0 means that contact + * backup is disabled. . Must be in valid duration format: + * {digits}{time-unit}. digits: 0-9, time-unit: [hmsd]|ms. h - hours, m - + * minutes, s - seconds, d - days, ms - milliseconds. If time unit is not + * specified, the default is s(seconds). + * + * @param zimbraFeatureContactBackupFrequency new value + * @throws com.zimbra.common.service.ServiceException if error during update + * + * @since ZCS 8.8.5 + */ + @ZAttr(id=2124) + public void setFeatureContactBackupFrequency(String zimbraFeatureContactBackupFrequency) throws com.zimbra.common.service.ServiceException { + HashMap attrs = new HashMap(); + attrs.put(Provisioning.A_zimbraFeatureContactBackupFrequency, zimbraFeatureContactBackupFrequency); + getProvisioning().modifyAttrs(this, attrs); + } + + /** + * Sleep time between subsequent contact backups. 0 means that contact + * backup is disabled. . Must be in valid duration format: + * {digits}{time-unit}. digits: 0-9, time-unit: [hmsd]|ms. h - hours, m - + * minutes, s - seconds, d - days, ms - milliseconds. If time unit is not + * specified, the default is s(seconds). + * + * @param zimbraFeatureContactBackupFrequency new value + * @param attrs existing map to populate, or null to create a new map + * @return populated map to pass into Provisioning.modifyAttrs + * + * @since ZCS 8.8.5 + */ + @ZAttr(id=2124) + public Map setFeatureContactBackupFrequency(String zimbraFeatureContactBackupFrequency, Map attrs) { + if (attrs == null) attrs = new HashMap(); + attrs.put(Provisioning.A_zimbraFeatureContactBackupFrequency, zimbraFeatureContactBackupFrequency); + return attrs; + } + + /** + * Sleep time between subsequent contact backups. 0 means that contact + * backup is disabled. . Must be in valid duration format: + * {digits}{time-unit}. digits: 0-9, time-unit: [hmsd]|ms. h - hours, m - + * minutes, s - seconds, d - days, ms - milliseconds. If time unit is not + * specified, the default is s(seconds). + * + * @throws com.zimbra.common.service.ServiceException if error during update + * + * @since ZCS 8.8.5 + */ + @ZAttr(id=2124) + public void unsetFeatureContactBackupFrequency() throws com.zimbra.common.service.ServiceException { + HashMap attrs = new HashMap(); + attrs.put(Provisioning.A_zimbraFeatureContactBackupFrequency, ""); + getProvisioning().modifyAttrs(this, attrs); + } + + /** + * Sleep time between subsequent contact backups. 0 means that contact + * backup is disabled. . Must be in valid duration format: + * {digits}{time-unit}. digits: 0-9, time-unit: [hmsd]|ms. h - hours, m - + * minutes, s - seconds, d - days, ms - milliseconds. If time unit is not + * specified, the default is s(seconds). + * + * @param attrs existing map to populate, or null to create a new map + * @return populated map to pass into Provisioning.modifyAttrs + * + * @since ZCS 8.8.5 + */ + @ZAttr(id=2124) + public Map unsetFeatureContactBackupFrequency(Map attrs) { + if (attrs == null) attrs = new HashMap(); + attrs.put(Provisioning.A_zimbraFeatureContactBackupFrequency, ""); + return attrs; + } + + /** + * Duration for which the backups should be preserved. . Must be in valid + * duration format: {digits}{time-unit}. digits: 0-9, time-unit: + * [hmsd]|ms. h - hours, m - minutes, s - seconds, d - days, ms - + * milliseconds. If time unit is not specified, the default is + * s(seconds). + * + *

Use getFeatureContactBackupLifeTimeAsString to access value as a string. + * + * @see #getFeatureContactBackupLifeTimeAsString() + * + * @return zimbraFeatureContactBackupLifeTime in millseconds, or 1296000000 (15d) if unset + * + * @since ZCS 8.8.5 + */ + @ZAttr(id=2125) + public long getFeatureContactBackupLifeTime() { + return getTimeInterval(Provisioning.A_zimbraFeatureContactBackupLifeTime, 1296000000L, true); + } + + /** + * Duration for which the backups should be preserved. . Must be in valid + * duration format: {digits}{time-unit}. digits: 0-9, time-unit: + * [hmsd]|ms. h - hours, m - minutes, s - seconds, d - days, ms - + * milliseconds. If time unit is not specified, the default is + * s(seconds). + * + * @return zimbraFeatureContactBackupLifeTime, or "15d" if unset + * + * @since ZCS 8.8.5 + */ + @ZAttr(id=2125) + public String getFeatureContactBackupLifeTimeAsString() { + return getAttr(Provisioning.A_zimbraFeatureContactBackupLifeTime, "15d", true); + } + + /** + * Duration for which the backups should be preserved. . Must be in valid + * duration format: {digits}{time-unit}. digits: 0-9, time-unit: + * [hmsd]|ms. h - hours, m - minutes, s - seconds, d - days, ms - + * milliseconds. If time unit is not specified, the default is + * s(seconds). + * + * @param zimbraFeatureContactBackupLifeTime new value + * @throws com.zimbra.common.service.ServiceException if error during update + * + * @since ZCS 8.8.5 + */ + @ZAttr(id=2125) + public void setFeatureContactBackupLifeTime(String zimbraFeatureContactBackupLifeTime) throws com.zimbra.common.service.ServiceException { + HashMap attrs = new HashMap(); + attrs.put(Provisioning.A_zimbraFeatureContactBackupLifeTime, zimbraFeatureContactBackupLifeTime); + getProvisioning().modifyAttrs(this, attrs); + } + + /** + * Duration for which the backups should be preserved. . Must be in valid + * duration format: {digits}{time-unit}. digits: 0-9, time-unit: + * [hmsd]|ms. h - hours, m - minutes, s - seconds, d - days, ms - + * milliseconds. If time unit is not specified, the default is + * s(seconds). + * + * @param zimbraFeatureContactBackupLifeTime new value + * @param attrs existing map to populate, or null to create a new map + * @return populated map to pass into Provisioning.modifyAttrs + * + * @since ZCS 8.8.5 + */ + @ZAttr(id=2125) + public Map setFeatureContactBackupLifeTime(String zimbraFeatureContactBackupLifeTime, Map attrs) { + if (attrs == null) attrs = new HashMap(); + attrs.put(Provisioning.A_zimbraFeatureContactBackupLifeTime, zimbraFeatureContactBackupLifeTime); + return attrs; + } + + /** + * Duration for which the backups should be preserved. . Must be in valid + * duration format: {digits}{time-unit}. digits: 0-9, time-unit: + * [hmsd]|ms. h - hours, m - minutes, s - seconds, d - days, ms - + * milliseconds. If time unit is not specified, the default is + * s(seconds). + * + * @throws com.zimbra.common.service.ServiceException if error during update + * + * @since ZCS 8.8.5 + */ + @ZAttr(id=2125) + public void unsetFeatureContactBackupLifeTime() throws com.zimbra.common.service.ServiceException { + HashMap attrs = new HashMap(); + attrs.put(Provisioning.A_zimbraFeatureContactBackupLifeTime, ""); + getProvisioning().modifyAttrs(this, attrs); + } + + /** + * Duration for which the backups should be preserved. . Must be in valid + * duration format: {digits}{time-unit}. digits: 0-9, time-unit: + * [hmsd]|ms. h - hours, m - minutes, s - seconds, d - days, ms - + * milliseconds. If time unit is not specified, the default is + * s(seconds). + * + * @param attrs existing map to populate, or null to create a new map + * @return populated map to pass into Provisioning.modifyAttrs + * + * @since ZCS 8.8.5 + */ + @ZAttr(id=2125) + public Map unsetFeatureContactBackupLifeTime(Map attrs) { + if (attrs == null) attrs = new HashMap(); + attrs.put(Provisioning.A_zimbraFeatureContactBackupLifeTime, ""); + return attrs; + } + /** * Whether to display the distribution list folder in address book * @@ -47209,7 +47433,9 @@ public Map unsetNetworkActivation(Map attrs) { } /** - * Whether to enable old zimbra network admin module. + * Deprecated since: 8.8.5. This attribute has been renamed to + * zimbraNetworkAdminNGEnabled. Orig desc: Whether to enable old zimbra + * network admin module. * * @return zimbraNetworkAdminEnabled, or true if unset * @@ -47221,7 +47447,9 @@ public boolean isNetworkAdminEnabled() { } /** - * Whether to enable old zimbra network admin module. + * Deprecated since: 8.8.5. This attribute has been renamed to + * zimbraNetworkAdminNGEnabled. Orig desc: Whether to enable old zimbra + * network admin module. * * @param zimbraNetworkAdminEnabled new value * @throws com.zimbra.common.service.ServiceException if error during update @@ -47236,7 +47464,9 @@ public void setNetworkAdminEnabled(boolean zimbraNetworkAdminEnabled) throws com } /** - * Whether to enable old zimbra network admin module. + * Deprecated since: 8.8.5. This attribute has been renamed to + * zimbraNetworkAdminNGEnabled. Orig desc: Whether to enable old zimbra + * network admin module. * * @param zimbraNetworkAdminEnabled new value * @param attrs existing map to populate, or null to create a new map @@ -47252,7 +47482,9 @@ public Map setNetworkAdminEnabled(boolean zimbraNetworkAdminEnabl } /** - * Whether to enable old zimbra network admin module. + * Deprecated since: 8.8.5. This attribute has been renamed to + * zimbraNetworkAdminNGEnabled. Orig desc: Whether to enable old zimbra + * network admin module. * * @throws com.zimbra.common.service.ServiceException if error during update * @@ -47266,7 +47498,9 @@ public void unsetNetworkAdminEnabled() throws com.zimbra.common.service.ServiceE } /** - * Whether to enable old zimbra network admin module. + * Deprecated since: 8.8.5. This attribute has been renamed to + * zimbraNetworkAdminNGEnabled. Orig desc: Whether to enable old zimbra + * network admin module. * * @param attrs existing map to populate, or null to create a new map * @return populated map to pass into Provisioning.modifyAttrs @@ -47280,6 +47514,78 @@ public Map unsetNetworkAdminEnabled(Map attrs) { return attrs; } + /** + * Whether to enable zimbra network new generation admin module. + * + * @return zimbraNetworkAdminNGEnabled, or false if unset + * + * @since ZCS 8.8.5 + */ + @ZAttr(id=2130) + public boolean isNetworkAdminNGEnabled() { + return getBooleanAttr(Provisioning.A_zimbraNetworkAdminNGEnabled, false, true); + } + + /** + * Whether to enable zimbra network new generation admin module. + * + * @param zimbraNetworkAdminNGEnabled new value + * @throws com.zimbra.common.service.ServiceException if error during update + * + * @since ZCS 8.8.5 + */ + @ZAttr(id=2130) + public void setNetworkAdminNGEnabled(boolean zimbraNetworkAdminNGEnabled) throws com.zimbra.common.service.ServiceException { + HashMap attrs = new HashMap(); + attrs.put(Provisioning.A_zimbraNetworkAdminNGEnabled, zimbraNetworkAdminNGEnabled ? Provisioning.TRUE : Provisioning.FALSE); + getProvisioning().modifyAttrs(this, attrs); + } + + /** + * Whether to enable zimbra network new generation admin module. + * + * @param zimbraNetworkAdminNGEnabled new value + * @param attrs existing map to populate, or null to create a new map + * @return populated map to pass into Provisioning.modifyAttrs + * + * @since ZCS 8.8.5 + */ + @ZAttr(id=2130) + public Map setNetworkAdminNGEnabled(boolean zimbraNetworkAdminNGEnabled, Map attrs) { + if (attrs == null) attrs = new HashMap(); + attrs.put(Provisioning.A_zimbraNetworkAdminNGEnabled, zimbraNetworkAdminNGEnabled ? Provisioning.TRUE : Provisioning.FALSE); + return attrs; + } + + /** + * Whether to enable zimbra network new generation admin module. + * + * @throws com.zimbra.common.service.ServiceException if error during update + * + * @since ZCS 8.8.5 + */ + @ZAttr(id=2130) + public void unsetNetworkAdminNGEnabled() throws com.zimbra.common.service.ServiceException { + HashMap attrs = new HashMap(); + attrs.put(Provisioning.A_zimbraNetworkAdminNGEnabled, ""); + getProvisioning().modifyAttrs(this, attrs); + } + + /** + * Whether to enable zimbra network new generation admin module. + * + * @param attrs existing map to populate, or null to create a new map + * @return populated map to pass into Provisioning.modifyAttrs + * + * @since ZCS 8.8.5 + */ + @ZAttr(id=2130) + public Map unsetNetworkAdminNGEnabled(Map attrs) { + if (attrs == null) attrs = new HashMap(); + attrs.put(Provisioning.A_zimbraNetworkAdminNGEnabled, ""); + return attrs; + } + /** * Contents of a signed Zimbra license key - an XML string. * @@ -47345,13 +47651,13 @@ public Map unsetNetworkLicense(Map attrs) { /** * Whether to enable zimbra network new generation mobile sync module. * - * @return zimbraNetworkMobileNGEnabled, or true if unset + * @return zimbraNetworkMobileNGEnabled, or false if unset * * @since ZCS 8.8.0 */ @ZAttr(id=2118) public boolean isNetworkMobileNGEnabled() { - return getBooleanAttr(Provisioning.A_zimbraNetworkMobileNGEnabled, true, true); + return getBooleanAttr(Provisioning.A_zimbraNetworkMobileNGEnabled, false, true); } /** diff --git a/store/src/java/com/zimbra/cs/account/ZAttrCos.java b/store/src/java/com/zimbra/cs/account/ZAttrCos.java index ae65e618c6c..5f95e0fee9a 100644 --- a/store/src/java/com/zimbra/cs/account/ZAttrCos.java +++ b/store/src/java/com/zimbra/cs/account/ZAttrCos.java @@ -7036,6 +7036,190 @@ public Map unsetExternalSharingEnabled(Map attrs) return attrs; } + /** + * Enable end-user email address verification + * + * @return zimbraFeatureAddressVerificationEnabled, or false if unset + * + * @since ZCS 8.8.5 + */ + @ZAttr(id=2126) + public boolean isFeatureAddressVerificationEnabled() { + return getBooleanAttr(Provisioning.A_zimbraFeatureAddressVerificationEnabled, false, true); + } + + /** + * Enable end-user email address verification + * + * @param zimbraFeatureAddressVerificationEnabled new value + * @throws com.zimbra.common.service.ServiceException if error during update + * + * @since ZCS 8.8.5 + */ + @ZAttr(id=2126) + public void setFeatureAddressVerificationEnabled(boolean zimbraFeatureAddressVerificationEnabled) throws com.zimbra.common.service.ServiceException { + HashMap attrs = new HashMap(); + attrs.put(Provisioning.A_zimbraFeatureAddressVerificationEnabled, zimbraFeatureAddressVerificationEnabled ? Provisioning.TRUE : Provisioning.FALSE); + getProvisioning().modifyAttrs(this, attrs); + } + + /** + * Enable end-user email address verification + * + * @param zimbraFeatureAddressVerificationEnabled new value + * @param attrs existing map to populate, or null to create a new map + * @return populated map to pass into Provisioning.modifyAttrs + * + * @since ZCS 8.8.5 + */ + @ZAttr(id=2126) + public Map setFeatureAddressVerificationEnabled(boolean zimbraFeatureAddressVerificationEnabled, Map attrs) { + if (attrs == null) attrs = new HashMap(); + attrs.put(Provisioning.A_zimbraFeatureAddressVerificationEnabled, zimbraFeatureAddressVerificationEnabled ? Provisioning.TRUE : Provisioning.FALSE); + return attrs; + } + + /** + * Enable end-user email address verification + * + * @throws com.zimbra.common.service.ServiceException if error during update + * + * @since ZCS 8.8.5 + */ + @ZAttr(id=2126) + public void unsetFeatureAddressVerificationEnabled() throws com.zimbra.common.service.ServiceException { + HashMap attrs = new HashMap(); + attrs.put(Provisioning.A_zimbraFeatureAddressVerificationEnabled, ""); + getProvisioning().modifyAttrs(this, attrs); + } + + /** + * Enable end-user email address verification + * + * @param attrs existing map to populate, or null to create a new map + * @return populated map to pass into Provisioning.modifyAttrs + * + * @since ZCS 8.8.5 + */ + @ZAttr(id=2126) + public Map unsetFeatureAddressVerificationEnabled(Map attrs) { + if (attrs == null) attrs = new HashMap(); + attrs.put(Provisioning.A_zimbraFeatureAddressVerificationEnabled, ""); + return attrs; + } + + /** + * Expiry time for end-user email address verification. Must be in valid + * duration format: {digits}{time-unit}. digits: 0-9, time-unit: + * [hmsd]|ms. h - hours, m - minutes, s - seconds, d - days, ms - + * milliseconds. If time unit is not specified, the default is + * s(seconds). + * + *

Use getFeatureAddressVerificationExpiryAsString to access value as a string. + * + * @see #getFeatureAddressVerificationExpiryAsString() + * + * @return zimbraFeatureAddressVerificationExpiry in millseconds, or 86400000 (1d) if unset + * + * @since ZCS 8.8.5 + */ + @ZAttr(id=2127) + public long getFeatureAddressVerificationExpiry() { + return getTimeInterval(Provisioning.A_zimbraFeatureAddressVerificationExpiry, 86400000L, true); + } + + /** + * Expiry time for end-user email address verification. Must be in valid + * duration format: {digits}{time-unit}. digits: 0-9, time-unit: + * [hmsd]|ms. h - hours, m - minutes, s - seconds, d - days, ms - + * milliseconds. If time unit is not specified, the default is + * s(seconds). + * + * @return zimbraFeatureAddressVerificationExpiry, or "1d" if unset + * + * @since ZCS 8.8.5 + */ + @ZAttr(id=2127) + public String getFeatureAddressVerificationExpiryAsString() { + return getAttr(Provisioning.A_zimbraFeatureAddressVerificationExpiry, "1d", true); + } + + /** + * Expiry time for end-user email address verification. Must be in valid + * duration format: {digits}{time-unit}. digits: 0-9, time-unit: + * [hmsd]|ms. h - hours, m - minutes, s - seconds, d - days, ms - + * milliseconds. If time unit is not specified, the default is + * s(seconds). + * + * @param zimbraFeatureAddressVerificationExpiry new value + * @throws com.zimbra.common.service.ServiceException if error during update + * + * @since ZCS 8.8.5 + */ + @ZAttr(id=2127) + public void setFeatureAddressVerificationExpiry(String zimbraFeatureAddressVerificationExpiry) throws com.zimbra.common.service.ServiceException { + HashMap attrs = new HashMap(); + attrs.put(Provisioning.A_zimbraFeatureAddressVerificationExpiry, zimbraFeatureAddressVerificationExpiry); + getProvisioning().modifyAttrs(this, attrs); + } + + /** + * Expiry time for end-user email address verification. Must be in valid + * duration format: {digits}{time-unit}. digits: 0-9, time-unit: + * [hmsd]|ms. h - hours, m - minutes, s - seconds, d - days, ms - + * milliseconds. If time unit is not specified, the default is + * s(seconds). + * + * @param zimbraFeatureAddressVerificationExpiry new value + * @param attrs existing map to populate, or null to create a new map + * @return populated map to pass into Provisioning.modifyAttrs + * + * @since ZCS 8.8.5 + */ + @ZAttr(id=2127) + public Map setFeatureAddressVerificationExpiry(String zimbraFeatureAddressVerificationExpiry, Map attrs) { + if (attrs == null) attrs = new HashMap(); + attrs.put(Provisioning.A_zimbraFeatureAddressVerificationExpiry, zimbraFeatureAddressVerificationExpiry); + return attrs; + } + + /** + * Expiry time for end-user email address verification. Must be in valid + * duration format: {digits}{time-unit}. digits: 0-9, time-unit: + * [hmsd]|ms. h - hours, m - minutes, s - seconds, d - days, ms - + * milliseconds. If time unit is not specified, the default is + * s(seconds). + * + * @throws com.zimbra.common.service.ServiceException if error during update + * + * @since ZCS 8.8.5 + */ + @ZAttr(id=2127) + public void unsetFeatureAddressVerificationExpiry() throws com.zimbra.common.service.ServiceException { + HashMap attrs = new HashMap(); + attrs.put(Provisioning.A_zimbraFeatureAddressVerificationExpiry, ""); + getProvisioning().modifyAttrs(this, attrs); + } + + /** + * Expiry time for end-user email address verification. Must be in valid + * duration format: {digits}{time-unit}. digits: 0-9, time-unit: + * [hmsd]|ms. h - hours, m - minutes, s - seconds, d - days, ms - + * milliseconds. If time unit is not specified, the default is + * s(seconds). + * + * @param attrs existing map to populate, or null to create a new map + * @return populated map to pass into Provisioning.modifyAttrs + * + * @since ZCS 8.8.5 + */ + @ZAttr(id=2127) + public Map unsetFeatureAddressVerificationExpiry(Map attrs) { + if (attrs == null) attrs = new HashMap(); + attrs.put(Provisioning.A_zimbraFeatureAddressVerificationExpiry, ""); + return attrs; + } + /** * whether email features and tabs are enabled in the web client if * accessed from the admin console @@ -11055,6 +11239,78 @@ public Map unsetFeatureManageZimlets(Map attrs) { return attrs; } + /** + * Mark messages sent to a forwarding address as read + * + * @return zimbraFeatureMarkMailForwardedAsRead, or false if unset + * + * @since ZCS 8.8.5 + */ + @ZAttr(id=2123) + public boolean isFeatureMarkMailForwardedAsRead() { + return getBooleanAttr(Provisioning.A_zimbraFeatureMarkMailForwardedAsRead, false, true); + } + + /** + * Mark messages sent to a forwarding address as read + * + * @param zimbraFeatureMarkMailForwardedAsRead new value + * @throws com.zimbra.common.service.ServiceException if error during update + * + * @since ZCS 8.8.5 + */ + @ZAttr(id=2123) + public void setFeatureMarkMailForwardedAsRead(boolean zimbraFeatureMarkMailForwardedAsRead) throws com.zimbra.common.service.ServiceException { + HashMap attrs = new HashMap(); + attrs.put(Provisioning.A_zimbraFeatureMarkMailForwardedAsRead, zimbraFeatureMarkMailForwardedAsRead ? Provisioning.TRUE : Provisioning.FALSE); + getProvisioning().modifyAttrs(this, attrs); + } + + /** + * Mark messages sent to a forwarding address as read + * + * @param zimbraFeatureMarkMailForwardedAsRead new value + * @param attrs existing map to populate, or null to create a new map + * @return populated map to pass into Provisioning.modifyAttrs + * + * @since ZCS 8.8.5 + */ + @ZAttr(id=2123) + public Map setFeatureMarkMailForwardedAsRead(boolean zimbraFeatureMarkMailForwardedAsRead, Map attrs) { + if (attrs == null) attrs = new HashMap(); + attrs.put(Provisioning.A_zimbraFeatureMarkMailForwardedAsRead, zimbraFeatureMarkMailForwardedAsRead ? Provisioning.TRUE : Provisioning.FALSE); + return attrs; + } + + /** + * Mark messages sent to a forwarding address as read + * + * @throws com.zimbra.common.service.ServiceException if error during update + * + * @since ZCS 8.8.5 + */ + @ZAttr(id=2123) + public void unsetFeatureMarkMailForwardedAsRead() throws com.zimbra.common.service.ServiceException { + HashMap attrs = new HashMap(); + attrs.put(Provisioning.A_zimbraFeatureMarkMailForwardedAsRead, ""); + getProvisioning().modifyAttrs(this, attrs); + } + + /** + * Mark messages sent to a forwarding address as read + * + * @param attrs existing map to populate, or null to create a new map + * @return populated map to pass into Provisioning.modifyAttrs + * + * @since ZCS 8.8.5 + */ + @ZAttr(id=2123) + public Map unsetFeatureMarkMailForwardedAsRead(Map attrs) { + if (attrs == null) attrs = new HashMap(); + attrs.put(Provisioning.A_zimbraFeatureMarkMailForwardedAsRead, ""); + return attrs; + } + /** * Whether to enable Zimbra Mobile Gateway feature * diff --git a/store/src/java/com/zimbra/cs/account/ZAttrServer.java b/store/src/java/com/zimbra/cs/account/ZAttrServer.java index 0050ef5dd21..921ee4d4f5b 100644 --- a/store/src/java/com/zimbra/cs/account/ZAttrServer.java +++ b/store/src/java/com/zimbra/cs/account/ZAttrServer.java @@ -8769,6 +8769,230 @@ public Map unsetExternalAccountStatusCheckInterval(MapUse getFeatureContactBackupFrequencyAsString to access value as a string. + * + * @see #getFeatureContactBackupFrequencyAsString() + * + * @return zimbraFeatureContactBackupFrequency in millseconds, or 86400000 (1d) if unset + * + * @since ZCS 8.8.5 + */ + @ZAttr(id=2124) + public long getFeatureContactBackupFrequency() { + return getTimeInterval(Provisioning.A_zimbraFeatureContactBackupFrequency, 86400000L, true); + } + + /** + * Sleep time between subsequent contact backups. 0 means that contact + * backup is disabled. . Must be in valid duration format: + * {digits}{time-unit}. digits: 0-9, time-unit: [hmsd]|ms. h - hours, m - + * minutes, s - seconds, d - days, ms - milliseconds. If time unit is not + * specified, the default is s(seconds). + * + * @return zimbraFeatureContactBackupFrequency, or "1d" if unset + * + * @since ZCS 8.8.5 + */ + @ZAttr(id=2124) + public String getFeatureContactBackupFrequencyAsString() { + return getAttr(Provisioning.A_zimbraFeatureContactBackupFrequency, "1d", true); + } + + /** + * Sleep time between subsequent contact backups. 0 means that contact + * backup is disabled. . Must be in valid duration format: + * {digits}{time-unit}. digits: 0-9, time-unit: [hmsd]|ms. h - hours, m - + * minutes, s - seconds, d - days, ms - milliseconds. If time unit is not + * specified, the default is s(seconds). + * + * @param zimbraFeatureContactBackupFrequency new value + * @throws com.zimbra.common.service.ServiceException if error during update + * + * @since ZCS 8.8.5 + */ + @ZAttr(id=2124) + public void setFeatureContactBackupFrequency(String zimbraFeatureContactBackupFrequency) throws com.zimbra.common.service.ServiceException { + HashMap attrs = new HashMap(); + attrs.put(Provisioning.A_zimbraFeatureContactBackupFrequency, zimbraFeatureContactBackupFrequency); + getProvisioning().modifyAttrs(this, attrs); + } + + /** + * Sleep time between subsequent contact backups. 0 means that contact + * backup is disabled. . Must be in valid duration format: + * {digits}{time-unit}. digits: 0-9, time-unit: [hmsd]|ms. h - hours, m - + * minutes, s - seconds, d - days, ms - milliseconds. If time unit is not + * specified, the default is s(seconds). + * + * @param zimbraFeatureContactBackupFrequency new value + * @param attrs existing map to populate, or null to create a new map + * @return populated map to pass into Provisioning.modifyAttrs + * + * @since ZCS 8.8.5 + */ + @ZAttr(id=2124) + public Map setFeatureContactBackupFrequency(String zimbraFeatureContactBackupFrequency, Map attrs) { + if (attrs == null) attrs = new HashMap(); + attrs.put(Provisioning.A_zimbraFeatureContactBackupFrequency, zimbraFeatureContactBackupFrequency); + return attrs; + } + + /** + * Sleep time between subsequent contact backups. 0 means that contact + * backup is disabled. . Must be in valid duration format: + * {digits}{time-unit}. digits: 0-9, time-unit: [hmsd]|ms. h - hours, m - + * minutes, s - seconds, d - days, ms - milliseconds. If time unit is not + * specified, the default is s(seconds). + * + * @throws com.zimbra.common.service.ServiceException if error during update + * + * @since ZCS 8.8.5 + */ + @ZAttr(id=2124) + public void unsetFeatureContactBackupFrequency() throws com.zimbra.common.service.ServiceException { + HashMap attrs = new HashMap(); + attrs.put(Provisioning.A_zimbraFeatureContactBackupFrequency, ""); + getProvisioning().modifyAttrs(this, attrs); + } + + /** + * Sleep time between subsequent contact backups. 0 means that contact + * backup is disabled. . Must be in valid duration format: + * {digits}{time-unit}. digits: 0-9, time-unit: [hmsd]|ms. h - hours, m - + * minutes, s - seconds, d - days, ms - milliseconds. If time unit is not + * specified, the default is s(seconds). + * + * @param attrs existing map to populate, or null to create a new map + * @return populated map to pass into Provisioning.modifyAttrs + * + * @since ZCS 8.8.5 + */ + @ZAttr(id=2124) + public Map unsetFeatureContactBackupFrequency(Map attrs) { + if (attrs == null) attrs = new HashMap(); + attrs.put(Provisioning.A_zimbraFeatureContactBackupFrequency, ""); + return attrs; + } + + /** + * Duration for which the backups should be preserved. . Must be in valid + * duration format: {digits}{time-unit}. digits: 0-9, time-unit: + * [hmsd]|ms. h - hours, m - minutes, s - seconds, d - days, ms - + * milliseconds. If time unit is not specified, the default is + * s(seconds). + * + *

Use getFeatureContactBackupLifeTimeAsString to access value as a string. + * + * @see #getFeatureContactBackupLifeTimeAsString() + * + * @return zimbraFeatureContactBackupLifeTime in millseconds, or 1296000000 (15d) if unset + * + * @since ZCS 8.8.5 + */ + @ZAttr(id=2125) + public long getFeatureContactBackupLifeTime() { + return getTimeInterval(Provisioning.A_zimbraFeatureContactBackupLifeTime, 1296000000L, true); + } + + /** + * Duration for which the backups should be preserved. . Must be in valid + * duration format: {digits}{time-unit}. digits: 0-9, time-unit: + * [hmsd]|ms. h - hours, m - minutes, s - seconds, d - days, ms - + * milliseconds. If time unit is not specified, the default is + * s(seconds). + * + * @return zimbraFeatureContactBackupLifeTime, or "15d" if unset + * + * @since ZCS 8.8.5 + */ + @ZAttr(id=2125) + public String getFeatureContactBackupLifeTimeAsString() { + return getAttr(Provisioning.A_zimbraFeatureContactBackupLifeTime, "15d", true); + } + + /** + * Duration for which the backups should be preserved. . Must be in valid + * duration format: {digits}{time-unit}. digits: 0-9, time-unit: + * [hmsd]|ms. h - hours, m - minutes, s - seconds, d - days, ms - + * milliseconds. If time unit is not specified, the default is + * s(seconds). + * + * @param zimbraFeatureContactBackupLifeTime new value + * @throws com.zimbra.common.service.ServiceException if error during update + * + * @since ZCS 8.8.5 + */ + @ZAttr(id=2125) + public void setFeatureContactBackupLifeTime(String zimbraFeatureContactBackupLifeTime) throws com.zimbra.common.service.ServiceException { + HashMap attrs = new HashMap(); + attrs.put(Provisioning.A_zimbraFeatureContactBackupLifeTime, zimbraFeatureContactBackupLifeTime); + getProvisioning().modifyAttrs(this, attrs); + } + + /** + * Duration for which the backups should be preserved. . Must be in valid + * duration format: {digits}{time-unit}. digits: 0-9, time-unit: + * [hmsd]|ms. h - hours, m - minutes, s - seconds, d - days, ms - + * milliseconds. If time unit is not specified, the default is + * s(seconds). + * + * @param zimbraFeatureContactBackupLifeTime new value + * @param attrs existing map to populate, or null to create a new map + * @return populated map to pass into Provisioning.modifyAttrs + * + * @since ZCS 8.8.5 + */ + @ZAttr(id=2125) + public Map setFeatureContactBackupLifeTime(String zimbraFeatureContactBackupLifeTime, Map attrs) { + if (attrs == null) attrs = new HashMap(); + attrs.put(Provisioning.A_zimbraFeatureContactBackupLifeTime, zimbraFeatureContactBackupLifeTime); + return attrs; + } + + /** + * Duration for which the backups should be preserved. . Must be in valid + * duration format: {digits}{time-unit}. digits: 0-9, time-unit: + * [hmsd]|ms. h - hours, m - minutes, s - seconds, d - days, ms - + * milliseconds. If time unit is not specified, the default is + * s(seconds). + * + * @throws com.zimbra.common.service.ServiceException if error during update + * + * @since ZCS 8.8.5 + */ + @ZAttr(id=2125) + public void unsetFeatureContactBackupLifeTime() throws com.zimbra.common.service.ServiceException { + HashMap attrs = new HashMap(); + attrs.put(Provisioning.A_zimbraFeatureContactBackupLifeTime, ""); + getProvisioning().modifyAttrs(this, attrs); + } + + /** + * Duration for which the backups should be preserved. . Must be in valid + * duration format: {digits}{time-unit}. digits: 0-9, time-unit: + * [hmsd]|ms. h - hours, m - minutes, s - seconds, d - days, ms - + * milliseconds. If time unit is not specified, the default is + * s(seconds). + * + * @param attrs existing map to populate, or null to create a new map + * @return populated map to pass into Provisioning.modifyAttrs + * + * @since ZCS 8.8.5 + */ + @ZAttr(id=2125) + public Map unsetFeatureContactBackupLifeTime(Map attrs) { + if (attrs == null) attrs = new HashMap(); + attrs.put(Provisioning.A_zimbraFeatureContactBackupLifeTime, ""); + return attrs; + } + /** * Maximum size in bytes for file uploads * @@ -35238,7 +35462,9 @@ public Map unsetMtaVirtualMailboxMaps(Map attrs) { } /** - * Whether to enable old zimbra network admin module. + * Deprecated since: 8.8.5. This attribute has been renamed to + * zimbraNetworkAdminNGEnabled. Orig desc: Whether to enable old zimbra + * network admin module. * * @return zimbraNetworkAdminEnabled, or true if unset * @@ -35250,7 +35476,9 @@ public boolean isNetworkAdminEnabled() { } /** - * Whether to enable old zimbra network admin module. + * Deprecated since: 8.8.5. This attribute has been renamed to + * zimbraNetworkAdminNGEnabled. Orig desc: Whether to enable old zimbra + * network admin module. * * @param zimbraNetworkAdminEnabled new value * @throws com.zimbra.common.service.ServiceException if error during update @@ -35265,7 +35493,9 @@ public void setNetworkAdminEnabled(boolean zimbraNetworkAdminEnabled) throws com } /** - * Whether to enable old zimbra network admin module. + * Deprecated since: 8.8.5. This attribute has been renamed to + * zimbraNetworkAdminNGEnabled. Orig desc: Whether to enable old zimbra + * network admin module. * * @param zimbraNetworkAdminEnabled new value * @param attrs existing map to populate, or null to create a new map @@ -35281,7 +35511,9 @@ public Map setNetworkAdminEnabled(boolean zimbraNetworkAdminEnabl } /** - * Whether to enable old zimbra network admin module. + * Deprecated since: 8.8.5. This attribute has been renamed to + * zimbraNetworkAdminNGEnabled. Orig desc: Whether to enable old zimbra + * network admin module. * * @throws com.zimbra.common.service.ServiceException if error during update * @@ -35295,7 +35527,9 @@ public void unsetNetworkAdminEnabled() throws com.zimbra.common.service.ServiceE } /** - * Whether to enable old zimbra network admin module. + * Deprecated since: 8.8.5. This attribute has been renamed to + * zimbraNetworkAdminNGEnabled. Orig desc: Whether to enable old zimbra + * network admin module. * * @param attrs existing map to populate, or null to create a new map * @return populated map to pass into Provisioning.modifyAttrs @@ -35309,16 +35543,88 @@ public Map unsetNetworkAdminEnabled(Map attrs) { return attrs; } + /** + * Whether to enable zimbra network new generation admin module. + * + * @return zimbraNetworkAdminNGEnabled, or false if unset + * + * @since ZCS 8.8.5 + */ + @ZAttr(id=2130) + public boolean isNetworkAdminNGEnabled() { + return getBooleanAttr(Provisioning.A_zimbraNetworkAdminNGEnabled, false, true); + } + + /** + * Whether to enable zimbra network new generation admin module. + * + * @param zimbraNetworkAdminNGEnabled new value + * @throws com.zimbra.common.service.ServiceException if error during update + * + * @since ZCS 8.8.5 + */ + @ZAttr(id=2130) + public void setNetworkAdminNGEnabled(boolean zimbraNetworkAdminNGEnabled) throws com.zimbra.common.service.ServiceException { + HashMap attrs = new HashMap(); + attrs.put(Provisioning.A_zimbraNetworkAdminNGEnabled, zimbraNetworkAdminNGEnabled ? Provisioning.TRUE : Provisioning.FALSE); + getProvisioning().modifyAttrs(this, attrs); + } + + /** + * Whether to enable zimbra network new generation admin module. + * + * @param zimbraNetworkAdminNGEnabled new value + * @param attrs existing map to populate, or null to create a new map + * @return populated map to pass into Provisioning.modifyAttrs + * + * @since ZCS 8.8.5 + */ + @ZAttr(id=2130) + public Map setNetworkAdminNGEnabled(boolean zimbraNetworkAdminNGEnabled, Map attrs) { + if (attrs == null) attrs = new HashMap(); + attrs.put(Provisioning.A_zimbraNetworkAdminNGEnabled, zimbraNetworkAdminNGEnabled ? Provisioning.TRUE : Provisioning.FALSE); + return attrs; + } + + /** + * Whether to enable zimbra network new generation admin module. + * + * @throws com.zimbra.common.service.ServiceException if error during update + * + * @since ZCS 8.8.5 + */ + @ZAttr(id=2130) + public void unsetNetworkAdminNGEnabled() throws com.zimbra.common.service.ServiceException { + HashMap attrs = new HashMap(); + attrs.put(Provisioning.A_zimbraNetworkAdminNGEnabled, ""); + getProvisioning().modifyAttrs(this, attrs); + } + + /** + * Whether to enable zimbra network new generation admin module. + * + * @param attrs existing map to populate, or null to create a new map + * @return populated map to pass into Provisioning.modifyAttrs + * + * @since ZCS 8.8.5 + */ + @ZAttr(id=2130) + public Map unsetNetworkAdminNGEnabled(Map attrs) { + if (attrs == null) attrs = new HashMap(); + attrs.put(Provisioning.A_zimbraNetworkAdminNGEnabled, ""); + return attrs; + } + /** * Whether to enable zimbra network new generation mobile sync module. * - * @return zimbraNetworkMobileNGEnabled, or true if unset + * @return zimbraNetworkMobileNGEnabled, or false if unset * * @since ZCS 8.8.0 */ @ZAttr(id=2118) public boolean isNetworkMobileNGEnabled() { - return getBooleanAttr(Provisioning.A_zimbraNetworkMobileNGEnabled, true, true); + return getBooleanAttr(Provisioning.A_zimbraNetworkMobileNGEnabled, false, true); } /** diff --git a/store/src/java/com/zimbra/cs/account/callback/ContactBackupFeature.java b/store/src/java/com/zimbra/cs/account/callback/ContactBackupFeature.java new file mode 100644 index 00000000000..4409966af81 --- /dev/null +++ b/store/src/java/com/zimbra/cs/account/callback/ContactBackupFeature.java @@ -0,0 +1,41 @@ +/* + * ***** BEGIN LICENSE BLOCK ***** + * Zimbra Collaboration Suite Server + * Copyright (C) 2017 Synacor, Inc. + * + * This program is free software: you can redistribute it and/or modify it under + * the terms of the GNU General Public License as published by the Free Software Foundation, + * version 2 of the License. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * You should have received a copy of the GNU General Public License along with this program. + * If not, see . + * ***** END LICENSE BLOCK ***** + */ +package com.zimbra.cs.account.callback; + +import java.util.Map; + +import com.zimbra.common.service.ServiceException; +import com.zimbra.cs.account.AttributeCallback; +import com.zimbra.cs.account.Entry; + +public class ContactBackupFeature extends AttributeCallback { + + @SuppressWarnings("rawtypes") + @Override + public void preModify(CallbackContext context, String attrName, Object attrValue, Map attrsToModify, Entry entry) + throws ServiceException { + // TODO Populate while implementing contact backup feature + + } + + @Override + public void postModify(CallbackContext context, String attrName, Entry entry) { + // TODO Populate while implementing contact backup feature + + } + +} diff --git a/store/src/java/com/zimbra/cs/account/ldap/entry/LdapDataSource.java b/store/src/java/com/zimbra/cs/account/ldap/entry/LdapDataSource.java index 9fb7c0c8ddd..d020eb3625f 100644 --- a/store/src/java/com/zimbra/cs/account/ldap/entry/LdapDataSource.java +++ b/store/src/java/com/zimbra/cs/account/ldap/entry/LdapDataSource.java @@ -18,17 +18,16 @@ import java.util.List; -import com.zimbra.soap.admin.type.DataSourceType; import com.zimbra.common.service.ServiceException; import com.zimbra.common.util.ZimbraLog; import com.zimbra.cs.account.Account; import com.zimbra.cs.account.AttributeClass; import com.zimbra.cs.account.DataSource; import com.zimbra.cs.account.Provisioning; +import com.zimbra.cs.ldap.IAttributes.CheckBinary; import com.zimbra.cs.ldap.LdapException; import com.zimbra.cs.ldap.ZAttributes; -import com.zimbra.cs.ldap.IAttributes.CheckBinary; -import com.zimbra.cs.ldap.ZSearchResultEntry; +import com.zimbra.soap.admin.type.DataSourceType; /** * @@ -64,7 +63,14 @@ public static String getObjectClass(DataSourceType type) { case gal: return AttributeClass.OC_zimbraGalDataSource; default: - return null; + /* + * All DataSource objects that are not pop3, imap, rss or gal are considered 'generic' + * and are represented by 'dataSource' objectClass in LDAP. + * WARNING: avoid adding more LDAP object classes for new implementations of data sources. Use dataSource object class + * instead and keep all specifics of implementation in DataImport. + * Any configuration that is not covered by existing attributes can be stored in zimbraDataSourceAttribute or outside of LDAP. + */ + return AttributeClass.OC_zimbraDataSource; } } @@ -78,15 +84,18 @@ static DataSourceType getObjectType(ZAttributes attrs) throws ServiceException { } List attr = attrs.getMultiAttrStringAsList(Provisioning.A_objectClass, CheckBinary.NOCHECK); - if (attr.contains(AttributeClass.OC_zimbraPop3DataSource)) + if (attr.contains(AttributeClass.OC_zimbraPop3DataSource)) { return DataSourceType.pop3; - else if (attr.contains(AttributeClass.OC_zimbraImapDataSource)) + } else if (attr.contains(AttributeClass.OC_zimbraImapDataSource)) { return DataSourceType.imap; - else if (attr.contains(AttributeClass.OC_zimbraRssDataSource)) + } else if (attr.contains(AttributeClass.OC_zimbraRssDataSource)) { return DataSourceType.rss; - else if (attr.contains(AttributeClass.OC_zimbraGalDataSource)) + } else if (attr.contains(AttributeClass.OC_zimbraGalDataSource)) { return DataSourceType.gal; - else + } else if (attr.contains(AttributeClass.OC_zimbraDataSource)) { + return DataSourceType.unknown; + } else { throw ServiceException.FAILURE("unable to determine data source type from object class", null); + } } } diff --git a/store/src/java/com/zimbra/cs/datasource/DataSourceManager.java b/store/src/java/com/zimbra/cs/datasource/DataSourceManager.java index b017b8d2614..ba91ae1e4f9 100644 --- a/store/src/java/com/zimbra/cs/datasource/DataSourceManager.java +++ b/store/src/java/com/zimbra/cs/datasource/DataSourceManager.java @@ -210,15 +210,39 @@ public DataImport getDataImport(DataSource ds, boolean test) throws ServiceExcep } catch (ClassNotFoundException x) { cmdClass = ExtensionUtil.findClass(className); } - Constructor constructor = cmdClass.getConstructor(new Class[] {DataSource.class}); - return (DataImport) constructor.newInstance(ds); + if(cmdClass != null) { + Constructor constructor = cmdClass.getConstructor(new Class[] {DataSource.class}); + return (DataImport) constructor.newInstance(ds); + } + ZimbraLog.datasource.warn("Could not find DataImport class: %s for xsync dataSource %s Check your classpath.", className, ds.getName()); + return null; } } catch (Exception x) { ZimbraLog.datasource.warn("Failed instantiating xsync class: %s", ds, x); } default: - // yab is handled by OfflineDataSourceManager - throw new IllegalArgumentException("Unknown data import type: " + ds.getType()); + String className = ds.getDataSourceImportClassName(); + if (className != null && className.length() > 0) { + try { + Class cmdClass; + try { + cmdClass = Class.forName(className); + } catch (ClassNotFoundException x) { + cmdClass = ExtensionUtil.findClass(className); + } + if(cmdClass != null) { + Constructor constructor = cmdClass.getConstructor(new Class[] {DataSource.class}); + return (DataImport) constructor.newInstance(ds); + } + ZimbraLog.datasource.warn("Could not find DataImport class: %s for dataSource %s Check your classpath.", className, ds.getName()); + return null; + } catch (Exception x) { + ZimbraLog.datasource.warn("Caught an exception while instantiating DataImport class: %s", ds, x); + return null; + } + } else { + throw new IllegalArgumentException(String.format("Cannot create datasource %s with unknown data import type: %s and undefined zimbraDataSourceImportClassName", ds.getName(), ds.getType())); + } } } diff --git a/store/src/java/com/zimbra/cs/dav/DavContext.java b/store/src/java/com/zimbra/cs/dav/DavContext.java index 631bf2e5bbb..f7c46bb2591 100644 --- a/store/src/java/com/zimbra/cs/dav/DavContext.java +++ b/store/src/java/com/zimbra/cs/dav/DavContext.java @@ -677,7 +677,11 @@ public ZMailbox getZMailbox(Account acct) throws ServiceException { zoptions.setNoSession(true); zoptions.setTargetAccount(acct.getId()); zoptions.setTargetAccountBy(Key.AccountBy.id); - return ZMailbox.getMailbox(zoptions); + ZMailbox zmbx = ZMailbox.getMailbox(zoptions); + if (zmbx != null) { + zmbx.setName(acct.getName()); /* need this when logging in using another user's auth */ + } + return zmbx; } public String getNewName() throws DavException { diff --git a/store/src/java/com/zimbra/cs/dav/property/ResourceProperty.java b/store/src/java/com/zimbra/cs/dav/property/ResourceProperty.java index 0e54dc38f83..af44145c03a 100644 --- a/store/src/java/com/zimbra/cs/dav/property/ResourceProperty.java +++ b/store/src/java/com/zimbra/cs/dav/property/ResourceProperty.java @@ -171,7 +171,7 @@ public void setVisible(boolean v) { protected Element createHref(String path) { Element e = org.dom4j.DocumentHelper.createElement(DavElements.E_HREF); - e.setText(HttpUtil.urlEscape(path)); + e.setText(HttpUtil.urlEscape(path).replaceAll("@", "%40")); return e; } diff --git a/store/src/java/com/zimbra/cs/dav/resource/AddressbookCollection.java b/store/src/java/com/zimbra/cs/dav/resource/AddressbookCollection.java index a1c87f10e46..1fa04f9b8ac 100644 --- a/store/src/java/com/zimbra/cs/dav/resource/AddressbookCollection.java +++ b/store/src/java/com/zimbra/cs/dav/resource/AddressbookCollection.java @@ -46,49 +46,54 @@ public class AddressbookCollection extends Collection { + private static QName[] SUPPORTED_REPORTS = { + DavElements.CardDav.E_ADDRESSBOOK_MULTIGET, + DavElements.CardDav.E_ADDRESSBOOK_QUERY, + DavElements.E_ACL_PRINCIPAL_PROP_SET, + DavElements.E_PRINCIPAL_MATCH, + DavElements.E_PRINCIPAL_PROPERTY_SEARCH, + DavElements.E_PRINCIPAL_SEARCH_PROPERTY_SET, + DavElements.E_EXPAND_PROPERTY + }; + public AddressbookCollection(DavContext ctxt, Folder f) throws DavException, ServiceException { super(ctxt, f); + setupAddressbookCollection(this, ctxt, f); + } + + protected static void setupAddressbookCollection(Collection coll, DavContext ctxt, Folder f) + throws ServiceException { Account acct = f.getAccount(); Locale lc = acct.getLocale(); - String description = L10nUtil.getMessage(MsgKey.carddavAddressbookDescription, lc, acct.getAttr(Provisioning.A_displayName), f.getName()); + String description = L10nUtil.getMessage(MsgKey.carddavAddressbookDescription, + lc, acct.getAttr(Provisioning.A_displayName), f.getName()); ResourceProperty rp = new ResourceProperty(DavElements.CardDav.E_ADDRESSBOOK_DESCRIPTION); rp.setMessageLocale(lc); rp.setStringValue(description); rp.setProtected(false); - addProperty(rp); - addProperty(ResourceProperty.AddMember.create(UrlNamespace.getFolderUrl(ctxt.getUser(), f.getName()))); + coll.addProperty(rp); + coll.addProperty(ResourceProperty.AddMember.create(UrlNamespace.getFolderUrl(ctxt.getUser(), + f.getName()))); rp = new ResourceProperty(DavElements.CardDav.E_SUPPORTED_ADDRESS_DATA); Element vcard = rp.addChild(DavElements.CardDav.E_ADDRESS_DATA); vcard.addAttribute(DavElements.P_CONTENT_TYPE, DavProtocol.VCARD_CONTENT_TYPE); vcard.addAttribute(DavElements.P_VERSION, DavProtocol.VCARD_VERSION); rp.setProtected(true); - addProperty(rp); - long maxSize = Provisioning.getInstance().getLocalServer().getLongAttr(Provisioning.A_zimbraFileUploadMaxSize, -1); + coll.addProperty(rp); + long maxSize = Provisioning.getInstance().getLocalServer().getLongAttr( + Provisioning.A_zimbraFileUploadMaxSize, -1); if (maxSize > 0) { rp = new ResourceProperty(DavElements.CardDav.E_MAX_RESOURCE_SIZE_ADDRESSBOOK); rp.setStringValue(Long.toString(maxSize)); rp.setProtected(true); - addProperty(rp); + coll.addProperty(rp); } if (f.getDefaultView() == MailItem.Type.CONTACT) { - addResourceType(DavElements.CardDav.E_ADDRESSBOOK); + coll.addResourceType(DavElements.CardDav.E_ADDRESSBOOK); } - mCtag = CtagInfo.makeCtag(f); - setProperty(DavElements.E_GETCTAG, mCtag); + coll.setProperty(DavElements.E_GETCTAG, CtagInfo.makeCtag(f)); } - private final String mCtag; - - private static QName[] SUPPORTED_REPORTS = { - DavElements.CardDav.E_ADDRESSBOOK_MULTIGET, - DavElements.CardDav.E_ADDRESSBOOK_QUERY, - DavElements.E_ACL_PRINCIPAL_PROP_SET, - DavElements.E_PRINCIPAL_MATCH, - DavElements.E_PRINCIPAL_PROPERTY_SEARCH, - DavElements.E_PRINCIPAL_SEARCH_PROPERTY_SET, - DavElements.E_EXPAND_PROPERTY - }; - @Override protected QName[] getSupportedReports() { return SUPPORTED_REPORTS; diff --git a/store/src/java/com/zimbra/cs/dav/resource/MailItemResource.java b/store/src/java/com/zimbra/cs/dav/resource/MailItemResource.java index cd82f676e70..938e4cbf5c9 100644 --- a/store/src/java/com/zimbra/cs/dav/resource/MailItemResource.java +++ b/store/src/java/com/zimbra/cs/dav/resource/MailItemResource.java @@ -254,7 +254,11 @@ private static ZMailbox getZMailbox(DavContext ctxt, Collection col) throws Serv zoptions.setNoSession(true); zoptions.setTargetAccount(acct.getId()); zoptions.setTargetAccountBy(Key.AccountBy.id); - return ZMailbox.getMailbox(zoptions); + ZMailbox zmbx = ZMailbox.getMailbox(zoptions); + if (zmbx != null) { + zmbx.setName(acct.getName()); /* need this when logging in using another user's auth */ + } + return zmbx; } private void deleteDestinationItem(DavContext ctxt, Collection dest, int id) throws ServiceException, DavException { Mailbox mbox = getMailbox(ctxt); diff --git a/store/src/java/com/zimbra/cs/dav/resource/RemoteAddressbookCollection.java b/store/src/java/com/zimbra/cs/dav/resource/RemoteAddressbookCollection.java new file mode 100644 index 00000000000..bdd9c613c53 --- /dev/null +++ b/store/src/java/com/zimbra/cs/dav/resource/RemoteAddressbookCollection.java @@ -0,0 +1,35 @@ +/* + * ***** BEGIN LICENSE BLOCK ***** + * Zimbra Collaboration Suite Server + * Copyright (C) 2017 Synacor, Inc. + * + * This program is free software: you can redistribute it and/or modify it under + * the terms of the GNU General Public License as published by the Free Software Foundation, + * version 2 of the License. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * You should have received a copy of the GNU General Public License along with this program. + * If not, see . + * ***** END LICENSE BLOCK ***** + */ +package com.zimbra.cs.dav.resource; + +import com.zimbra.common.service.ServiceException; +import com.zimbra.cs.dav.DavContext; +import com.zimbra.cs.dav.DavException; +import com.zimbra.cs.mailbox.Mountpoint; + +public class RemoteAddressbookCollection extends RemoteCollection { + + public RemoteAddressbookCollection(DavContext ctxt, Mountpoint mp) + throws DavException, ServiceException { + super(ctxt, mp); + AddressbookCollection.setupAddressbookCollection(this, ctxt, mp); + } + + public short getRights() { + return mRights; + } +} diff --git a/store/src/java/com/zimbra/cs/dav/resource/RemoteCollection.java b/store/src/java/com/zimbra/cs/dav/resource/RemoteCollection.java index c928a45b1d0..0774ad2c301 100644 --- a/store/src/java/com/zimbra/cs/dav/resource/RemoteCollection.java +++ b/store/src/java/com/zimbra/cs/dav/resource/RemoteCollection.java @@ -21,6 +21,8 @@ import javax.servlet.http.HttpServletResponse; +import com.zimbra.client.ZFolder; +import com.zimbra.client.ZMailbox; import com.zimbra.common.account.Key; import com.zimbra.common.auth.ZAuthToken; import com.zimbra.common.service.ServiceException; @@ -40,8 +42,6 @@ import com.zimbra.cs.service.AuthProvider; import com.zimbra.cs.service.util.ItemId; import com.zimbra.cs.util.AccountUtil; -import com.zimbra.client.ZFolder; -import com.zimbra.client.ZMailbox; public class RemoteCollection extends Collection { @@ -78,7 +78,7 @@ public RemoteCollection(DavContext ctxt, String path, Account user) throws DavEx if (zview != null) view = MailItem.Type.of(zview.name()); } - + @Override public void delete(DavContext ctxt) throws DavException { throw new DavException("cannot delete this resource", HttpServletResponse.SC_FORBIDDEN, null); @@ -101,7 +101,11 @@ static ZMailbox getRemoteMailbox(ZAuthToken zat, String ownerId) throws ServiceE zoptions.setNoSession(true); zoptions.setTargetAccount(ownerId); zoptions.setTargetAccountBy(Key.AccountBy.id); - return ZMailbox.getMailbox(zoptions); + ZMailbox zmbx = ZMailbox.getMailbox(zoptions); + if (zmbx != null) { + zmbx.setName(target.getName()); /* need this when logging in using another user's auth */ + } + return zmbx; } protected void getMountpointTarget(DavContext ctxt) throws ServiceException { ZAuthToken zat = AuthProvider.getAuthToken(ctxt.getAuthAccount()).toZAuthToken(); diff --git a/store/src/java/com/zimbra/cs/dav/resource/UrlNamespace.java b/store/src/java/com/zimbra/cs/dav/resource/UrlNamespace.java index 0061b839096..381c0bb915c 100644 --- a/store/src/java/com/zimbra/cs/dav/resource/UrlNamespace.java +++ b/store/src/java/com/zimbra/cs/dav/resource/UrlNamespace.java @@ -60,6 +60,18 @@ */ public class UrlNamespace { public static final String ATTACHMENTS_PREFIX = "/attachments"; + public static final String PRINCIPALS = "principals"; + public static final String PRINCIPAL_USERS = "users"; + public static final String PRINCIPALS_PATH = "/" + PRINCIPALS + "/" + PRINCIPAL_USERS + "/"; + + public static final String ACL_USER = PRINCIPALS_PATH; + public static final String ACL_GUEST = "/" + PRINCIPALS + "/" + "guests" + "/"; + public static final String ACL_GROUP = "/" + PRINCIPALS + "/" + "groups" + "/"; + public static final String ACL_COS = "/" + PRINCIPALS + "/" + "cos" + "/"; + public static final String ACL_DOMAIN = "/" + PRINCIPALS + "/" + "domain" + "/"; + + private static Map ,Pair>sRenamedResourceMap = + MapUtil.newLruMap(100); public static class UrlComponents { public String user; @@ -72,7 +84,8 @@ public static class UrlComponents { * @return user and path info as UrlComponents */ - public static UrlComponents parseUrl(String url) { + public static UrlComponents parseUrl(String urlToParse) { + String url = urlToParse; UrlComponents uc = new UrlComponents(); int index = url.indexOf(DavServlet.DAV_PATH); @@ -200,7 +213,7 @@ public static java.util.Collection getResources( DavContext ctxt, String user, String path, boolean includeChildren) throws DavException { ArrayList rss = new ArrayList(); - if (user.equals("")) { + if ("".equals(user)) { try { rss.add(new Principal(ctxt.getAuthAccount(), DavServlet.DAV_PATH)); return rss; @@ -254,16 +267,6 @@ public static DavResource getResourceByItemId(DavContext ctxt, String user, int return getResourceFromMailItem(ctxt, item); } - public static final String PRINCIPALS = "principals"; - public static final String PRINCIPAL_USERS = "users"; - public static final String PRINCIPALS_PATH = "/" + PRINCIPALS + "/" + PRINCIPAL_USERS + "/"; - - public static final String ACL_USER = PRINCIPALS_PATH; - public static final String ACL_GUEST = "/" + PRINCIPALS + "/" + "guests" + "/"; - public static final String ACL_GROUP = "/" + PRINCIPALS + "/" + "groups" + "/"; - public static final String ACL_COS = "/" + PRINCIPALS + "/" + "cos" + "/"; - public static final String ACL_DOMAIN = "/" + PRINCIPALS + "/" + "domain" + "/"; - /* RFC 3744 */ public static String getAclUrl(String principal, String type) throws DavException { Account account = null; @@ -276,7 +279,7 @@ public static String getAclUrl(String principal, String type) throws DavExceptio buf.append(account.getName()); else buf.append(principal); - return getAbsoluteUrl(null, buf.toString()); + return getAbsoluteUrl(null, buf.toString().replaceAll("@", "%40")); } catch (ServiceException e) { throw new DavException("cannot create ACL URL for principal "+principal, HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e); } @@ -337,13 +340,21 @@ public static String getCalendarProxyWriteUrl(Account authAccount, Account targe } public static String getPrincipalUrl(String user) { - return HttpUtil.urlEscape(PRINCIPALS_PATH + user + "/"); + return HttpUtil.urlEscape(PRINCIPALS_PATH + user + "/").replaceAll("@", "%40"); } public static String getPrincipalCollectionUrl(Account acct) throws ServiceException { return HttpUtil.urlEscape(PRINCIPALS_PATH); } + public static String getCalendarHomeSetUrl(String authUser) { + return DavServlet.DAV_PATH + "/" + authUser.replaceAll("@", "%40"); + } + + public static String getAddressbookHomeSetUrl(String authUser) { + return getCalendarHomeSetUrl(authUser); + } + public static String getSchedulingInboxUrl(String authUser, String user) { StringBuilder url = new StringBuilder(); // always use authenticated user's inbox. @@ -388,8 +399,6 @@ private static String getAbsoluteUrl(Account user, String path) throws ServiceEx return DavServlet.getServiceUrl(server, domain, path); } - private static Map ,Pair>sRenamedResourceMap = MapUtil.newLruMap(100); - public static void addToRenamedResource(String user, String path, DavResource rsc) { synchronized (sRenamedResourceMap) { sRenamedResourceMap.put(new Pair(user, path.toLowerCase()), @@ -456,7 +465,7 @@ private static DavResource getMailItemResource(DavContext ctxt, String user, Str MailItem item = null; // simple case. root folder or if id is specified. - if (path.equals("/")) { + if ("/".equals(path)) { item = mbox.getFolderByPath(octxt, "/"); } else if (id > 0) { item = mbox.getItemById(octxt, id, MailItem.Type.UNKNOWN); @@ -599,8 +608,11 @@ public static DavResource getResourceFromMailItem(DavContext ctxt, MailItem item Mountpoint mp = (Mountpoint) item; viewType = mp.getDefaultView(); // don't expose mounted calendars when using iCal style delegation model. - if (!ctxt.useIcalDelegation() && (viewType == MailItem.Type.APPOINTMENT || viewType == MailItem.Type.TASK)) { + if (!ctxt.useIcalDelegation() && + (viewType == MailItem.Type.APPOINTMENT || viewType == MailItem.Type.TASK)) { resource = new RemoteCalendarCollection(ctxt, mp); + } else if (viewType == MailItem.Type.CONTACT) { + resource = new RemoteAddressbookCollection(ctxt, mp); } else { resource = new RemoteCollection(ctxt, mp); } @@ -633,9 +645,10 @@ public static DavResource getResourceFromMailItem(DavContext ctxt, MailItem item case CONTACT : resource = new AddressObject(ctxt, (Contact)item); break; + default: + break; } } catch (ServiceException e) { - resource = null; ZimbraLog.dav.info("cannot create DavResource", e); } return resource; @@ -714,6 +727,7 @@ private static DavResource getPhantomResource(DavContext ctxt, String user) thro break; default: resource = null; + break; } return resource; diff --git a/store/src/java/com/zimbra/cs/dav/service/DavServlet.java b/store/src/java/com/zimbra/cs/dav/service/DavServlet.java index a60cd88478c..859e04df143 100644 --- a/store/src/java/com/zimbra/cs/dav/service/DavServlet.java +++ b/store/src/java/com/zimbra/cs/dav/service/DavServlet.java @@ -112,6 +112,31 @@ public class DavServlet extends ZimbraServlet { private static Map sMethods; + private static final Set PROXY_REQUEST_HEADERS = ImmutableSet.of( + DavProtocol.HEADER_DAV, + DavProtocol.HEADER_DEPTH, + DavProtocol.HEADER_CONTENT_TYPE, + DavProtocol.HEADER_ETAG, + DavProtocol.HEADER_IF_MATCH, + DavProtocol.HEADER_OVERWRITE, + DavProtocol.HEADER_DESTINATION); + + private static final Set IGNORABLE_PROXY_REQUEST_HEADERS = ImmutableSet.of( + DavProtocol.HEADER_AUTHORIZATION, + DavProtocol.HEADER_HOST, + DavProtocol.HEADER_USER_AGENT, + DavProtocol.HEADER_CONTENT_LENGTH); + + private static final Set PROXY_RESPONSE_HEADERS = ImmutableSet.of( + DavProtocol.HEADER_DAV, + DavProtocol.HEADER_ALLOW, + DavProtocol.HEADER_CONTENT_TYPE, + DavProtocol.HEADER_ETAG, + DavProtocol.HEADER_LOCATION); + + private static final Set IGNORABLE_PROXY_RESPONSE_HEADERS = ImmutableSet.of( + DavProtocol.HEADER_DATE, + DavProtocol.HEADER_CONTENT_LENGTH); @Override public void init() throws ServletException { super.init(); @@ -353,11 +378,9 @@ public void service(HttpServletRequest req, HttpServletResponse resp) throws Ser try { Upload upload = ctxt.getUpload(); if (upload.getSize() > 0 && upload.getContentType().startsWith("text")) { - if (ZimbraLog.dav.isDebugEnabled()) { - StringBuilder logMsg = new StringBuilder("REQUEST\n").append( - new String(ByteUtil.readInput(upload.getInputStream(), -1, 20480), "UTF-8")); - ZimbraLog.dav.debug(logMsg.toString()); - } + StringBuilder logMsg = new StringBuilder("REQUEST\n").append( + new String(ByteUtil.readInput(upload.getInputStream(), -1, 20480), "UTF-8")); + ZimbraLog.dav.debug(logMsg.toString()); } } catch (DavException de) { throw de; @@ -405,11 +428,10 @@ else if (e.getStatus() == HttpServletResponse.SC_MOVED_TEMPORARILY || } catch (IllegalStateException ise) { ZimbraLog.dav.debug("can't write error msg", ise); } + } catch (MailServiceException.NoSuchItemException nsie) { + sendError(resp, HttpServletResponse.SC_NOT_FOUND, ctxt.getUri()+" not found", null, Level.info); + return; } catch (ServiceException e) { - if (e instanceof MailServiceException.NoSuchItemException) { - sendError(resp, HttpServletResponse.SC_NOT_FOUND, ctxt.getUri()+" not found", null, Level.info); - return; - } sendError(resp, HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "error handling method "+method.getName(), e); } catch (Exception e) { try { @@ -431,7 +453,7 @@ public static String getDavUrl(String user) throws DavException, ServiceExceptio Account account = prov.get(AccountBy.name, user); if (account == null) throw new DavException("unknown user "+user, HttpServletResponse.SC_NOT_FOUND, null); - return getServiceUrl(account, DAV_PATH); + return getServiceUrl(account, DAV_PATH).replaceAll("@", "%40"); } private boolean isCtagRequest(DavContext ctxt) throws DavException { @@ -455,13 +477,13 @@ private boolean isCtagRequest(DavContext ctxt) throws DavException { } private static class CacheStates { - boolean ctagCacheEnabled = MemcachedConnector.isConnected(); - boolean gzipAccepted = false; - boolean cacheThisCtagResponse = false; - CtagResponseCacheKey ctagCacheKey = null; - String acctVerSnapshot = null; - Map ctagsSnapshot = null; - CtagResponseCache ctagResponseCache = null; + private final boolean ctagCacheEnabled = MemcachedConnector.isConnected(); + private boolean gzipAccepted = false; + private boolean cacheThisCtagResponse = false; + private CtagResponseCacheKey ctagCacheKey = null; + private String acctVerSnapshot = null; + private Map ctagsSnapshot = null; + private CtagResponseCache ctagResponseCache = null; } private CacheStates checkCachedResponse(DavContext ctxt, Account authUser) throws IOException, DavException, ServiceException { @@ -633,33 +655,6 @@ private void cacheCleanUp(DavContext ctxt, CacheStates cache) throws IOException } } - private static final Set PROXY_REQUEST_HEADERS = ImmutableSet.of( - DavProtocol.HEADER_DAV, - DavProtocol.HEADER_DEPTH, - DavProtocol.HEADER_CONTENT_TYPE, - DavProtocol.HEADER_ETAG, - DavProtocol.HEADER_IF_MATCH, - DavProtocol.HEADER_OVERWRITE, - DavProtocol.HEADER_DESTINATION); - - private static final Set IGNORABLE_PROXY_REQUEST_HEADERS = ImmutableSet.of( - DavProtocol.HEADER_AUTHORIZATION, - DavProtocol.HEADER_HOST, - DavProtocol.HEADER_USER_AGENT, - DavProtocol.HEADER_CONTENT_LENGTH); - - private static final Set PROXY_RESPONSE_HEADERS = ImmutableSet.of( - DavProtocol.HEADER_DAV, - DavProtocol.HEADER_ALLOW, - DavProtocol.HEADER_CONTENT_TYPE, - DavProtocol.HEADER_ETAG, - DavProtocol.HEADER_LOCATION); - - private static final Set IGNORABLE_PROXY_RESPONSE_HEADERS = ImmutableSet.of( - DavProtocol.HEADER_DATE, - DavProtocol.HEADER_CONTENT_LENGTH); - - private boolean isProxyRequest(DavContext ctxt, DavMethod m) throws IOException, DavException, ServiceException { Provisioning prov = Provisioning.getInstance(); ItemId target = null; diff --git a/store/src/java/com/zimbra/cs/filter/FilterUtil.java b/store/src/java/com/zimbra/cs/filter/FilterUtil.java index 6149988e0b2..2af0e26245e 100644 --- a/store/src/java/com/zimbra/cs/filter/FilterUtil.java +++ b/store/src/java/com/zimbra/cs/filter/FilterUtil.java @@ -31,7 +31,7 @@ import java.util.Set; import java.util.regex.Matcher; import java.util.regex.Pattern; -import org.apache.commons.lang.StringEscapeUtils; + import javax.mail.Address; import javax.mail.Header; import javax.mail.MessagingException; @@ -40,6 +40,7 @@ import javax.mail.internet.MimeMessage; import javax.mail.internet.MimeMultipart; +import org.apache.commons.lang.StringEscapeUtils; import org.apache.commons.lang.StringUtils; import org.apache.jsieve.exception.SyntaxException; @@ -257,7 +258,11 @@ public static ZMailbox getRemoteZMailbox(Mailbox localMbox, Mountpoint mountpoin zoptions.setNoSession(true); zoptions.setTargetAccount(account.getId()); zoptions.setTargetAccountBy(AccountBy.id); - return ZMailbox.getMailbox(zoptions); + ZMailbox zmbx = ZMailbox.getMailbox(zoptions); + if (zmbx != null) { + zmbx.setName(account.getName()); /* need this when logging in using another user's auth */ + } + return zmbx; } public static final String HEADER_FORWARDED = "X-Zimbra-Forwarded"; @@ -704,7 +709,7 @@ public static void notifyMailto(LmtpEnvelope envelope, OperationContext octxt, M "body".equalsIgnoreCase(headerName))) { List values = mailtoParams.get(headerName); for (String value : values) { - notification.addHeader(headerName, value); + notification.addHeaderLine(headerName + ": " + value); } } } @@ -850,7 +855,7 @@ public static String[] getTagsUnion(String[] tags1, String[] tags2) { * @param matchedVariables a list of Matched Variables * @param sourceStr text string that may contain "variable-ref" (RFC 5229 Section 3.) * @return Replaced text string - * @throws SyntaxException + * @throws SyntaxException */ public static String replaceVariables(ZimbraMailAdapter mailAdapter, String sourceStr) throws SyntaxException { if (null == mailAdapter) { @@ -860,7 +865,7 @@ public static String replaceVariables(ZimbraMailAdapter mailAdapter, String sour return sourceStr; } validateVariableIndex(sourceStr); - + try { Require.checkCapability(mailAdapter, CAPABILITY_VARIABLES); } catch (SyntaxException e) { @@ -989,7 +994,7 @@ public static String handleQuotedAndEncodedVar(String varName) { } } processedStr = sb.toString(); - + return processedStr; } @@ -1035,7 +1040,7 @@ else if (isSieveMatcherSpecialChar(pattern.charAt(ch + 1))) } return buffer.toString(); } - + /** * Returns true if the char is a special char for regex */ diff --git a/store/src/java/com/zimbra/cs/filter/JsieveConfigMapHandler.java b/store/src/java/com/zimbra/cs/filter/JsieveConfigMapHandler.java index d7a5e7e16e4..0906c3784f5 100644 --- a/store/src/java/com/zimbra/cs/filter/JsieveConfigMapHandler.java +++ b/store/src/java/com/zimbra/cs/filter/JsieveConfigMapHandler.java @@ -40,6 +40,7 @@ public class JsieveConfigMapHandler { public static final String CAPABILITY_REJECT = "reject"; public static final String CAPABILITY_ENOTIFY = "enotify"; public static final String CAPABILITY_EDITHEADER = "editheader"; + public static final String CAPABILITY_COMPARATOR_NUMERIC = "comparator-i;ascii-numeric"; /* * jSieve's command map @@ -115,6 +116,7 @@ private static Map createDefaultTestMap() { mTestMap.put("community_requests", com.zimbra.cs.filter.jsieve.CommunityRequestsTest.class.getName()); mTestMap.put("community_content", com.zimbra.cs.filter.jsieve.CommunityContentTest.class.getName()); mTestMap.put(CAPABILITY_RELATIONAL, com.zimbra.cs.filter.jsieve.RelationalTest.class.getName()); + mTestMap.put(CAPABILITY_COMPARATOR_NUMERIC, com.zimbra.cs.filter.jsieve.ComparatorNumericTest.class.getName()); mTestMap.put("string", com.zimbra.cs.filter.jsieve.StringTest.class.getName()); // The capability string associated with the 'notify' action is diff --git a/store/src/java/com/zimbra/cs/filter/RuleManager.java b/store/src/java/com/zimbra/cs/filter/RuleManager.java index 95d8e92d546..46dec9ffb2d 100644 --- a/store/src/java/com/zimbra/cs/filter/RuleManager.java +++ b/store/src/java/com/zimbra/cs/filter/RuleManager.java @@ -406,10 +406,10 @@ public static List applyRulesToIncomingMessage( }; try { + boolean applyRules = true; Account account = mailbox.getAccount(); for (String filter : filters) { // Determine whether to apply rules - boolean applyRules = true; Node node = getRulesNode(account, filter); if (null == node) { @@ -419,6 +419,7 @@ public static List applyRulesToIncomingMessage( !account.getBooleanAttr(Provisioning.A_zimbraSpamApplyUserFilters, false)) { // Don't apply user filters to spam by default applyRules = false; + break; } if (applyRules) { if (filter.equals(FILTER_RULES_CACHE_KEY)) { @@ -435,8 +436,10 @@ public static List applyRulesToIncomingMessage( mailAdapter.resetCapabilities(); } } - mailAdapter.executeAllActions(); - addedMessageIds = mailAdapter.getAddedMessageIds(); + if (applyRules) { + mailAdapter.executeAllActions(); + addedMessageIds = mailAdapter.getAddedMessageIds(); + } } catch (ErejectException ex) { throw DeliveryServiceException.DELIVERY_REJECTED(ex.getMessage(), ex); } catch (Exception e) { diff --git a/store/src/java/com/zimbra/cs/filter/SieveToSoap.java b/store/src/java/com/zimbra/cs/filter/SieveToSoap.java index 31ca69f3848..6d08958a003 100644 --- a/store/src/java/com/zimbra/cs/filter/SieveToSoap.java +++ b/store/src/java/com/zimbra/cs/filter/SieveToSoap.java @@ -288,7 +288,7 @@ protected void visitHeaderTest(Node node, VisitPhase phase, RuleProperties props @Override protected void visitHeaderTest(Node node, VisitPhase phase, RuleProperties props, - List headers, Sieve.ValueComparison comparison, boolean isCount, String value) { + List headers, Sieve.ValueComparison comparison, Sieve.Comparator comparator, boolean isCount, String value) { if (phase == VisitPhase.begin) { FilterTest.HeaderTest test = addTest(new FilterTest.HeaderTest(), props); test.setHeaders(Joiner.on(',').join(headers)); @@ -296,6 +296,12 @@ protected void visitHeaderTest(Node node, VisitPhase phase, RuleProperties props test.setCountComparison(comparison.toString()); } else { test.setValueComparison(comparison.toString()); + if (comparator != null) { + test.setValueComparisonComparator(comparator.toString()); + if (Sieve.Comparator.ioctet.equals(comparator)) { + test.setCaseSensitive(true); + } + } } test.setValue(value); } @@ -317,9 +323,9 @@ protected void visitMimeHeaderTest(Node node, VisitPhase phase, RuleProperties p @Override protected void visitAddressTest(Node node, VisitPhase phase, RuleProperties props, List headers, - Sieve.AddressPart part, Sieve.ValueComparison comparison, boolean isCount, String value) { + Sieve.AddressPart part, Sieve.ValueComparison comparison, Sieve.Comparator comparator, boolean isCount, String value) { FilterTest.AddressTest test = new FilterTest.AddressTest(); - visitAddress(test, phase, props, headers, part, comparison, isCount, value); + visitAddress(test, phase, props, headers, part, comparison, comparator, isCount, value); } @Override @@ -338,13 +344,13 @@ protected void visitEnvelopeTest(Node node, VisitPhase phase, RuleProperties pro @Override protected void visitEnvelopeTest(Node node, VisitPhase phase, RuleProperties props, List headers, - Sieve.AddressPart part, Sieve.ValueComparison comparison, boolean isCount, String value) { + Sieve.AddressPart part, Sieve.ValueComparison comparison, Sieve.Comparator comparator, boolean isCount, String value) { FilterTest.EnvelopeTest test = new FilterTest.EnvelopeTest(); - visitAddress(test, phase, props, headers, part, comparison, isCount, value); + visitAddress(test, phase, props, headers, part, comparison, comparator, isCount, value); } private void visitAddress(FilterTest.AddressTest test, VisitPhase phase, RuleProperties props, List headers, Sieve.AddressPart part, - Sieve.ValueComparison comparison, boolean isCount, String value) { + Sieve.ValueComparison comparison, Sieve.Comparator comparator, boolean isCount, String value) { if (test != null && phase == VisitPhase.begin) { addTest(test, props); test.setHeader(Joiner.on(',').join(headers)); @@ -353,6 +359,12 @@ private void visitAddress(FilterTest.AddressTest test, VisitPhase phase, RulePro test.setCountComparison(comparison.toString()); } else { test.setValueComparison(comparison.toString()); + if (comparator != null) { + test.setValueComparisonComparator(comparator.toString()); + if (Sieve.Comparator.ioctet.equals(comparator)) { + test.setCaseSensitive(true); + } + } } test.setValue(value); } diff --git a/store/src/java/com/zimbra/cs/filter/SieveVisitor.java b/store/src/java/com/zimbra/cs/filter/SieveVisitor.java index 1e964533845..19a6ba186f7 100644 --- a/store/src/java/com/zimbra/cs/filter/SieveVisitor.java +++ b/store/src/java/com/zimbra/cs/filter/SieveVisitor.java @@ -88,7 +88,7 @@ protected void visitHeaderTest(Node node, VisitPhase phase, RuleProperties props @SuppressWarnings("unused") protected void visitHeaderTest(Node node, VisitPhase phase, RuleProperties props, List headers, - Sieve.ValueComparison comparison, boolean isCount, String value) throws ServiceException { + Sieve.ValueComparison comparison, Sieve.Comparator comparator, boolean isCount, String value) throws ServiceException { // empty method } @@ -107,7 +107,7 @@ protected void visitAddressTest(Node node, VisitPhase phase, RuleProperties prop @SuppressWarnings("unused") protected void visitAddressTest(Node node, VisitPhase phase, RuleProperties props, List headers, - Sieve.AddressPart part, Sieve.ValueComparison comparison, boolean isCount, String value) + Sieve.AddressPart part, Sieve.ValueComparison comparison, Sieve.Comparator comparator, boolean isCount, String value) throws ServiceException { // empty method } @@ -119,7 +119,7 @@ protected void visitEnvelopeTest(Node node, VisitPhase phase, RuleProperties pro } protected void visitEnvelopeTest(Node node, VisitPhase phase, RuleProperties props, List headers, - Sieve.AddressPart part, Sieve.ValueComparison comparison, boolean isCount, String value) + Sieve.AddressPart part, Sieve.ValueComparison comparison, Sieve.Comparator comparator, boolean isCount, String value) throws ServiceException { // empty method } @@ -513,9 +513,9 @@ private void acceptTest(Node node, RuleProperties props) throws ServiceException if ("header".equalsIgnoreCase(nodeName)) { if (valueComparison != null) { - visitHeaderTest(node, VisitPhase.begin, props, headers, valueComparison, isCount, value); + visitHeaderTest(node, VisitPhase.begin, props, headers, valueComparison, comparator, isCount, value); accept(node, props); - visitHeaderTest(node, VisitPhase.end, props, headers, valueComparison, isCount, value); + visitHeaderTest(node, VisitPhase.end, props, headers, valueComparison, comparator, isCount, value); } else { visitHeaderTest(node, VisitPhase.begin, props, headers, comparison, caseSensitive, value); accept(node, props); @@ -541,8 +541,8 @@ private void acceptTest(Node node, RuleProperties props) throws ServiceException firstTagArgNode = (SieveNode) getNode(node, 0, 0); if (firstTagArgNode.getValue() instanceof TagArgument) { String firstArgStr = firstTagArgNode.getValue().toString(); - if (":count".equals(firstArgStr) || ":value".equals(firstArgStr)) { - if (":count".equals(firstArgStr)) { + if (HeaderConstants.COUNT.equalsIgnoreCase(firstArgStr) || HeaderConstants.VALUE.equalsIgnoreCase(firstArgStr)) { + if (HeaderConstants.COUNT.equalsIgnoreCase(firstArgStr)) { isCount = true; } valueComparison = Sieve.ValueComparison.valueOf(getValue(node, 0, 1, 0, 0)); @@ -577,10 +577,10 @@ private void acceptTest(Node node, RuleProperties props) throws ServiceException if ("envelope".equalsIgnoreCase(nodeName)) { if (valueComparison != null) { - visitEnvelopeTest(node, VisitPhase.begin, props, headers, part, valueComparison, isCount, + visitEnvelopeTest(node, VisitPhase.begin, props, headers, part, valueComparison, comparator, isCount, value); accept(node, props); - visitEnvelopeTest(node, VisitPhase.end, props, headers, part, valueComparison, isCount, value); + visitEnvelopeTest(node, VisitPhase.end, props, headers, part, valueComparison, comparator, isCount, value); } else { visitEnvelopeTest(node, VisitPhase.begin, props, headers, part, comparison, caseSensitive, value); @@ -589,9 +589,9 @@ private void acceptTest(Node node, RuleProperties props) throws ServiceException } } else { if (valueComparison != null) { - visitAddressTest(node, VisitPhase.begin, props, headers, part, valueComparison, isCount, value); + visitAddressTest(node, VisitPhase.begin, props, headers, part, valueComparison, comparator, isCount, value); accept(node, props); - visitAddressTest(node, VisitPhase.end, props, headers, part, valueComparison, isCount, value); + visitAddressTest(node, VisitPhase.end, props, headers, part, valueComparison, comparator, isCount, value); } else { visitAddressTest(node, VisitPhase.begin, props, headers, part, comparison, caseSensitive, value); diff --git a/store/src/java/com/zimbra/cs/filter/SoapToSieve.java b/store/src/java/com/zimbra/cs/filter/SoapToSieve.java index 809a415333b..27aeb3576ac 100644 --- a/store/src/java/com/zimbra/cs/filter/SoapToSieve.java +++ b/store/src/java/com/zimbra/cs/filter/SoapToSieve.java @@ -31,6 +31,7 @@ import com.google.common.base.Strings; import com.zimbra.common.filter.Sieve; import com.zimbra.common.service.ServiceException; +import com.zimbra.common.soap.HeaderConstants; import com.zimbra.common.util.StringUtil; import com.zimbra.common.util.ZimbraLog; import com.zimbra.soap.mail.type.EditheaderTest; @@ -49,6 +50,9 @@ public final class SoapToSieve { // end of line in sieve script public static final String END_OF_LINE = ";\n"; + public static final String requireCommon = "\"fileinto\", \"copy\", \"reject\", \"tag\", \"flag\", " + + "\"variables\", \"log\", \"enotify\", \"envelope\", \"body\", " + + "\"ereject\", \"reject\", \"relational\", \"comparator-i;ascii-numeric\""; public SoapToSieve(List rules) { this.rules = rules; @@ -57,7 +61,7 @@ public SoapToSieve(List rules) { public String getSieveScript() throws ServiceException { if (buffer == null) { buffer = new StringBuilder(); - buffer.append("require [\"fileinto\", \"copy\", \"reject\", \"tag\", \"flag\", \"variables\", \"log\", \"enotify\"]" + END_OF_LINE); + buffer.append("require [" + requireCommon + "]" + END_OF_LINE); for (FilterRule rule : rules) { buffer.append('\n'); handleRule(rule); @@ -69,7 +73,7 @@ public String getSieveScript() throws ServiceException { public String getAdminSieveScript() throws ServiceException { if (buffer == null) { buffer = new StringBuilder(); - buffer.append("require [\"fileinto\", \"copy\", \"reject\", \"tag\", \"flag\", \"variables\", \"log\", \"enotify\", \"editheader\"]" + END_OF_LINE); + buffer.append("require [" + requireCommon + ", \"editheader\"]" + END_OF_LINE); for (FilterRule rule : rules) { buffer.append('\n'); handleRule(rule, true); @@ -388,12 +392,13 @@ private static String toSieve(FilterTest.HeaderTest test) throws ServiceExceptio if (StringUtils.isNotEmpty(test.getValueComparison())) { Sieve.ValueComparison comp = Sieve.ValueComparison.fromString(test.getValueComparison()); - return toSieve("header", header, comp, test.getValue(), false, null); + Sieve.Comparator valueComparisonComparator = Sieve.Comparator.fromString(test.getValueComparisonComparator()); + return toSieve("header", header, comp, valueComparisonComparator, test.isCaseSensitive(), test.getValue(), false, null); } if (StringUtils.isNotEmpty(test.getCountComparison())) { Sieve.ValueComparison comp = Sieve.ValueComparison.fromString(test.getCountComparison()); - return toSieve("header", header, comp, test.getValue(), true, null); + return toSieve("header", header, comp, null, false, test.getValue(), true, null); } return null; } @@ -412,20 +417,29 @@ private static String toSieve(String name, String header, Sieve.StringComparison return String.format(format, name, comp, header, FilterUtil.escape(value)); } - private static String toSieve(String name, String header, Sieve.ValueComparison comp, String value, boolean isCount, Sieve.AddressPart part) throws ServiceException { - String countOrVal = isCount ? ":count" : ":value"; - boolean numeric = true; - try { - Integer.parseInt(value); - } catch (NumberFormatException e) { - numeric = false; - } - //for :count, iasciinumeric comparator will be used always. - //for :value, iasciinumeric comparator will be used if value is numeric else - //iasciicasemap will be used until comparator value can be set from soap api. - Sieve.Comparator comparator= Sieve.Comparator.iasciinumeric; - if (!numeric && !isCount) { - comparator= Sieve.Comparator.iasciicasemap; + private static String toSieve(String name, String header, Sieve.ValueComparison comp, Sieve.Comparator valueComparator, boolean caseSensitive, String value, boolean isCount, Sieve.AddressPart part) throws ServiceException { + String countOrVal = isCount ? HeaderConstants.COUNT : HeaderConstants.VALUE; + Sieve.Comparator comparator; + if (valueComparator == null) { + boolean numeric = true; + try { + Integer.parseInt(value); + } catch (NumberFormatException e) { + numeric = false; + } + //for :count, iasciinumeric comparator will be used always + //for :value, if comparator value is not set in input, iasciinumeric comparator will be used if value is numeric + //else iasciicasemap will be used for case-insensitive and ioctet for case-sensitive + comparator = Sieve.Comparator.iasciinumeric; + if (!numeric && !isCount) { + if (caseSensitive) { + comparator = Sieve.Comparator.ioctet; + } else { + comparator = Sieve.Comparator.iasciicasemap; + } + } + } else { + comparator = valueComparator; } if (part == null) { String format = "%s " + countOrVal + " \"%s\" :comparator \"" + comparator + "\" %s \"%s\""; @@ -462,12 +476,13 @@ private static String formatAddress(FilterTest.AddressTest test, String testName if (StringUtils.isNotEmpty(test.getValueComparison())) { Sieve.ValueComparison comp = Sieve.ValueComparison.fromString(test.getValueComparison()); - return toSieve(testName, header, comp, test.getValue(), false, part); + Sieve.Comparator valueComparisonComparator = Sieve.Comparator.fromString(test.getValueComparisonComparator()); + return toSieve(testName, header, comp, valueComparisonComparator, test.isCaseSensitive(), test.getValue(), false, part); } if (StringUtils.isNotEmpty(test.getCountComparison())) { Sieve.ValueComparison comp = Sieve.ValueComparison.fromString(test.getCountComparison()); - return toSieve(testName, header, comp, test.getValue(), true, part); + return toSieve(testName, header, comp, null, false, test.getValue(), true, part); } return null; } diff --git a/store/src/java/com/zimbra/cs/filter/ZimbraComparatorUtils.java b/store/src/java/com/zimbra/cs/filter/ZimbraComparatorUtils.java index 36caf56b28d..5c9c93239cf 100644 --- a/store/src/java/com/zimbra/cs/filter/ZimbraComparatorUtils.java +++ b/store/src/java/com/zimbra/cs/filter/ZimbraComparatorUtils.java @@ -16,6 +16,7 @@ */ package com.zimbra.cs.filter; +import static com.zimbra.cs.filter.JsieveConfigMapHandler.CAPABILITY_RELATIONAL; import static com.zimbra.cs.filter.jsieve.ComparatorName.ASCII_NUMERIC_COMPARATOR; import static org.apache.jsieve.comparators.ComparatorNames.ASCII_CASEMAP_COMPARATOR; import static org.apache.jsieve.comparators.MatchTypeTags.CONTAINS_TAG; @@ -48,6 +49,7 @@ import com.zimbra.common.soap.HeaderConstants; import com.zimbra.cs.filter.jsieve.Counts; import com.zimbra.cs.filter.jsieve.Equals2; +import com.zimbra.cs.filter.jsieve.Require; import com.zimbra.cs.filter.jsieve.Values; public class ZimbraComparatorUtils { @@ -187,10 +189,13 @@ else if (matchType == null * @return true if the target value matches the condition * @throws SieveException */ - public static boolean match(String comparatorName, String matchType, String operator, + public static boolean match(MailAdapter mail, String comparatorName, String matchType, String operator, String matchTarget, String matchArgument, SieveContext context) throws SieveException { boolean isMatched = false; + if (mail instanceof ZimbraMailAdapter && ASCII_NUMERIC_COMPARATOR.equalsIgnoreCase(comparatorName)) { + Require.checkCapability(mail, ASCII_NUMERIC_COMPARATOR); + } if (IS_TAG.equals(matchType)) { isMatched = is(comparatorName, matchTarget, matchArgument, context); } else if (CONTAINS_TAG.equals(matchType)) { @@ -200,7 +205,7 @@ public static boolean match(String comparatorName, String matchType, String oper isMatched = ComparatorUtils.matches(comparatorName, matchTarget, matchArgument, context); } else if (HeaderConstants.VALUE.equals(matchType)) { - isMatched = values(comparatorName, operator, matchTarget, matchArgument, + isMatched = values(mail, comparatorName, operator, matchTarget, matchArgument, context); } return isMatched; @@ -217,10 +222,14 @@ public static boolean match(String comparatorName, String matchType, String oper * @return boolean result of "[# of matchTarget] [operator] [matchArgument]" * @throws LookupException * @throws FeatureException + * @throws SyntaxException thrown when "relational" and "comparator-i;ascii-numeric" are not declared in the "require" command. */ - public static boolean counts(String comparatorName, + public static boolean counts(MailAdapter mail, String comparatorName, String operator, List matchTarget, String matchArgument, - SieveContext context) throws LookupException, FeatureException { + SieveContext context) throws LookupException, FeatureException, SyntaxException { + if (mail instanceof ZimbraMailAdapter) { + Require.checkCapability((ZimbraMailAdapter) mail, CAPABILITY_RELATIONAL); + } Counts comparatorObj = (Counts) context.getComparatorManager().getComparator(comparatorName); return comparatorObj.counts(operator, matchTarget, matchArgument); } @@ -236,9 +245,13 @@ public static boolean counts(String comparatorName, * @return boolean result of "[lhs] [operator] [rhs]" * @throws LookupException * @throws FeatureException + * @throws SyntaxException */ - public static boolean values(String comparatorName, String operator, - String lhs, String rhs, SieveContext context) throws LookupException, FeatureException { + public static boolean values(MailAdapter mail, String comparatorName, String operator, + String lhs, String rhs, SieveContext context) throws LookupException, FeatureException, SyntaxException { + if (mail instanceof ZimbraMailAdapter) { + Require.checkCapability((ZimbraMailAdapter) mail, CAPABILITY_RELATIONAL); + } Values comparatorObj = (Values) context.getComparatorManager().getComparator(comparatorName); return comparatorObj.values(operator, lhs, rhs); } @@ -323,9 +336,10 @@ static public boolean matches(String string, String glob) * @param context not null * @return boolean * @throws FeatureException the number is negative value + * @throws SyntaxException */ public static boolean is(String comparatorName, String string1, - String string2, SieveContext context) throws LookupException, FeatureException { + String string2, SieveContext context) throws LookupException, FeatureException, SyntaxException { Equals2 comparatorObj = (Equals2) context.getComparatorManager().getComparator(comparatorName); return comparatorObj.equals2(string1, string2); } diff --git a/store/src/java/com/zimbra/cs/filter/jsieve/AddressTest.java b/store/src/java/com/zimbra/cs/filter/jsieve/AddressTest.java index 7d3e9f679aa..24ddb74e324 100644 --- a/store/src/java/com/zimbra/cs/filter/jsieve/AddressTest.java +++ b/store/src/java/com/zimbra/cs/filter/jsieve/AddressTest.java @@ -16,6 +16,7 @@ */ package com.zimbra.cs.filter.jsieve; +import static com.zimbra.cs.filter.jsieve.ComparatorName.ASCII_NUMERIC_COMPARATOR; import static org.apache.jsieve.comparators.MatchTypeTags.IS_TAG; import static org.apache.jsieve.tests.AddressPartTags.ALL_TAG; import static org.apache.jsieve.tests.AddressPartTags.LOCALPART_TAG; @@ -78,6 +79,14 @@ protected boolean executeBasic(MailAdapter mail, Arguments arguments, throw new SieveException("Exception occured while evaluating variable expression.", e); } } + if (params.getMatchType() == null) { + params.setMatchType(IS_TAG); + } + params.setComparator(ZimbraComparatorUtils.getComparator(params.getComparator(), params.getMatchType())); + if (ASCII_NUMERIC_COMPARATOR.equalsIgnoreCase(params.getComparator())) { + Require.checkCapability((ZimbraMailAdapter) mail, ASCII_NUMERIC_COMPARATOR); + } + if (HeaderConstants.COUNT.equals(params.getMatchType()) || HeaderConstants.VALUE.equals(params.getMatchType()) || IS_TAG.equalsIgnoreCase(params.getMatchType())) { return match(mail, (params.getAddressPart() == null ? ALL_TAG : params.getAddressPart()), @@ -90,7 +99,7 @@ protected boolean executeBasic(MailAdapter mail, Arguments arguments, return match(mail, (params.getAddressPart() == null ? ALL_TAG : params.getAddressPart()), ZimbraComparatorUtils.getComparator(params.getComparator(), params.getMatchType()), - (params.getMatchType() == null ? IS_TAG : params.getMatchType()), + params.getMatchType(), params.getHeaderNames(), params.getKeys(), context); } @@ -121,7 +130,7 @@ private boolean match(MailAdapter mail, String addressPart, String comparator, if (HeaderConstants.COUNT.equals(matchType)) { for (final String key: keys) { - isMatched = ZimbraComparatorUtils.counts(comparator, + isMatched = ZimbraComparatorUtils.counts(mail, comparator, operator, headerValues, ZimbraComparatorUtils.getMatchKey(addressPart, key), context); if (isMatched) { break; @@ -130,26 +139,16 @@ private boolean match(MailAdapter mail, String addressPart, String comparator, } else { Iterator headerValuesIter = headerValues.iterator(); while (!isMatched && headerValuesIter.hasNext()) { - isMatched = match(comparator, matchType, - operator, (String)headerValuesIter.next(), keys, context); - } - } - return isMatched; - } + // Iterate over the keys looking for a match + String headerValue = (String)headerValuesIter.next(); + for (final String key: keys) { + isMatched = ZimbraComparatorUtils.match(mail, comparator, matchType, operator, + headerValue, key, context); + if (isMatched) { + break; + } + } - /** - * Compares the value of the specified address field with the operator - */ - private boolean match(String comparator, String matchType, String operator, - String headerValue, List keys, SieveContext context) - throws SieveException { - // Iterate over the keys looking for a match - boolean isMatched = false; - for (final String key: keys) { - isMatched = ZimbraComparatorUtils.match(comparator, matchType, operator, - headerValue, key, context); - if (isMatched) { - break; } } return isMatched; diff --git a/store/src/java/com/zimbra/cs/filter/jsieve/BodyTest.java b/store/src/java/com/zimbra/cs/filter/jsieve/BodyTest.java index db21c26e5e8..d6c4211fb67 100644 --- a/store/src/java/com/zimbra/cs/filter/jsieve/BodyTest.java +++ b/store/src/java/com/zimbra/cs/filter/jsieve/BodyTest.java @@ -1,7 +1,7 @@ /* * ***** BEGIN LICENSE BLOCK ***** * Zimbra Collaboration Suite Server - * Copyright (C) 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2013, 2014, 2016 Synacor, Inc. + * Copyright (C) 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2013, 2014, 2016, 2017 Synacor, Inc. * * This program is free software: you can redistribute it and/or modify it under * the terms of the GNU General Public License as published by the Free Software Foundation, @@ -44,6 +44,9 @@ import org.apache.jsieve.tests.AbstractTest; import javax.mail.Part; + +import static com.zimbra.cs.filter.jsieve.ComparatorName.ASCII_NUMERIC_COMPARATOR; + import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; @@ -98,7 +101,11 @@ protected boolean executeBasic(MailAdapter mail, Arguments arguments, SieveConte if (argument instanceof StringListArgument) { StringListArgument strList = (StringListArgument) argument; try { - caseSensitive = Sieve.Comparator.ioctet == Sieve.Comparator.fromString(strList.getList().get(0)); + String comparator = strList.getList().get(0); + if (ASCII_NUMERIC_COMPARATOR.equalsIgnoreCase(comparator) && mail instanceof ZimbraMailAdapter) { + Require.checkCapability((ZimbraMailAdapter) mail, ASCII_NUMERIC_COMPARATOR); + } + caseSensitive = Sieve.Comparator.ioctet == Sieve.Comparator.fromString(comparator); } catch (ServiceException e) { throw new SyntaxException(e.getMessage()); } diff --git a/store/src/java/com/zimbra/cs/filter/jsieve/ComparatorNumericTest.java b/store/src/java/com/zimbra/cs/filter/jsieve/ComparatorNumericTest.java new file mode 100644 index 00000000000..5c893baba41 --- /dev/null +++ b/store/src/java/com/zimbra/cs/filter/jsieve/ComparatorNumericTest.java @@ -0,0 +1,34 @@ +/* + * ***** BEGIN LICENSE BLOCK ***** + * Zimbra Collaboration Suite Server + * Copyright (C) 2017 Synacor, Inc. + * + * This program is free software: you can redistribute it and/or modify it under + * the terms of the GNU General Public License as published by the Free Software Foundation, + * version 2 of the License. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * You should have received a copy of the GNU General Public License along with this program. + * If not, see . + * ***** END LICENSE BLOCK ***** + */ +package com.zimbra.cs.filter.jsieve; + +import org.apache.jsieve.Arguments; +import org.apache.jsieve.SieveContext; +import org.apache.jsieve.exception.SieveException; +import org.apache.jsieve.exception.SyntaxException; +import org.apache.jsieve.mail.MailAdapter; +import org.apache.jsieve.tests.AbstractTest; + +import static com.zimbra.cs.filter.JsieveConfigMapHandler.CAPABILITY_COMPARATOR_NUMERIC; + +public class ComparatorNumericTest extends AbstractTest { + @Override + protected boolean executeBasic(MailAdapter mail, Arguments arguments, + SieveContext context) throws SieveException { + throw new SyntaxException("Unexpected test " + CAPABILITY_COMPARATOR_NUMERIC); + } +} diff --git a/store/src/java/com/zimbra/cs/filter/jsieve/DateTest.java b/store/src/java/com/zimbra/cs/filter/jsieve/DateTest.java index ceaedafc8ec..fd0ade8108e 100644 --- a/store/src/java/com/zimbra/cs/filter/jsieve/DateTest.java +++ b/store/src/java/com/zimbra/cs/filter/jsieve/DateTest.java @@ -64,15 +64,17 @@ protected boolean executeBasic(MailAdapter mail, Arguments arguments, SieveConte if (argument instanceof TagArgument) { String tag = ((TagArgument) argument).getTag(); - if (tag.equals(BEFORE) || tag.equals(AFTER)) + if (tag.equals(BEFORE) || tag.equals(AFTER)) { comparator = tag; - else + } else { throw new SyntaxException( "Found unexpected: \"" + tag + "\""); + } } } - if (null == comparator) + if (null == comparator) { throw new SyntaxException("Expecting \"" + BEFORE + "\" or \"" + AFTER + "\""); + } // Second argument MUST be a date if (argumentsIter.hasNext()) @@ -88,13 +90,15 @@ protected boolean executeBasic(MailAdapter mail, Arguments arguments, SieveConte } } } - if (null == date) + if (null == date) { throw new SyntaxException("Expecting a valid date (yyyyMMdd)"); + } // There MUST NOT be any further arguments - if (argumentsIter.hasNext()) - throw new SyntaxException("Found unexpected argument(s)"); - + if (argumentsIter.hasNext()) { + throw new SyntaxException("Found unexpected argument(s)"); + } + if (mail instanceof DummyMailAdapter) { return true; } diff --git a/store/src/java/com/zimbra/cs/filter/jsieve/DeleteHeader.java b/store/src/java/com/zimbra/cs/filter/jsieve/DeleteHeader.java index 628f2715e6c..6d94c018f74 100644 --- a/store/src/java/com/zimbra/cs/filter/jsieve/DeleteHeader.java +++ b/store/src/java/com/zimbra/cs/filter/jsieve/DeleteHeader.java @@ -17,6 +17,8 @@ package com.zimbra.cs.filter.jsieve; import static com.zimbra.cs.filter.JsieveConfigMapHandler.CAPABILITY_EDITHEADER; +import static com.zimbra.cs.filter.jsieve.ComparatorName.ASCII_NUMERIC_COMPARATOR; + import java.util.List; import java.util.Enumeration; import java.util.HashSet; @@ -55,6 +57,9 @@ protected Object executeBasic(MailAdapter mail, Arguments args, Block block, Sie return null; } ZimbraMailAdapter mailAdapter = (ZimbraMailAdapter) mail; + if (ASCII_NUMERIC_COMPARATOR.equalsIgnoreCase(ehe.getComparator())) { + Require.checkCapability((ZimbraMailAdapter) mail, ASCII_NUMERIC_COMPARATOR); + } Require.checkCapability(mailAdapter, CAPABILITY_EDITHEADER); if (!mailAdapter.getAccount().isSieveEditHeaderEnabled()) { mailAdapter.setDeleteHeaderPresent(true); diff --git a/store/src/java/com/zimbra/cs/filter/jsieve/EditHeaderExtension.java b/store/src/java/com/zimbra/cs/filter/jsieve/EditHeaderExtension.java index f2a6254fc75..c385e5a9a8c 100644 --- a/store/src/java/com/zimbra/cs/filter/jsieve/EditHeaderExtension.java +++ b/store/src/java/com/zimbra/cs/filter/jsieve/EditHeaderExtension.java @@ -415,14 +415,14 @@ public boolean matchCondition(ZimbraMailAdapter mailAdapter, Header header, List } if (this.valueTag) { - matchFound = ZimbraComparatorUtils.values(comparator, relationalComparator, unfoldedAndDecodedHeaderValue, value, context); + matchFound = ZimbraComparatorUtils.values(mailAdapter, comparator, relationalComparator, unfoldedAndDecodedHeaderValue, value, context); } else if (this.countTag) { - matchFound = ZimbraComparatorUtils.counts(comparator, relationalComparator, headerList, value, context); - } else if (this.is && ComparatorUtils.is(this.comparator, unfoldedAndDecodedHeaderValue, value, context)) { + matchFound = ZimbraComparatorUtils.counts(mailAdapter, comparator, relationalComparator, headerList, value, context); + } else if (this.is && ComparatorUtils.is(comparator, unfoldedAndDecodedHeaderValue, value, context)) { matchFound = true; - } else if (this.contains && ComparatorUtils.contains(this.comparator, unfoldedAndDecodedHeaderValue, value, context)) { + } else if (this.contains && ComparatorUtils.contains(comparator, unfoldedAndDecodedHeaderValue, value, context)) { matchFound = true; - } else if (this.matches && matchValue(value, unfoldedAndDecodedHeaderValue)) { + } else if (this.matches && ComparatorUtils.matches(comparator, unfoldedAndDecodedHeaderValue, value, context)) { matchFound = true; } else { ZimbraLog.filter.debug("Key: %s and Value: %s pair not matching requested criteria.", this.key, value); @@ -549,18 +549,6 @@ static public boolean isImmutableHeaderKey(String key, ZimbraMailAdapter mailAda return immutableHeaders.stream().map(String::trim).anyMatch(x -> x.equalsIgnoreCase(key)); } - /** - * @param regex - * @param value - * @return - */ - private boolean matchValue(String regex, String value) { - regex = ComparatorUtils.sieveToJavaRegex(regex); - Pattern pattern = Pattern.compile(regex, Pattern.CASE_INSENSITIVE | Pattern.DOTALL); - Matcher matcher = pattern.matcher(value); - return matcher.matches(); - } - static public boolean saveChanges(ZimbraMailAdapter zma, String actionName, MimeMessage mm) { if (zma.getEditHeaderParseStatus() == PARSESTATUS.UNKNOWN) { try { diff --git a/store/src/java/com/zimbra/cs/filter/jsieve/EnvelopeTest.java b/store/src/java/com/zimbra/cs/filter/jsieve/EnvelopeTest.java index 1c10bd71f61..624fd14ded6 100644 --- a/store/src/java/com/zimbra/cs/filter/jsieve/EnvelopeTest.java +++ b/store/src/java/com/zimbra/cs/filter/jsieve/EnvelopeTest.java @@ -17,6 +17,7 @@ package com.zimbra.cs.filter.jsieve; +import static com.zimbra.cs.filter.JsieveConfigMapHandler.CAPABILITY_ENVELOPE; import static com.zimbra.cs.filter.jsieve.ComparatorName.ASCII_NUMERIC_COMPARATOR; import static org.apache.jsieve.comparators.ComparatorNames.ASCII_CASEMAP_COMPARATOR; import static org.apache.jsieve.comparators.MatchTypeTags.IS_TAG; @@ -66,6 +67,8 @@ protected boolean executeBasic(MailAdapter mail, Arguments arguments, if (!(mail instanceof ZimbraMailAdapter)) { return false; } + ZimbraMailAdapter mailAdapter = (ZimbraMailAdapter) mail; + Require.checkCapability(mailAdapter, CAPABILITY_ENVELOPE); ZimbraComparatorUtils.TestParameters params = ZimbraComparatorUtils.parseTestArguments(mail, arguments, context); params.setKeys(HeaderTest.replaceVariables(params.getKeys(), mail)); @@ -75,13 +78,19 @@ protected boolean executeBasic(MailAdapter mail, Arguments arguments, } if (MatchTypeTags.MATCHES_TAG.equals(params.getMatchType())) { - ZimbraMailAdapter zma = (ZimbraMailAdapter) mail; try { - HeaderTest.evaluateVarExp(zma, params.getHeaderNames(), HeaderTest.SourceType.ENVELOPE, params.getKeys()); + HeaderTest.evaluateVarExp(mailAdapter, params.getHeaderNames(), HeaderTest.SourceType.ENVELOPE, params.getKeys()); } catch (MessagingException e) { throw new SieveException("Exception occured while evaluating variable expression.", e); } } + if (params.getMatchType() == null) { + params.setMatchType(IS_TAG); + } + params.setComparator(ZimbraComparatorUtils.getComparator(params.getComparator(), params.getMatchType())); + if (ASCII_NUMERIC_COMPARATOR.equalsIgnoreCase(params.getComparator())) { + Require.checkCapability(mailAdapter, ASCII_NUMERIC_COMPARATOR); + } if (HeaderConstants.COUNT.equals(params.getMatchType()) || HeaderConstants.VALUE.equals(params.getMatchType()) || IS_TAG.equals(params.getMatchType())) { return match(mail, @@ -95,7 +104,7 @@ protected boolean executeBasic(MailAdapter mail, Arguments arguments, return match(mail, (params.getAddressPart() == null ? ALL_TAG : params.getAddressPart()), ZimbraComparatorUtils.getComparator(params.getComparator(), params.getMatchType()), - (params.getMatchType() == null ? IS_TAG : params.getMatchType()), + params.getMatchType(), params.getHeaderNames(), params.getKeys(), context); } @@ -152,7 +161,7 @@ private boolean match(MailAdapter mail, String addressPart, String comparator, if (HeaderConstants.COUNT.equals(matchType)) { for (final String key: keys) { - isMatched = ZimbraComparatorUtils.counts(comparator, + isMatched = ZimbraComparatorUtils.counts(mail, comparator, operator, headerValues, ZimbraComparatorUtils.getMatchKey(addressPart, key), context); if (isMatched) { break; @@ -169,8 +178,15 @@ private boolean match(MailAdapter mail, String addressPart, String comparator, } else { normalizedKeys = keys; } - isMatched = match(comparator, matchType, operator, (String)headerValuesIter.next(), normalizedKeys, - context); + String headerValue = (String)headerValuesIter.next(); + // Iterate over the keys looking for a match + for (final String key: keys) { + isMatched = ZimbraComparatorUtils.match(mail, comparator, matchType, operator, + headerValue, key, context); + if (isMatched) { + break; + } + } } } @@ -215,19 +231,4 @@ private String getMatchAddressPart(String addressPart, String email) { } return matchAddress; } - - private boolean match(String comparator, String matchType, String operator, - String headerValue, List keys, SieveContext context) - throws SieveException { - // Iterate over the keys looking for a match - boolean isMatched = false; - for (final String key: keys) { - isMatched = ZimbraComparatorUtils.match(comparator, matchType, operator, - headerValue, key, context); - if (isMatched) { - break; - } - } - return isMatched; - } } diff --git a/store/src/java/com/zimbra/cs/filter/jsieve/Ereject.java b/store/src/java/com/zimbra/cs/filter/jsieve/Ereject.java index adcb2fca685..5825bd00a1b 100644 --- a/store/src/java/com/zimbra/cs/filter/jsieve/Ereject.java +++ b/store/src/java/com/zimbra/cs/filter/jsieve/Ereject.java @@ -16,6 +16,8 @@ */ package com.zimbra.cs.filter.jsieve; +import static com.zimbra.cs.filter.JsieveConfigMapHandler.CAPABILITY_EREJECT; + import org.apache.jsieve.Arguments; import org.apache.jsieve.Block; import org.apache.jsieve.SieveContext; @@ -34,7 +36,10 @@ protected Object executeBasic(MailAdapter mail, Arguments arguments, Block block if (!(mail instanceof ZimbraMailAdapter)) { return null; } - ((ZimbraMailAdapter) mail).setDiscardActionPresent(); + ZimbraMailAdapter mailAdapter = (ZimbraMailAdapter) mail; + Require.checkCapability(mailAdapter, CAPABILITY_EREJECT); + + mailAdapter.setDiscardActionPresent(); final String message = ((StringListArgument) arguments.getArgumentList().get(0)).getList().get(0); mail.addAction(new ActionEreject(message)); return null; diff --git a/store/src/java/com/zimbra/cs/filter/jsieve/HeaderTest.java b/store/src/java/com/zimbra/cs/filter/jsieve/HeaderTest.java index 4a4f961ced9..54dcdd17662 100644 --- a/store/src/java/com/zimbra/cs/filter/jsieve/HeaderTest.java +++ b/store/src/java/com/zimbra/cs/filter/jsieve/HeaderTest.java @@ -16,6 +16,7 @@ */ package com.zimbra.cs.filter.jsieve; +import static com.zimbra.cs.filter.jsieve.ComparatorName.ASCII_NUMERIC_COMPARATOR; import static org.apache.jsieve.comparators.MatchTypeTags.CONTAINS_TAG; import static org.apache.jsieve.comparators.MatchTypeTags.IS_TAG; import static org.apache.jsieve.comparators.MatchTypeTags.MATCHES_TAG; @@ -176,16 +177,18 @@ else if (matchType == null "Found unexpected arguments"); } + if (null == matchType) { + matchType = IS_TAG; + } + comparator = ZimbraComparatorUtils.getComparator(comparator, matchType); + if (ASCII_NUMERIC_COMPARATOR.equalsIgnoreCase(comparator) && mail instanceof ZimbraMailAdapter) { + Require.checkCapability((ZimbraMailAdapter) mail, ASCII_NUMERIC_COMPARATOR); + } if (matchType != null && (HeaderConstants.COUNT.equalsIgnoreCase(matchType) || HeaderConstants.VALUE.equalsIgnoreCase(matchType) || IS_TAG.equalsIgnoreCase(matchType))) { - return match(mail, - ZimbraComparatorUtils.getComparator(comparator, matchType), - matchType, operator, headerNames, keys, context); + return match(mail, comparator, matchType, operator, headerNames, keys, context); } else { - return match(mail, - ZimbraComparatorUtils.getComparator(comparator, matchType), - (matchType == null ? IS_TAG : matchType), - headerNames, keys, context); + return match(mail, comparator, matchType, headerNames, keys, context); } } @@ -228,7 +231,7 @@ protected boolean match(MailAdapter mail, String comparator, String matchType, headerValues.addAll(mail.getMatchingHeader((String) headerNamesIter.next())); } for (final String key: keys) { - isMatched = ZimbraComparatorUtils.counts(comparator, + isMatched = ZimbraComparatorUtils.counts(mail, comparator, operator, headerValues, key, context); if (isMatched) { break; @@ -236,52 +239,29 @@ protected boolean match(MailAdapter mail, String comparator, String matchType, } } else { while (!isMatched && headerNamesIter.hasNext()) { - isMatched = match(comparator, matchType, operator, - mail.getMatchingHeader((String) headerNamesIter.next()), - keys, context); + List headerValues = mail.getMatchingHeader((String) headerNamesIter.next()); + if (headerValues.isEmpty()) { + isMatched = false; + } else { + // Compares each header value to each key. + Iterator headerValuesIter = headerValues.iterator(); + while (!isMatched && headerValuesIter.hasNext()) { + String headerValue = (String) headerValuesIter.next(); + // Iterate over the keys looking for a match + for (final String key: keys) { + isMatched = ZimbraComparatorUtils.match(mail, comparator, matchType, operator, + headerValue, key, context); + if (isMatched) { + break; + } + } + } + } } } return isMatched; } - /** - * Traverses the values set of the specific header field(s) to check the filter key - */ - protected boolean match(String comparator, String matchType, String operator, - List headerValues, List keys, SieveContext context) - throws SieveException { - if (headerValues.isEmpty()) { - return false; - } - - // Iterate over the header values looking for a match - boolean isMatched = false; - Iterator headerValuesIter = headerValues.iterator(); - while (!isMatched && headerValuesIter.hasNext()) { - isMatched = match(comparator, matchType, operator, - (String) headerValuesIter.next(), keys, context); - } - return isMatched; - } - - /** - * Compares each header value to each key. - */ - protected boolean match(String comparator, String matchType, - String operator, String headerValue, List keys, SieveContext context) - throws SieveException { - // Iterate over the keys looking for a match - boolean isMatched = false; - for (final String key: keys) { - isMatched = ZimbraComparatorUtils.match(comparator, matchType, operator, - headerValue, key, context); - if (isMatched) { - break; - } - } - return isMatched; - } - protected boolean match(MailAdapter mail, String comparator, String matchType, List headerNames, List keys, SieveContext context) throws SieveException { if (mail instanceof DummyMailAdapter) { diff --git a/store/src/java/com/zimbra/cs/filter/jsieve/MimeHeaderTest.java b/store/src/java/com/zimbra/cs/filter/jsieve/MimeHeaderTest.java index 2980ae45447..1e01d8f9fc3 100644 --- a/store/src/java/com/zimbra/cs/filter/jsieve/MimeHeaderTest.java +++ b/store/src/java/com/zimbra/cs/filter/jsieve/MimeHeaderTest.java @@ -1,7 +1,7 @@ /* * ***** BEGIN LICENSE BLOCK ***** * Zimbra Collaboration Suite Server - * Copyright (C) 2010, 2013, 2014, 2016 Synacor, Inc. + * Copyright (C) 2010, 2013, 2014, 2016, 2017 Synacor, Inc. * * This program is free software: you can redistribute it and/or modify it under * the terms of the GNU General Public License as published by the Free Software Foundation, @@ -17,6 +17,8 @@ package com.zimbra.cs.filter.jsieve; +import static com.zimbra.cs.filter.jsieve.ComparatorName.ASCII_NUMERIC_COMPARATOR; + import java.util.ArrayList; import java.util.Iterator; import java.util.List; @@ -48,6 +50,9 @@ protected boolean match(MailAdapter mail, String comparator, return false; } ZimbraMailAdapter zma = (ZimbraMailAdapter) mail; + if (ASCII_NUMERIC_COMPARATOR.equalsIgnoreCase(comparator)) { + Require.checkCapability(zma, ASCII_NUMERIC_COMPARATOR); + } // Iterate over the header names looking for a match boolean isMatched = false; Iterator headerNamesIter = headerNames.iterator(); diff --git a/store/src/java/com/zimbra/cs/filter/jsieve/NotifyMailto.java b/store/src/java/com/zimbra/cs/filter/jsieve/NotifyMailto.java index c7e19a35cc2..3b4b4d5ecd0 100644 --- a/store/src/java/com/zimbra/cs/filter/jsieve/NotifyMailto.java +++ b/store/src/java/com/zimbra/cs/filter/jsieve/NotifyMailto.java @@ -44,6 +44,7 @@ import java.util.List; import java.util.ListIterator; import java.util.Map; +import java.util.TreeMap; import javax.mail.internet.InternetAddress; import javax.mail.internet.AddressException; @@ -251,7 +252,7 @@ private Object execute(MailAdapter mail, Arguments arguments, SieveContext conte method = FilterUtil.replaceVariables(mailAdapter, method); } - mailtoParams = new HashMap>(); + mailtoParams = new TreeMap>(String.CASE_INSENSITIVE_ORDER); try { URL url = new URL(method); mailto = FilterUtil.replaceVariables(mailAdapter, url.getPath()); @@ -275,12 +276,13 @@ private Object execute(MailAdapter mail, Arguments arguments, SieveContext conte String headerName = null; String headerValue = null; try { - headerName = URLDecoder.decode(token[0].toLowerCase(), "UTF-8"); + headerName = URLDecoder.decode(token[0], "UTF-8"); } catch (UnsupportedEncodingException e) { // No exception should be thrown because the charset is always "UTF-8" } catch (IllegalArgumentException e) { - headerName = token[0].toLowerCase(); + headerName = token[0]; } + headerName = Character.toUpperCase(headerName.charAt(0)) + headerName.substring(1); if (token.length == 1) { // The value must be empty headerValue = ""; diff --git a/store/src/java/com/zimbra/cs/filter/jsieve/NotifyMethodCapabilityTest.java b/store/src/java/com/zimbra/cs/filter/jsieve/NotifyMethodCapabilityTest.java index 7ee9f6d574f..c4e993f6af4 100644 --- a/store/src/java/com/zimbra/cs/filter/jsieve/NotifyMethodCapabilityTest.java +++ b/store/src/java/com/zimbra/cs/filter/jsieve/NotifyMethodCapabilityTest.java @@ -192,7 +192,7 @@ else if (matchType == null throw context.getCoordinate().syntaxException( "Found unexpected arguments"); } - return test(comparator, matchType, operator, uri, capability, keys, context); + return test(mail, comparator, matchType, operator, uri, capability, keys, context); } @Override @@ -200,7 +200,7 @@ protected void validateArguments(Arguments arguments, SieveContext context) { // override validation -- it's already done in executeBasic above } - private boolean test(String comparator, String matchType, String operator, + private boolean test(MailAdapter mail, String comparator, String matchType, String operator, String uri, String capability, List keys, SieveContext context) throws SieveException { if (null == uri || null == capability) { return false; @@ -216,7 +216,7 @@ private boolean test(String comparator, String matchType, String operator, } if (HeaderConstants.COUNT.equalsIgnoreCase(matchType)) { - return testCount(keys, comparator, operator, context); + return testCount(mail, keys, comparator, operator, context); } // There is no way to detect the online/offline status of the recipient. // The test always returns "maybe" for the "mailto" notification method @@ -230,11 +230,11 @@ private boolean test(String comparator, String matchType, String operator, return false; } - private boolean testCount(List keys, String comparator, String operator, SieveContext context) throws SieveException { + private boolean testCount(MailAdapter mail, List keys, String comparator, String operator, SieveContext context) throws SieveException { List values = Arrays.asList(CAPABILITY_MAYBE); boolean isMatched = false; for (String key : keys) { - isMatched = ZimbraComparatorUtils.counts(comparator, + isMatched = ZimbraComparatorUtils.counts(mail, comparator, operator, values, key, context); if (isMatched) { break; diff --git a/store/src/java/com/zimbra/cs/filter/jsieve/Reject.java b/store/src/java/com/zimbra/cs/filter/jsieve/Reject.java index 170d45d911b..6039824329c 100644 --- a/store/src/java/com/zimbra/cs/filter/jsieve/Reject.java +++ b/store/src/java/com/zimbra/cs/filter/jsieve/Reject.java @@ -20,6 +20,8 @@ import com.zimbra.cs.filter.FilterUtil; import com.zimbra.cs.filter.ZimbraMailAdapter; +import static com.zimbra.cs.filter.JsieveConfigMapHandler.CAPABILITY_REJECT; + import org.apache.jsieve.Arguments; import org.apache.jsieve.Block; import org.apache.jsieve.SieveContext; @@ -42,6 +44,8 @@ protected Object executeBasic(MailAdapter mail, Arguments arguments, Block block return null; } ZimbraMailAdapter mailAdapter = (ZimbraMailAdapter) mail; + Require.checkCapability(mailAdapter, CAPABILITY_REJECT); + Account account = null; account = mailAdapter.getAccount(); if (account.isSieveRejectMailEnabled()) { diff --git a/store/src/java/com/zimbra/cs/filter/jsieve/ReplaceHeader.java b/store/src/java/com/zimbra/cs/filter/jsieve/ReplaceHeader.java index 12c82e34039..edd8371c13f 100644 --- a/store/src/java/com/zimbra/cs/filter/jsieve/ReplaceHeader.java +++ b/store/src/java/com/zimbra/cs/filter/jsieve/ReplaceHeader.java @@ -17,6 +17,8 @@ package com.zimbra.cs.filter.jsieve; import static com.zimbra.cs.filter.JsieveConfigMapHandler.CAPABILITY_EDITHEADER; +import static com.zimbra.cs.filter.jsieve.ComparatorName.ASCII_NUMERIC_COMPARATOR; + import java.io.UnsupportedEncodingException; import java.util.ArrayList; import java.util.Arrays; @@ -59,6 +61,9 @@ protected Object executeBasic(MailAdapter mail, Arguments arguments, return null; } ZimbraMailAdapter mailAdapter = (ZimbraMailAdapter) mail; + if (ASCII_NUMERIC_COMPARATOR.equalsIgnoreCase(ehe.getComparator())) { + Require.checkCapability((ZimbraMailAdapter) mail, ASCII_NUMERIC_COMPARATOR); + } Require.checkCapability(mailAdapter, CAPABILITY_EDITHEADER); if (!mailAdapter.getAccount().isSieveEditHeaderEnabled()) { mailAdapter.setReplaceHeaderPresent(true); diff --git a/store/src/java/com/zimbra/cs/filter/jsieve/StringTest.java b/store/src/java/com/zimbra/cs/filter/jsieve/StringTest.java index 5b486b4f676..37ee9e91dca 100644 --- a/store/src/java/com/zimbra/cs/filter/jsieve/StringTest.java +++ b/store/src/java/com/zimbra/cs/filter/jsieve/StringTest.java @@ -16,6 +16,7 @@ */ package com.zimbra.cs.filter.jsieve; +import static com.zimbra.cs.filter.JsieveConfigMapHandler.CAPABILITY_VARIABLES; import static com.zimbra.cs.filter.jsieve.ComparatorName.ASCII_NUMERIC_COMPARATOR; import static org.apache.jsieve.comparators.ComparatorNames.ASCII_CASEMAP_COMPARATOR; import static org.apache.jsieve.comparators.MatchTypeTags.CONTAINS_TAG; @@ -72,6 +73,7 @@ protected boolean executeBasic(MailAdapter mail, Arguments arguments, SieveConte } ZimbraMailAdapter mailAdapter = (ZimbraMailAdapter) mail; + Require.checkCapability(mailAdapter, CAPABILITY_VARIABLES); String matchType = null; String comparator = null; @@ -226,7 +228,7 @@ private boolean match(MailAdapter mail, String comparator, String matchType, Str sourceValues.removeAll(Arrays.asList("", null)); keyIter = keyValues.iterator(); while (!isMatched && keyIter.hasNext()) { - isMatched = ZimbraComparatorUtils.counts(comparator, + isMatched = ZimbraComparatorUtils.counts(mail, comparator, operator, sourceValues, keyIter.next(), context); } } else { @@ -235,7 +237,7 @@ private boolean match(MailAdapter mail, String comparator, String matchType, Str String source = sourceIter.next(); keyIter = keyValues.iterator(); while(!isMatched && keyIter.hasNext()) { - isMatched = ZimbraComparatorUtils.match(comparator, matchType, operator, + isMatched = ZimbraComparatorUtils.match(mail, comparator, matchType, operator, source, keyIter.next(), context); } } diff --git a/store/src/java/com/zimbra/cs/imap/ImapCredentials.java b/store/src/java/com/zimbra/cs/imap/ImapCredentials.java index 3fc528b0a4b..c57d933c7c9 100644 --- a/store/src/java/com/zimbra/cs/imap/ImapCredentials.java +++ b/store/src/java/com/zimbra/cs/imap/ImapCredentials.java @@ -25,6 +25,7 @@ import java.util.Set; import com.google.common.base.Objects; +import com.google.common.collect.Sets; import com.zimbra.client.ZMailbox; import com.zimbra.client.event.ZEventHandler; import com.zimbra.common.account.Key; @@ -158,8 +159,13 @@ private void saveSubscriptions(Set subscriptions) throws ServiceExceptio getImapMailboxStore().saveSubscriptions(getContext(), subscriptions); } + /** @return Modifiable set of subscriptions or null if there are no subscriptions */ protected Set listSubscriptions() throws ServiceException { - return getImapMailboxStore().listSubscriptions(getContext()); + Set subs = getImapMailboxStore().listSubscriptions(getContext()); + /* subs may be an unmodifiable set as that is what the JAXB provides in the remote case. + * Change it into a modifiable set, as the result is modified when subscribing/unsubscribing + */ + return (subs == null || subs.isEmpty()) ? null : Sets.newHashSet(subs); } protected void subscribe(ImapPath path) throws ServiceException { diff --git a/store/src/java/com/zimbra/cs/imap/ImapDaemon.java b/store/src/java/com/zimbra/cs/imap/ImapDaemon.java index bd461bfcd38..492d598051c 100644 --- a/store/src/java/com/zimbra/cs/imap/ImapDaemon.java +++ b/store/src/java/com/zimbra/cs/imap/ImapDaemon.java @@ -17,19 +17,21 @@ package com.zimbra.cs.imap; +import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.util.Properties; import org.apache.log4j.PropertyConfigurator; +import com.zimbra.common.calendar.WellKnownTimeZones; import com.zimbra.common.localconfig.LC; import com.zimbra.common.service.ServiceException; import com.zimbra.common.util.ZimbraLog; import com.zimbra.cs.account.Provisioning; -import com.zimbra.cs.store.StoreManager; -import com.zimbra.common.util.ZimbraLog; import com.zimbra.cs.stats.ZimbraPerf; +import com.zimbra.cs.store.StoreManager; +import com.zimbra.cs.util.MemoryStats; public class ImapDaemon { @@ -92,7 +94,9 @@ private void stopServers() { public static void main(String[] args) { try { Properties props = new Properties(); - props.load(new FileInputStream(IMAPD_LOG4J_CONFIG)); + try (FileInputStream fisLog4j = new FileInputStream(IMAPD_LOG4J_CONFIG)) { + props.load(fisLog4j); + } PropertyConfigurator.configure(props); String imapdClassStore=LC.imapd_class_store.value(); try { @@ -102,11 +106,22 @@ public static void main(String[] args) { } if(isZimbraImapEnabled()) { + ZimbraPerf.prepare(ZimbraPerf.ServerID.IMAP_DAEMON); + MemoryStats.startup(); + ZimbraPerf.initialize(ZimbraPerf.ServerID.IMAP_DAEMON); + + String tzFilePath = LC.timezone_file.value(); + try { + File tzFile = new File(tzFilePath); + WellKnownTimeZones.loadFromFile(tzFile); + } catch (Throwable t) { + ZimbraLog.imap.error("Unable to load timezones from %s.", tzFilePath, t); + errorExit("ImapDaemon: imapd service was unable to intialize timezones EXITING."); + } + ImapDaemon daemon = new ImapDaemon(); int numStarted = daemon.startServers(); - ZimbraPerf.initialize(true); - if(numStarted > 0) { Runtime.getRuntime().addShutdownHook(new Thread() { @Override @@ -126,6 +141,7 @@ public void run() { } } catch (Exception e) { System.err.println("ImapDaemon: " + e); + e.printStackTrace(System.err); } } diff --git a/store/src/java/com/zimbra/cs/imap/ImapHandler.java b/store/src/java/com/zimbra/cs/imap/ImapHandler.java index abc7bb66ebd..9006ed0493b 100644 --- a/store/src/java/com/zimbra/cs/imap/ImapHandler.java +++ b/store/src/java/com/zimbra/cs/imap/ImapHandler.java @@ -46,13 +46,16 @@ import javax.mail.MessagingException; import javax.mail.internet.MimeMessage; +import com.zimbra.cs.service.admin.AddAccountLogger; import org.dom4j.DocumentException; import com.google.common.base.Charsets; +import com.google.common.base.Joiner; import com.google.common.base.Strings; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Lists; import com.google.common.io.Closeables; +import com.zimbra.client.ZFolder; import com.zimbra.client.ZSharedFolder; import com.zimbra.common.account.Key; import com.zimbra.common.account.Key.AccountBy; @@ -77,6 +80,8 @@ import com.zimbra.common.util.AccessBoundedRegex; import com.zimbra.common.util.Constants; import com.zimbra.common.util.DateUtil; +import com.zimbra.common.util.Log; +import com.zimbra.common.util.LogFactory; import com.zimbra.common.util.Pair; import com.zimbra.common.util.StringUtil; import com.zimbra.common.util.ZimbraLog; @@ -121,9 +126,16 @@ import com.zimbra.soap.admin.type.CacheSelector; public abstract class ImapHandler { - enum State { NOT_AUTHENTICATED, AUTHENTICATED, SELECTED, LOGOUT } + protected enum State { NOT_AUTHENTICATED, AUTHENTICATED, SELECTED, LOGOUT } - enum ImapExtension { CONDSTORE, QRESYNC } + protected enum ImapExtension { CONDSTORE, QRESYNC } + + private static final Set SUPPORTED_EXTENSIONS = new LinkedHashSet(Arrays.asList( + "ACL", "BINARY", "CATENATE", "CHILDREN", "CONDSTORE", "ENABLE", "ESEARCH", "ESORT", + "I18NLEVEL=1", "ID", "IDLE", "LIST-EXTENDED", "LIST-STATUS", "LITERAL+", "LOGIN-REFERRALS", + "MULTIAPPEND", "NAMESPACE", "QRESYNC", "QUOTA", "RIGHTS=ektx", "SASL-IR", "SEARCHRES", + "SORT", "THREAD=ORDEREDSUBJECT", "UIDPLUS", "UNSELECT", "WITHIN", "XLIST" + )); private static final long MAXIMUM_IDLE_PROCESSING_MILLIS = 15 * Constants.MILLIS_PER_SECOND; @@ -131,23 +143,94 @@ enum ImapExtension { CONDSTORE, QRESYNC } private static final String ID_PARAMS = "\"NAME\" \"Zimbra\" \"VERSION\" \"" + BuildInfo.VERSION + "\" \"RELEASE\" \"" + BuildInfo.RELEASE + "\""; - static final char[] LINE_SEPARATOR = { '\r', '\n' }; - static final byte[] LINE_SEPARATOR_BYTES = { '\r', '\n' }; + private static final String IMAP_READ_RIGHTS = "lr"; + private static final String IMAP_WRITE_RIGHTS = "sw"; + private static final String IMAP_INSERT_RIGHTS = "ick"; + private static final String IMAP_DELETE_RIGHTS = "xted"; + private static final String IMAP_ADMIN_RIGHTS = "a"; + + /* All the supported IMAP rights, concatenated together into a single string. */ + private static final String IMAP_CONCATENATED_RIGHTS = IMAP_READ_RIGHTS + IMAP_WRITE_RIGHTS + + IMAP_INSERT_RIGHTS + IMAP_DELETE_RIGHTS + IMAP_ADMIN_RIGHTS; + /* All the supported IMAP rights, with linked sets of rights + * grouped together and the groups delimited by spaces. */ + private static final String IMAP_DELIMITED_RIGHTS = IMAP_READ_RIGHTS + ' ' + IMAP_WRITE_RIGHTS + ' ' + + IMAP_INSERT_RIGHTS + ' ' + IMAP_DELETE_RIGHTS + ' ' + IMAP_ADMIN_RIGHTS; + + /* The set of rights required to create a new subfolder in ZCS. */ + private final short SUBFOLDER_RIGHTS = ACL.RIGHT_INSERT | ACL.RIGHT_READ; + + protected static final char[] LINE_SEPARATOR = { '\r', '\n' }; + protected static final byte[] LINE_SEPARATOR_BYTES = { '\r', '\n' }; + + protected static final int FETCH_BODY = 0x0001; + protected static final int FETCH_BODYSTRUCTURE = 0x0002; + protected static final int FETCH_ENVELOPE = 0x0004; + protected static final int FETCH_FLAGS = 0x0008; + protected static final int FETCH_INTERNALDATE = 0x0010; + protected static final int FETCH_RFC822_SIZE = 0x0020; + protected static final int FETCH_BINARY_SIZE = 0x0040; + protected static final int FETCH_UID = 0x0080; + protected static final int FETCH_MODSEQ = 0x0100; + protected static final int FETCH_VANISHED = 0x0200; + protected static final int FETCH_MARK_READ = 0x1000; + + protected static final int FETCH_FROM_CACHE = FETCH_FLAGS | FETCH_UID; + protected static final int FETCH_FROM_MIME = FETCH_BODY | FETCH_BODYSTRUCTURE | FETCH_ENVELOPE; + + protected static final int FETCH_FAST = FETCH_FLAGS | FETCH_INTERNALDATE | FETCH_RFC822_SIZE; + protected static final int FETCH_ALL = FETCH_FAST | FETCH_ENVELOPE; + protected static final int FETCH_FULL = FETCH_ALL | FETCH_BODY; + + private static final byte SELECT_SUBSCRIBED = 0x01; + private static final byte SELECT_REMOTE = 0x02; + private static final byte SELECT_RECURSIVE = 0x04; + + private static final byte RETURN_SUBSCRIBED = 0x01; + private static final byte RETURN_CHILDREN = 0x02; + private static final byte RETURN_XLIST = 0x04; + + private final int SUGGESTED_BATCH_SIZE = 100; + private final int SUGGESTED_COPY_BATCH_SIZE = 50; + private final int SUGGESTED_DELETE_BATCH_SIZE = 30; + + protected enum StoreAction { REPLACE, ADD, REMOVE } + + private static final int RETURN_MIN = 0x01; + private static final int RETURN_MAX = 0x02; + private static final int RETURN_ALL = 0x04; + private static final int RETURN_COUNT = 0x08; + private static final int RETURN_SAVE = 0x10; + + private static final int LARGEST_FOLDER_BATCH = 600; + public static final Set ITEM_TYPES = ImapMessage.SUPPORTED_TYPES; + + protected static final boolean IDLE_START = true; + protected static final boolean IDLE_STOP = false; - ImapConfig config; + private static final boolean[] REGEXP_ESCAPED = new boolean[128]; + static { + REGEXP_ESCAPED['('] = REGEXP_ESCAPED[')'] = REGEXP_ESCAPED['.'] = true; + REGEXP_ESCAPED['['] = REGEXP_ESCAPED[']'] = REGEXP_ESCAPED['|'] = true; + REGEXP_ESCAPED['^'] = REGEXP_ESCAPED['$'] = REGEXP_ESCAPED['?'] = true; + REGEXP_ESCAPED['{'] = REGEXP_ESCAPED['}'] = REGEXP_ESCAPED['*'] = true; + REGEXP_ESCAPED['\\'] = true; + } + + protected ImapConfig config; protected OutputStream output; - Authenticator authenticator; - ImapCredentials credentials; - boolean startedTLS; - String lastCommand; - int consecutiveError; + protected Authenticator authenticator; + protected ImapCredentials credentials; + protected boolean startedTLS; + protected String lastCommand; + protected int consecutiveError; private ImapProxy imapProxy; - ImapListener selectedFolderListener; + protected ImapListener selectedFolderListener; private String idleTag; private String origRemoteIp; private String via; private String userAgent; - boolean goodbyeSent; + protected boolean goodbyeSent; private Set activeExtensions; private final ServerThrottle reqThrottle; private final ImapCommandThrottle commandThrottle; @@ -191,7 +274,7 @@ protected ImapHandler(ImapConfig config) { protected abstract boolean doSTARTTLS(String tag) throws IOException; protected abstract InetSocketAddress getLocalAddress(); - ImapCredentials getCredentials() { + protected ImapCredentials getCredentials() { return credentials; } @@ -210,19 +293,19 @@ public ImapConfig getConfig() { protected abstract String getRemoteIp(); - String getOrigRemoteIp() { + protected String getOrigRemoteIp() { return origRemoteIp; } - String getVia() { + protected String getVia() { return via; } - String getUserAgent() { + protected String getUserAgent() { return userAgent; } - void setLoggingContext() { + protected void setLoggingContext() { ZimbraLog.clearContext(); ImapListener i4selected = selectedFolderListener; MailboxStore mbox = i4selected == null ? null : i4selected.getMailbox(); @@ -260,13 +343,13 @@ protected void handleParseException(ImapParseException e) throws IOException { } } - void checkEOF(String tag, ImapRequest req) throws ImapParseException { + private void checkEOF(String tag, ImapRequest req) throws ImapParseException { if (!req.eof()) { throw new ImapParseException(tag, "excess characters at end of command"); } } - boolean continueAuthentication(ImapRequest req) throws IOException { + protected boolean continueAuthentication(ImapRequest req) throws IOException { String tag = getTag(authenticator); try { // use the tag from the original AUTHENTICATE command @@ -313,7 +396,7 @@ private boolean continueAuthentication(byte[] response) throws IOException { return true; } - boolean isIdle() { + protected boolean isIdle() { return idleTag != null; } @@ -325,7 +408,7 @@ private static boolean canContinue(Authenticator auth) { return ((ImapAuthenticatorUser) auth.getAuthenticatorUser()).canContinue(); } - boolean checkAccountStatus() { + protected boolean checkAccountStatus() { if (!config.isServiceEnabled()) { ZimbraLog.imap.warn("user services are disabled; dropping connection"); return false; @@ -368,7 +451,7 @@ boolean checkAccountStatus() { return true; } - boolean executeRequest(ImapRequest req) throws IOException, ImapException { + protected boolean executeRequest(ImapRequest req) throws IOException, ImapException { boolean isProxied = imapProxy != null; if (getCredentials() != null) { @@ -580,7 +663,9 @@ boolean executeRequest(ImapRequest req) throws IOException, ImapException { } else if (command.equals("LIST")) { Set patterns = new LinkedHashSet(2); boolean parenthesized = false; - byte selectOptions = 0, returnOptions = 0, status = 0; + byte selectOptions = 0; + byte returnOptions = 0; + byte status = 0; req.skipSpace(); if (req.peekChar() == '(' && extensionEnabled("LIST-EXTENDED")) { @@ -636,7 +721,8 @@ boolean executeRequest(ImapRequest req) throws IOException, ImapException { returnOptions |= RETURN_CHILDREN; } else if (option.equals("STATUS") && extensionEnabled("LIST-STATUS")) { req.skipSpace(); - status = parseStatusFields(req); + StatusDataItemNames cmdInfo = new StatusDataItemNames(req); + status = cmdInfo.cmdStatus; } else { throw new ImapParseException(tag, "unknown LIST return option \"" + option + '"'); } @@ -772,9 +858,9 @@ boolean executeRequest(ImapRequest req) throws IOException, ImapException { req.skipSpace(); ImapPath path = new ImapPath(req.readFolder(), credentials); req.skipSpace(); - byte status = parseStatusFields(req); + StatusDataItemNames dataItemNames = new StatusDataItemNames(req); checkEOF(tag, req); - return doSTATUS(tag, path, status); + return doSTATUS(tag, path, dataItemNames); } else if (command.equals("SORT") && extensionEnabled("SORT")) { Integer options = null; req.skipSpace(); @@ -906,6 +992,15 @@ boolean executeRequest(ImapRequest req) throws IOException, ImapException { } else if (command.equals("X-ZIMBRA-RELOADLC")) { checkEOF(tag, req); return doRELOADLC(tag); + } else if (command.equals("X-ZIMBRA-ADD-ACCOUNT-LOGGER")) { + req.skipSpace(); + String accountString = req.readAstring(); + req.skipSpace(); + String category = req.readAstring(); + req.skipSpace(); + String level = req.readAstring(); + checkEOF(tag, req); + return doADDACCOUNTLOGGER(tag, accountString, category, level); } break; } @@ -914,32 +1009,66 @@ boolean executeRequest(ImapRequest req) throws IOException, ImapException { throw new ImapParseException(tag, "command not implemented"); } - private byte parseStatusFields(ImapRequest req) throws ImapParseException { - byte status = 0; - req.skipChar('('); - do { - if (status != 0) { - req.skipSpace(); + private static class StatusDataItemNames { + private static final int STATUS_MESSAGES = 0x01; + private static final int STATUS_RECENT = 0x02; + private static final int STATUS_UIDNEXT = 0x04; + private static final int STATUS_UIDVALIDITY = 0x08; + private static final int STATUS_UNSEEN = 0x10; + private static final int STATUS_HIGHESTMODSEQ = 0x20; + public final byte cmdStatus; + + StatusDataItemNames(ImapRequest req) throws ImapParseException { + byte status = 0; + req.skipChar('('); + do { + if (status != 0) { + req.skipSpace(); + } + String flag = req.readATOM(); + if (flag.equals("MESSAGES")) { + status |= STATUS_MESSAGES; + } else if (flag.equals("RECENT")) { + status |= STATUS_RECENT; + } else if (flag.equals("UIDNEXT")) { + status |= STATUS_UIDNEXT; + } else if (flag.equals("UIDVALIDITY")) { + status |= STATUS_UIDVALIDITY; + } else if (flag.equals("UNSEEN")) { + status |= STATUS_UNSEEN; + } else if (flag.equals("HIGHESTMODSEQ")) { + status |= STATUS_HIGHESTMODSEQ; + } else { + throw new ImapParseException(req.getTag(), "unknown STATUS attribute \"" + flag + '"'); + } + } while (req.peekChar() != ')'); + req.skipChar(')'); + cmdStatus = status; + } + + @Override + public String toString() { + List requested = Lists.newArrayListWithExpectedSize(6); + if ((cmdStatus & STATUS_MESSAGES) != 0) { + requested.add("MESSAGES"); } - String flag = req.readATOM(); - if (flag.equals("MESSAGES")) { - status |= STATUS_MESSAGES; - } else if (flag.equals("RECENT")) { - status |= STATUS_RECENT; - } else if (flag.equals("UIDNEXT")) { - status |= STATUS_UIDNEXT; - } else if (flag.equals("UIDVALIDITY")) { - status |= STATUS_UIDVALIDITY; - } else if (flag.equals("UNSEEN")) { - status |= STATUS_UNSEEN; - } else if (flag.equals("HIGHESTMODSEQ")) { - status |= STATUS_HIGHESTMODSEQ; - } else { - throw new ImapParseException(req.getTag(), "unknown STATUS attribute \"" + flag + '"'); + if ((cmdStatus & STATUS_RECENT) != 0) { + requested.add("RECENT"); } - } while (req.peekChar() != ')'); - req.skipChar(')'); - return status; + if ((cmdStatus & STATUS_UIDNEXT) != 0) { + requested.add("UIDNEXT"); + } + if ((cmdStatus & STATUS_UIDVALIDITY) != 0) { + requested.add("UIDVALIDITY"); + } + if ((cmdStatus & STATUS_UNSEEN) != 0) { + requested.add("UNSEEN"); + } + if ((cmdStatus & STATUS_HIGHESTMODSEQ) != 0) { + requested.add("HIGHESTMODSEQ"); + } + return "(" + Joiner.on(' ').join(requested) + ")"; + } } private int parseSearchOptions(ImapRequest req) throws ImapParseException { @@ -996,7 +1125,7 @@ private QResyncInfo parseQResyncInfo(ImapRequest req) throws ImapParseException return qri; } - State getState() { + protected State getState() { if (goodbyeSent) { return State.LOGOUT; } else if (selectedFolderListener != null || imapProxy != null) { @@ -1012,7 +1141,7 @@ protected boolean isAuthenticated() { return credentials != null; } - boolean checkState(String tag, State required) throws IOException { + protected boolean checkState(String tag, State required) throws IOException { State state = getState(); if (required == State.NOT_AUTHENTICATED && state != State.NOT_AUTHENTICATED) { sendNO(tag, "must be in NOT AUTHENTICATED state"); @@ -1028,16 +1157,16 @@ boolean checkState(String tag, State required) throws IOException { } } - ImapListener getCurrentImapListener() { + protected ImapListener getCurrentImapListener() { return getState() == State.LOGOUT ? null : selectedFolderListener; } - ImapFolder getSelectedFolder() throws ImapSessionClosedException { + protected ImapFolder getSelectedFolder() throws ImapSessionClosedException { ImapListener i4selected = getCurrentImapListener(); return i4selected == null ? null : i4selected.getImapFolder(); } - void unsetSelectedFolder(boolean sendClosed) throws IOException { + protected void unsetSelectedFolder(boolean sendClosed) throws IOException { ImapListener i4selected = selectedFolderListener; selectedFolderListener = null; if (i4selected != null) { @@ -1057,19 +1186,19 @@ void unsetSelectedFolder(boolean sendClosed) throws IOException { } } - FolderDetails setSelectedFolder(ImapPath path, byte params) throws ServiceException, IOException { + protected FolderDetails setSelectedFolder(ImapPath path, byte params) + throws ServiceException, IOException { unsetSelectedFolder(true); if (path == null) { return new FolderDetails(null, null); } FolderDetails selectdata = ImapSessionManager.getInstance().openFolder(path, params, this); selectedFolderListener = selectdata.listener; - ZimbraLog.imap.info("selected folder " + selectdata.listener.getPath()); return selectdata; } - boolean canContinue(ServiceException e) { + protected boolean canContinue(ServiceException e) { String errCode = e.getCode(); if(errCode.equals(ServiceException.AUTH_EXPIRED)) { setCredentials(null); @@ -1078,7 +1207,7 @@ boolean canContinue(ServiceException e) { return e.getCode().equals(MailServiceException.MAINTENANCE) || e.getCode().equals(ServiceException.TEMPORARILY_UNAVAILABLE) ? false : true; } - OperationContext getContext() throws ServiceException { + protected OperationContext getContext() throws ServiceException { if (!isAuthenticated()) { throw ServiceException.AUTH_REQUIRED(); } @@ -1087,7 +1216,7 @@ OperationContext getContext() throws ServiceException { return oc; } - OperationContext getContextOrNull() { + protected OperationContext getContextOrNull() { try { return getContext(); } catch (ServiceException e) { @@ -1095,19 +1224,12 @@ OperationContext getContextOrNull() { } } - boolean doCAPABILITY(String tag) throws IOException { + private boolean doCAPABILITY(String tag) throws IOException { sendUntagged(getCapabilityString()); sendOK(tag, "CAPABILITY completed"); return true; } - private static final Set SUPPORTED_EXTENSIONS = new LinkedHashSet(Arrays.asList( - "ACL", "BINARY", "CATENATE", "CHILDREN", "CONDSTORE", "ENABLE", "ESEARCH", "ESORT", - "I18NLEVEL=1", "ID", "IDLE", "LIST-EXTENDED", "LIST-STATUS", "LITERAL+", "LOGIN-REFERRALS", - "MULTIAPPEND", "NAMESPACE", "QRESYNC", "QUOTA", "RIGHTS=ektx", "SASL-IR", "SEARCHRES", - "SORT", "THREAD=ORDEREDSUBJECT", "UIDPLUS", "UNSELECT", "WITHIN", "XLIST" - )); - protected String getCapabilityString() { // [IMAP4rev1] RFC 3501: Internet Message Access Protocol - Version 4rev1 // [LOGINDISABLED] RFC 3501: Internet Message Access Protocol - Version 4rev1 @@ -1169,7 +1291,7 @@ protected String getCapabilityString() { return capability.toString(); } - boolean extensionEnabled(String extension) { + protected boolean extensionEnabled(String extension) { if (config.isCapabilityDisabled(extension)) { // check whether the extension is explicitly disabled on the server return false; @@ -1197,7 +1319,7 @@ private boolean mechanismEnabled(String mechanism) { return extensionEnabled("AUTH=" + mechanism); } - boolean doNOOP(String tag) throws IOException { + private boolean doNOOP(String tag) throws IOException { ImapListener i4selected = getCurrentImapListener(); if(i4selected != null) { MailboxStore mbox = i4selected.getMailbox(); @@ -1219,7 +1341,7 @@ boolean doNOOP(String tag) throws IOException { // RFC 2971 3: "The sole purpose of the ID extension is to enable clients and servers // to exchange information on their implementations for the purposes of // statistical analysis and problem determination." - boolean doID(String tag, Map fields) throws IOException { + private boolean doID(String tag, Map fields) throws IOException { setIDFields(fields); sendNotifications(true, false); if (isAuthenticated()) { @@ -1296,7 +1418,7 @@ private void setIDFields(Map paramFields) { ZimbraLog.imap.debug("IMAP client identified as: %s", fields); } - String getNextVia() { + protected String getNextVia() { StringBuilder result = new StringBuilder(); if (via != null) { result.append(via).append(','); @@ -1308,7 +1430,7 @@ String getNextVia() { return result.toString(); } - boolean doENABLE(String tag, List extensions) throws IOException { + private boolean doENABLE(String tag, List extensions) throws IOException { if (!checkState(tag, State.AUTHENTICATED)) { return true; } @@ -1348,7 +1470,7 @@ boolean doENABLE(String tag, List extensions) throws IOException { return true; } - void activateExtension(ImapExtension ext) { + protected void activateExtension(ImapExtension ext) { if (ext == null) { return; } @@ -1358,11 +1480,11 @@ void activateExtension(ImapExtension ext) { activeExtensions.add(ext); } - boolean sessionActivated(ImapExtension ext) { + protected boolean sessionActivated(ImapExtension ext) { return activeExtensions != null && activeExtensions.contains(ext); } - boolean doLOGOUT(String tag) throws IOException { + private boolean doLOGOUT(String tag) throws IOException { sendBYE(); sendOK(tag, "LOGOUT completed"); return false; @@ -1375,7 +1497,50 @@ private boolean checkZimbraAdminAuth() { } - boolean doFLUSHCACHE(String tag, List types, List entries) throws IOException { + private boolean doADDACCOUNTLOGGER(String tag, String id, String category, String sLevel) throws IOException { + ZimbraLog.imap.info("Adding account level logger for '%s' category: %s level %s", id, category, sLevel); + + if (!checkState(tag, State.AUTHENTICATED)) { + return true; + } else if (!checkZimbraAdminAuth()) { + sendNO(tag, "must be authenticated as admin with X-ZIMBRA auth mechanism"); + return true; + } + + try { + Account account = Provisioning.getInstance().get(AccountBy.name, id); + if (account == null || !account.isAccountStatusActive()) { + ZimbraLog.imap.warn("target account missing or not active; dropping connection"); + sendNO(tag, "must specify an actual account"); + return true; + } + + // Handle level. + Log.Level level = null; + try { + level = Log.Level.valueOf(sLevel.toLowerCase()); + } catch (IllegalArgumentException e) { + String error = String.format("Invalid level: %s. Valid values are %s.", + sLevel, StringUtil.join(",", Log.Level.values())); + ZimbraLog.imap.warn(error); + sendNO(tag, "FATAL: " + error); + return true; + } + + AddAccountLogger.addAccountLogger(account, category, level); + + } catch (ServiceException e) { + ZimbraLog.imap.warn("error checking target account status; dropping connection", e); + sendNO(tag, "must specify an actual account"); + return false; + } + + sendOK(tag, "ADDACCOUNTLOGGER completed"); + return true; + } + + private boolean doFLUSHCACHE(String tag, List types, List entries) + throws IOException { if (!checkState(tag, State.AUTHENTICATED)) { return true; } else if (!checkZimbraAdminAuth()) { @@ -1414,7 +1579,7 @@ boolean doFLUSHCACHE(String tag, List types, List preferredServers = Provisioning.getPreferredIMAPServers(account); - ZimbraLog.imap.info("%s failed; mechanism: %s. Should be contacting one of these hosts: %s ", command, mechanism, String.join(", ", preferredServers)); + List preferredServers = Provisioning.getPreferredIMAPServers(account); + List preferredServerHostnames = new ArrayList(); + for (Server server: preferredServers){ + preferredServerHostnames.add(server.getServiceHostname()); + } + ZimbraLog.imap.info("%s failed; mechanism: %s. Should be contacting one of these hosts: %s ", command, mechanism, String.join(", ", preferredServerHostnames)); if (!extensionEnabled("LOGIN_REFERRALS") || preferredServers.isEmpty()) { sendNO(tag, "%s failed (wrong host)", command); } else { - sendNO(tag, "[REFERRAL imap://%s@%s/] %s failed", URLEncoder.encode(account.getName(), "utf-8"), preferredServers.get(0), command); + sendNO(tag, "[REFERRAL imap://%s@%s/] %s failed", URLEncoder.encode(account.getName(), "utf-8"), preferredServers.get(0).getServiceHostname(), command); } return null; } @@ -1573,12 +1743,14 @@ private ImapCredentials startSession(Account account, EnabledHack hack, String t return credentials; } - boolean doSELECT(String tag, ImapPath path, byte params, QResyncInfo qri) throws IOException, ImapException { + private boolean doSELECT(String tag, ImapPath path, byte params, QResyncInfo qri) + throws IOException, ImapException { checkCommandThrottle(new SelectCommand(path, params, qri)); return selectFolder(tag, "SELECT", path, params, qri); } - boolean doEXAMINE(String tag, ImapPath path, byte params, QResyncInfo qri) throws IOException, ImapException { + private boolean doEXAMINE(String tag, ImapPath path, byte params, QResyncInfo qri) + throws IOException, ImapException { checkCommandThrottle(new ExamineCommand(path, params, qri)); return selectFolder(tag, "EXAMINE", path, (byte) (params | ImapFolder.SELECT_READONLY), qri); } @@ -1699,7 +1871,7 @@ private boolean selectFolder(String tag, String command, ImapPath path, byte par return true; } - boolean doCREATE(String tag, ImapPath path) throws IOException, ImapThrottledException { + private boolean doCREATE(String tag, ImapPath path) throws IOException, ImapThrottledException { checkCommandThrottle(new CreateCommand(path)); if (!checkState(tag, State.AUTHENTICATED)) { return true; @@ -1728,7 +1900,7 @@ boolean doCREATE(String tag, ImapPath path) throws IOException, ImapThrottledExc } else if (e.getCode().equals(ServiceException.PERM_DENIED)) { cause += ": permission denied"; } - if (cause.equals("CREATE failed")) { + if ("CREATE failed".equals(cause)) { ZimbraLog.imap.warn(cause, e); } else { ZimbraLog.imap.info("%s: %s", cause, path); @@ -1742,7 +1914,7 @@ boolean doCREATE(String tag, ImapPath path) throws IOException, ImapThrottledExc return true; } - boolean doDELETE(String tag, ImapPath path) throws IOException { + private boolean doDELETE(String tag, ImapPath path) throws IOException { if (!checkState(tag, State.AUTHENTICATED)) { return true; } @@ -1810,12 +1982,13 @@ boolean doDELETE(String tag, ImapPath path) throws IOException { return true; } - boolean doRENAME(String tag, ImapPath oldPath, ImapPath newPath) throws IOException { + private boolean doRENAME(String tag, ImapPath oldPath, ImapPath newPath) throws IOException { if (!checkState(tag, State.AUTHENTICATED)) { return true; } try { - Account source = oldPath.getOwnerAccount(), target = newPath.getOwnerAccount(); + Account source = oldPath.getOwnerAccount(); + Account target = newPath.getOwnerAccount(); if (source == null || target == null) { ZimbraLog.imap.info("RENAME failed: no such account for %s or %s", oldPath, newPath); sendNO(tag, "RENAME failed: no such account"); @@ -1868,7 +2041,7 @@ boolean doRENAME(String tag, ImapPath oldPath, ImapPath newPath) throws IOExcept return true; } - boolean doSUBSCRIBE(String tag, ImapPath path) throws IOException { + private boolean doSUBSCRIBE(String tag, ImapPath path) throws IOException { if (!checkState(tag, State.AUTHENTICATED)) { return true; } @@ -1906,7 +2079,7 @@ boolean doSUBSCRIBE(String tag, ImapPath path) throws IOException { return true; } - boolean doUNSUBSCRIBE(String tag, ImapPath path) throws IOException { + private boolean doUNSUBSCRIBE(String tag, ImapPath path) throws IOException { if (!checkState(tag, State.AUTHENTICATED)) { return true; } @@ -1936,16 +2109,8 @@ boolean doUNSUBSCRIBE(String tag, ImapPath path) throws IOException { return true; } - private static final byte SELECT_SUBSCRIBED = 0x01; - private static final byte SELECT_REMOTE = 0x02; - private static final byte SELECT_RECURSIVE = 0x04; - - private static final byte RETURN_SUBSCRIBED = 0x01; - private static final byte RETURN_CHILDREN = 0x02; - private static final byte RETURN_XLIST = 0x04; - - boolean doLIST(String tag, String referenceName, Set mailboxNames, byte selectOptions, byte returnOptions, - byte status) throws ImapException, IOException { + private boolean doLIST(String tag, String referenceName, Set mailboxNames, + byte selectOptions, byte returnOptions, byte status) throws ImapException, IOException { checkCommandThrottle(new ListCommand(referenceName, mailboxNames, selectOptions, returnOptions, status)); if (!checkState(tag, State.AUTHENTICATED)) { return true; @@ -2126,15 +2291,6 @@ boolean doLIST(String tag, String referenceName, Set mailboxNames, byte return true; } - private static final boolean[] REGEXP_ESCAPED = new boolean[128]; - static { - REGEXP_ESCAPED['('] = REGEXP_ESCAPED[')'] = REGEXP_ESCAPED['.'] = true; - REGEXP_ESCAPED['['] = REGEXP_ESCAPED[']'] = REGEXP_ESCAPED['|'] = true; - REGEXP_ESCAPED['^'] = REGEXP_ESCAPED['$'] = REGEXP_ESCAPED['?'] = true; - REGEXP_ESCAPED['{'] = REGEXP_ESCAPED['}'] = REGEXP_ESCAPED['*'] = true; - REGEXP_ESCAPED['\\'] = true; - } - private static Pair resolvePath(String referenceName, String mailboxName) { int startWildcards = referenceName.length(); String resolved = mailboxName; @@ -2183,11 +2339,14 @@ private void accumulatePaths(ImapMailboxStore imapStore, String owner, ImapPath boolean isMailFolders = Provisioning.getInstance().getLocalServer().isImapDisplayMailFoldersOnly(); for (FolderStore folderStore : visibleFolders) { //bug 6418 ..filter out folders which are contacts and chat for LIST command. - if(isMailFolders) { - // chat has item type of message. hence ignoring the chat folder by name. - if (folderStore.isChatsFolder() || (folderStore.getName().equals ("Chats"))) { - continue; - } + // chat has item type of message. hence ignoring the chat folder by name. + if (isMailFolders && (folderStore.isChatsFolder() || (folderStore.getName().equals ("Chats")))) { + continue; + } + /* In remote case, list includes children of mountpoints - filter them out here and instead + * tackle them via accumulatePaths in the owner mailbox. */ + if (!(folderStore instanceof MountpointStore) && isInMountpointHierarchy(folderStore)) { + continue; } ImapPath path = relativeTo == null ? new ImapPath(owner, folderStore, credentials) : new ImapPath(owner, folderStore, relativeTo); @@ -2206,6 +2365,21 @@ private void accumulatePaths(ImapMailboxStore imapStore, String owner, ImapPath } } + /** Note this only really works with ZFolders - but that is sufficient for current needs */ + private boolean isInMountpointHierarchy(FolderStore fldr) throws ServiceException { + if (fldr == null) { + return false; + } + if (fldr instanceof MountpointStore) { + return true; + } + if (fldr instanceof ZFolder) { + ZFolder zfldr = (ZFolder) fldr; + return isInMountpointHierarchy(zfldr.getParent()); + } + return false; + } + private static boolean pathMatches(String path, Pattern pattern) throws ServiceException { return AccessBoundedRegex.matches(path, pattern, @@ -2301,7 +2475,7 @@ private String getFolderAttrs(ImapPath path, byte returnOptions, Map subscriptions) throws ServiceException { if (path.belongsTo(credentials)) { - FolderStore folderStore = path.getFolder();; + FolderStore folderStore = path.getFolder(); if (folderStore != null) { return folderStore.isIMAPSubscribed(); } else { @@ -2317,7 +2491,8 @@ private boolean isPathSubscribed(ImapPath path, Set subscriptions) throw return false; } - boolean doLSUB(String tag, String referenceName, String mailboxName) throws ImapException, IOException { + private boolean doLSUB(String tag, String referenceName, String mailboxName) + throws ImapException, IOException { checkCommandThrottle(new LsubCommand(referenceName, mailboxName)); if (!checkState(tag, State.AUTHENTICATED)) { return true; @@ -2344,6 +2519,11 @@ boolean doLSUB(String tag, String referenceName, String mailboxName) throws Imap if ((isMailFolders) && (folder.isChatsFolder() || (folder.getName().equals ("Chats")))) { continue; } + String fAcctId = folder.getFolderItemIdentifier().accountId; + if ((fAcctId != null) && !fAcctId.equals(credentials.getAccountId())) { + // ignore imapSubscribed flag on remote folders - they apply to the remote acct + continue; + } if (folder.isIMAPSubscribed()) { checkSubscription(new SubscribedImapPath( new ImapPath(null, folder, credentials)), pattern, childPattern, hits); @@ -2470,19 +2650,16 @@ private void checkSubscription(SubscribedImapPath path, Pattern pattern, Pattern path.addUnsubsribedMatchingParents(pattern, hits); } - static final int STATUS_MESSAGES = 0x01; - static final int STATUS_RECENT = 0x02; - static final int STATUS_UIDNEXT = 0x04; - static final int STATUS_UIDVALIDITY = 0x08; - static final int STATUS_UNSEEN = 0x10; - static final int STATUS_HIGHESTMODSEQ = 0x20; - - boolean doSTATUS(String tag, ImapPath path, byte status) throws ImapException, IOException { + private boolean doSTATUS(String tag, ImapPath path, StatusDataItemNames dataItemNames) + throws ImapException, IOException { if (!checkState(tag, State.AUTHENTICATED)) { return true; } try { path.canonicalize(); + if (imapProxy != null && path.isEquivalent(imapProxy.getPath())) { + return imapProxy.status(tag, dataItemNames.toString()); + } if (!path.isVisible()) { ZimbraLog.imap.info("STATUS failed: folder not visible: %s", path); sendNO(tag, "STATUS failed"); @@ -2494,7 +2671,7 @@ boolean doSTATUS(String tag, ImapPath path, byte status) throws ImapException, I mbox.noOp(); } } - sendUntagged(status(path, status)); + sendUntagged(status(path, dataItemNames.cmdStatus)); } catch (ServiceException e) { if (e.getCode().equals(MailServiceException.NO_SUCH_FOLDER)) { ZimbraLog.imap.info("STATUS failed: no such folder: %s", path); @@ -2510,11 +2687,16 @@ boolean doSTATUS(String tag, ImapPath path, byte status) throws ImapException, I return true; } - String status(ImapPath path, byte status) throws ImapException, ServiceException { + private String status(ImapPath path, byte status) throws ImapException, ServiceException { StringBuilder data = new StringBuilder("STATUS ").append(path.asUtf7String()).append(" ("); int empty = data.length(); - int messages, recent, uidnext, uvv, unread, modseq; + int messages; + int recent; + int uidnext; + int uvv; + int unread; + int modseq; MailboxStore mboxStore = path.getOwnerMailbox(); if (mboxStore != null) { FolderStore folder = path.getFolder(); @@ -2523,7 +2705,7 @@ String status(ImapPath path, byte status) throws ImapException, ServiceException } ImapFolder i4folder = getSelectedFolder(); messages = folder.getImapMessageCount(); - if ((status & STATUS_RECENT) == 0) { + if ((status & StatusDataItemNames.STATUS_RECENT) == 0) { recent = -1; } else if (messages == 0) { recent = 0; @@ -2545,30 +2727,31 @@ String status(ImapPath path, byte status) throws ImapException, ServiceException throw AccountServiceException.NO_SUCH_ACCOUNT(path.getOwner()); } - if (messages >= 0 && (status & STATUS_MESSAGES) != 0) { + if (messages >= 0 && (status & StatusDataItemNames.STATUS_MESSAGES) != 0) { data.append(data.length() != empty ? " " : "").append("MESSAGES ").append(messages); } - if (recent >= 0 && (status & STATUS_RECENT) != 0) { + if (recent >= 0 && (status & StatusDataItemNames.STATUS_RECENT) != 0) { data.append(data.length() != empty ? " " : "").append("RECENT ").append(recent); } // note: we're not supporting UIDNEXT for search folders; see the comments in selectFolder() - if (uidnext > 0 && (status & STATUS_UIDNEXT) != 0) { + if (uidnext > 0 && (status & StatusDataItemNames.STATUS_UIDNEXT) != 0) { data.append(data.length() != empty ? " " : "").append("UIDNEXT ").append(uidnext); } - if (uvv > 0 && (status & STATUS_UIDVALIDITY) != 0) { + if (uvv > 0 && (status & StatusDataItemNames.STATUS_UIDVALIDITY) != 0) { data.append(data.length() != empty ? " " : "").append("UIDVALIDITY ").append(uvv); } - if (unread >= 0 && (status & STATUS_UNSEEN) != 0) { + if (unread >= 0 && (status & StatusDataItemNames.STATUS_UNSEEN) != 0) { data.append(data.length() != empty ? " " : "").append("UNSEEN ").append(unread); } - if (modseq >= 0 && (status & STATUS_HIGHESTMODSEQ) != 0) { + if (modseq >= 0 && (status & StatusDataItemNames.STATUS_HIGHESTMODSEQ) != 0) { data.append(data.length() != empty ? " " : "").append("HIGHESTMODSEQ ").append(modseq); } return data.append(')').toString(); } - boolean doAPPEND(String tag, ImapPath path, List appends) throws IOException, ImapException { + private boolean doAPPEND(String tag, ImapPath path, List appends) + throws IOException, ImapException { checkCommandThrottle(new AppendCommand(path, appends)); if (!checkState(tag, State.AUTHENTICATED)) { return true; @@ -2655,16 +2838,13 @@ private void deleteTags(List ltags) { } } - static final boolean IDLE_START = true; - static final boolean IDLE_STOP = false; - // RFC 2177 3: "The IDLE command is sent from the client to the server when the client is // ready to accept unsolicited mailbox update messages. The server requests // a response to the IDLE command using the continuation ("+") response. The // IDLE command remains active until the client responds to the continuation, // and as long as an IDLE command is active, the server is now free to send // untagged EXISTS, EXPUNGE, and other messages at any time." - boolean doIDLE(String tag, boolean begin, boolean success, ImapRequest req) throws IOException { + private boolean doIDLE(String tag, boolean begin, boolean success, ImapRequest req) throws IOException { if (!checkState(tag, State.AUTHENTICATED)) return true; @@ -2692,7 +2872,7 @@ boolean doIDLE(String tag, boolean begin, boolean success, ImapRequest req) thro return true; } - boolean doSETQUOTA(String tag) throws IOException { + private boolean doSETQUOTA(String tag) throws IOException { if (!checkState(tag, State.AUTHENTICATED)) { return true; } @@ -2701,7 +2881,7 @@ boolean doSETQUOTA(String tag) throws IOException { return true; } - boolean doGETQUOTA(String tag, ImapPath qroot) throws IOException { + private boolean doGETQUOTA(String tag, ImapPath qroot) throws IOException { if (!checkState(tag, State.AUTHENTICATED)) return true; @@ -2731,7 +2911,7 @@ boolean doGETQUOTA(String tag, ImapPath qroot) throws IOException { return true; } - boolean doGETQUOTAROOT(String tag, ImapPath qroot) throws IOException { + private boolean doGETQUOTAROOT(String tag, ImapPath qroot) throws IOException { if (!checkState(tag, State.AUTHENTICATED)) return true; @@ -2770,7 +2950,7 @@ boolean doGETQUOTAROOT(String tag, ImapPath qroot) throws IOException { return true; } - boolean doNAMESPACE(String tag) throws IOException { + private boolean doNAMESPACE(String tag) throws IOException { if (!checkState(tag, State.AUTHENTICATED)) { return true; } @@ -2780,12 +2960,6 @@ boolean doNAMESPACE(String tag) throws IOException { return true; } - private static final String IMAP_READ_RIGHTS = "lr"; - private static final String IMAP_WRITE_RIGHTS = "sw"; - private static final String IMAP_INSERT_RIGHTS = "ick"; - private static final String IMAP_DELETE_RIGHTS = "xted"; - private static final String IMAP_ADMIN_RIGHTS = "a"; - // Returns whether all of a set of linked RFC 4314 rights is contained within a string. private boolean allRightsPresent(final String i4rights, final String linked) { for (int i = 0; i < linked.length(); i++) { @@ -2805,10 +2979,10 @@ public GranteeIdAndType(String granteeId, byte typ) { } } - GranteeIdAndType getPrincipalGranteeInfo(String principal) throws ServiceException { + private GranteeIdAndType getPrincipalGranteeInfo(String principal) throws ServiceException { String granteeId = null; byte granteeType = ACL.GRANTEE_AUTHUSER; - if (principal.equals("anyone")) { + if ("anyone".equals(principal)) { granteeId = GuestAccount.GUID_AUTHUSER; granteeType = ACL.GRANTEE_AUTHUSER; } else { @@ -2825,7 +2999,8 @@ GranteeIdAndType getPrincipalGranteeInfo(String principal) throws ServiceExcepti return new GranteeIdAndType(granteeId, granteeType); } - boolean doSETACL(String tag, ImapPath path, String principal, String i4rights, StoreAction action) throws IOException { + private boolean doSETACL(String tag, ImapPath path, String principal, String i4rights, + StoreAction action) throws IOException { if (!checkState(tag, State.AUTHENTICATED)) return true; @@ -2928,7 +3103,7 @@ boolean doSETACL(String tag, ImapPath path, String principal, String i4rights, S return true; } - boolean doDELETEACL(String tag, ImapPath path, String principal) throws IOException { + private boolean doDELETEACL(String tag, ImapPath path, String principal) throws IOException { if (!checkState(tag, State.AUTHENTICATED)) { return true; } @@ -2979,7 +3154,7 @@ boolean doDELETEACL(String tag, ImapPath path, String principal) throws IOExcept return true; } - boolean doGETACL(String tag, ImapPath path) throws IOException { + private boolean doGETACL(String tag, ImapPath path) throws IOException { if (!checkState(tag, State.AUTHENTICATED)) { return true; } @@ -3037,9 +3212,6 @@ boolean doGETACL(String tag, ImapPath path) throws IOException { return true; } - /* The set of rights required to create a new subfolder in ZCS. */ - private final short SUBFOLDER_RIGHTS = ACL.RIGHT_INSERT | ACL.RIGHT_READ; - /* Converts a Zimbra rights bitmask to an RFC 4314-compatible rights string */ private String exportRights(short rights) { StringBuilder imapRights = new StringBuilder(12); @@ -3064,21 +3236,13 @@ private String exportRights(short rights) { return imapRights.length() == 0 ? "\"\"" : imapRights.toString(); } - /* All the supported IMAP rights, concatenated together into a single string. */ - private static final String IMAP_CONCATENATED_RIGHTS = IMAP_READ_RIGHTS + IMAP_WRITE_RIGHTS + IMAP_INSERT_RIGHTS + - IMAP_DELETE_RIGHTS + IMAP_ADMIN_RIGHTS; - /* All the supported IMAP rights, with linked sets of rights - * grouped together and the groups delimited by spaces. */ - private static final String IMAP_DELIMITED_RIGHTS = IMAP_READ_RIGHTS + ' ' + IMAP_WRITE_RIGHTS + ' ' + - IMAP_INSERT_RIGHTS + ' ' + IMAP_DELETE_RIGHTS + ' ' + IMAP_ADMIN_RIGHTS; - - boolean doLISTRIGHTS(String tag, ImapPath path, String principal) throws IOException { + private boolean doLISTRIGHTS(String tag, ImapPath path, String principal) throws IOException { if (!checkState(tag, State.AUTHENTICATED)) { return true; } boolean isOwner = false; try { - if (!principal.equals("anyone")) { + if (!"anyone".equals(principal)) { Account acct = Provisioning.getInstance().get(Key.AccountBy.name, principal); if (acct == null) { throw AccountServiceException.NO_SUCH_ACCOUNT(principal); @@ -3113,7 +3277,7 @@ boolean doLISTRIGHTS(String tag, ImapPath path, String principal) throws IOExcep return true; } - boolean doMYRIGHTS(String tag, ImapPath path) throws IOException { + private boolean doMYRIGHTS(String tag, ImapPath path) throws IOException { if (!checkState(tag, State.AUTHENTICATED)) { return true; } @@ -3143,7 +3307,7 @@ boolean doMYRIGHTS(String tag, ImapPath path) throws IOException { return true; } - boolean doCHECK(String tag) throws IOException { + private boolean doCHECK(String tag) throws IOException { if (!checkState(tag, State.SELECTED)) { return true; } @@ -3152,7 +3316,7 @@ boolean doCHECK(String tag) throws IOException { return true; } - boolean doCLOSE(String tag) throws IOException, ImapException { + private boolean doCLOSE(String tag) throws IOException, ImapException { if (!checkState(tag, State.SELECTED)) { return true; } @@ -3202,7 +3366,7 @@ boolean doCLOSE(String tag) throws IOException, ImapException { // mailbox and returns the server to the authenticated state. This command // performs the same actions as CLOSE, except that no messages are permanently // removed from the currently selected mailbox." - boolean doUNSELECT(String tag) throws IOException { + private boolean doUNSELECT(String tag) throws IOException { if (!checkState(tag, State.SELECTED)) { return true; } @@ -3212,9 +3376,8 @@ boolean doUNSELECT(String tag) throws IOException { return true; } - private final int SUGGESTED_DELETE_BATCH_SIZE = 30; - - boolean doEXPUNGE(String tag, boolean byUID, String sequenceSet) throws IOException, ImapException { + private boolean doEXPUNGE(String tag, boolean byUID, String sequenceSet) + throws IOException, ImapException { if (!checkState(tag, State.SELECTED)) { return true; } @@ -3268,11 +3431,10 @@ private boolean expungeMessages(String tag, ImapFolder i4folder, String sequence MailboxStore selectedMailbox = selectedFolderListener.getMailbox(); for (int i = 1, max = i4folder.getSize(); i <= max; i++) { ImapMessage i4msg = i4folder.getBySequence(i); - if (i4msg != null && !i4msg.isExpunged() && (i4msg.flags & Flag.BITMASK_DELETED) > 0) { - if (i4set == null || i4set.contains(i4msg)) { + if ( (i4msg != null && !i4msg.isExpunged() && (i4msg.flags & Flag.BITMASK_DELETED) > 0) && + (i4set == null || i4set.contains(i4msg))) { ids.add(i4msg.msgId); changed = true; - } } if (ids.size() >= (i == max ? 1 : SUGGESTED_DELETE_BATCH_SIZE)) { @@ -3302,29 +3464,20 @@ private boolean expungeMessages(String tag, ImapFolder i4folder, String sequence return changed; } - private static final int RETURN_MIN = 0x01; - private static final int RETURN_MAX = 0x02; - private static final int RETURN_ALL = 0x04; - private static final int RETURN_COUNT = 0x08; - private static final int RETURN_SAVE = 0x10; - - private static final int LARGEST_FOLDER_BATCH = 600; - public static final Set ITEM_TYPES = ImapMessage.SUPPORTED_TYPES; - - boolean doSEARCH(String tag, ImapSearch i4search, boolean byUID, Integer options) + protected boolean doSEARCH(String tag, ImapSearch i4search, boolean byUID, Integer options) throws IOException, ImapException { checkCommandThrottle(new SearchCommand(i4search, options)); return search(tag, "SEARCH", i4search, byUID, options, null); } - boolean doSORT(String tag, ImapSearch i4search, boolean byUID, Integer options, List order) - throws IOException, ImapException { + private boolean doSORT(String tag, ImapSearch i4search, boolean byUID, Integer options, + List order) throws IOException, ImapException { checkCommandThrottle(new SortCommand(i4search, options)); return search(tag, "SORT", i4search, byUID, options, order); } - boolean search(String tag, String command, ImapSearch i4search, boolean byUID, Integer options, List order) - throws IOException, ImapException { + private boolean search(String tag, String command, ImapSearch i4search, boolean byUID, Integer options, + List order) throws IOException, ImapException { if (!checkState(tag, State.SELECTED)) { return true; } @@ -3402,7 +3555,8 @@ boolean search(String tag, String command, ImapSearch i4search, boolean byUID, I } int size = hits.size(); - ImapMessage first = null, last = null; + ImapMessage first = null; + ImapMessage last = null; if (size != 0 && options != null && (options & (RETURN_MIN | RETURN_MAX)) != 0) { if (unsorted) { first = ((ImapMessageSet) hits).first(); @@ -3507,7 +3661,8 @@ private ZimbraQueryHitResults runSearch(ImapSearch i4search, ImapFolder i4folder return mbox.searchImap(getContext(), params); } - boolean doTHREAD(String tag, ImapSearch i4search, boolean byUID) throws IOException, ImapException { + private boolean doTHREAD(String tag, ImapSearch i4search, boolean byUID) + throws IOException, ImapException { if (!checkState(tag, State.SELECTED)) { return true; } @@ -3592,39 +3747,20 @@ boolean doTHREAD(String tag, ImapSearch i4search, boolean byUID) throws IOExcept return true; } - static final int FETCH_BODY = 0x0001; - static final int FETCH_BODYSTRUCTURE = 0x0002; - static final int FETCH_ENVELOPE = 0x0004; - static final int FETCH_FLAGS = 0x0008; - static final int FETCH_INTERNALDATE = 0x0010; - static final int FETCH_RFC822_SIZE = 0x0020; - static final int FETCH_BINARY_SIZE = 0x0040; - static final int FETCH_UID = 0x0080; - static final int FETCH_MODSEQ = 0x0100; - static final int FETCH_VANISHED = 0x0200; - static final int FETCH_MARK_READ = 0x1000; - - static final int FETCH_FROM_CACHE = FETCH_FLAGS | FETCH_UID; - static final int FETCH_FROM_MIME = FETCH_BODY | FETCH_BODYSTRUCTURE | FETCH_ENVELOPE; - - static final int FETCH_FAST = FETCH_FLAGS | FETCH_INTERNALDATE | FETCH_RFC822_SIZE; - static final int FETCH_ALL = FETCH_FAST | FETCH_ENVELOPE; - static final int FETCH_FULL = FETCH_ALL | FETCH_BODY; - - boolean doFETCH(String tag, String sequenceSet, int attributes, List parts, boolean byUID, - int changedSince) throws IOException, ImapException { + protected boolean doFETCH(String tag, String sequenceSet, int attributes, List parts, + boolean byUID, int changedSince) throws IOException, ImapException { checkCommandThrottle(new FetchCommand(sequenceSet, attributes, parts)); return fetch(tag, sequenceSet, attributes, parts, byUID, changedSince, true); } - boolean fetch(String tag, String sequenceSet, int attributes, List parts, boolean byUID, - int changedSince, boolean standalone) throws IOException, ImapException { + private boolean fetch(String tag, String sequenceSet, int attributes, List parts, + boolean byUID, int changedSince, boolean standalone) throws IOException, ImapException { return fetch(tag, sequenceSet, attributes, parts, byUID, changedSince, standalone, false /* allowOutOfRangeMsgSeq */); } - boolean fetch(String tag, String sequenceSet, int attributes, List parts, boolean byUID, - int changedSince, boolean standalone, boolean allowOutOfRangeMsgSeq) + private boolean fetch(String tag, String sequenceSet, int attributes, List parts, + boolean byUID, int changedSince, boolean standalone, boolean allowOutOfRangeMsgSeq) throws IOException, ImapException { if (!checkState(tag, State.SELECTED)) { return true; @@ -3958,7 +4094,7 @@ private void fetchStub(ImapMessage i4msg, ImapFolder i4folder, int attributes, L for (ImapPartSpecifier pspec : parts) { // pretending that all messages have 1 text part means we should return NIL for other FETCHes String pnum = pspec.getSectionPart(); - String value = (pnum.equals("") || pnum.equals("1")) ? (pspec.getCommand().equals("BINARY.SIZE") ? "0" : "\"\"") : "NIL"; + String value = ("".equals(pnum) || "1".equals(pnum)) ? (pspec.getCommand().equals("BINARY.SIZE") ? "0" : "\"\"") : "NIL"; result.print((empty ? "" : " ") + pspec + ' ' + value); empty = false; } } @@ -3976,11 +4112,7 @@ private void fetchStub(ImapMessage i4msg, ImapFolder i4folder, int attributes, L i4folder.undirtyMessage(i4msg); } - enum StoreAction { REPLACE, ADD, REMOVE } - - private final int SUGGESTED_BATCH_SIZE = 100; - - boolean doSTORE(String tag, String sequenceSet, List flagNames, StoreAction operation, boolean silent, + private boolean doSTORE(String tag, String sequenceSet, List flagNames, StoreAction operation, boolean silent, int modseq, boolean byUID) throws IOException, ImapException { checkCommandThrottle(new StoreCommand(sequenceSet, flagNames, operation, modseq)); if (!checkState(tag, State.SELECTED)) { @@ -4046,10 +4178,9 @@ boolean doSTORE(String tag, String sequenceSet, List flagNames, StoreAct if (!i4folder.getPath().isWritable(ACL.RIGHT_DELETE)) { throw ServiceException.PERM_DENIED("you do not have permission to set the \\Deleted flag"); } - } else if (i4flag.mPermanent) { - if (!i4folder.getPath().isWritable(ACL.RIGHT_WRITE)) { - throw ServiceException.PERM_DENIED("you do not have permission to set the " + i4flag.mName + " flag"); - } + } else if (i4flag.mPermanent && (!i4folder.getPath().isWritable(ACL.RIGHT_WRITE))) { + throw ServiceException.PERM_DENIED( + "you do not have permission to set the " + i4flag.mName + " flag"); } } } @@ -4197,12 +4328,11 @@ boolean doSTORE(String tag, String sequenceSet, List flagNames, StoreAct return true; } - private final int SUGGESTED_COPY_BATCH_SIZE = 50; - /** * @param path of target folder */ - boolean doCOPY(String tag, String sequenceSet, ImapPath path, boolean byUID) throws IOException, ImapException { + protected boolean doCOPY(String tag, String sequenceSet, ImapPath path, boolean byUID) + throws IOException, ImapException { checkCommandThrottle(new CopyCommand(sequenceSet, path)); if (!checkState(tag, State.SELECTED)) { return true; @@ -4481,57 +4611,57 @@ public void sendNotifications(boolean notifyExpunges, boolean flush) throws IOEx } } - void sendIdleUntagged() throws IOException { + protected void sendIdleUntagged() throws IOException { sendUntagged("NOOP", true); } - void sendOK(String tag, String response) throws IOException { + protected void sendOK(String tag, String response) throws IOException { consecutiveError = 0; sendResponse(tag, "OK " + (Strings.isNullOrEmpty(response) ? " " : response), true); } - void sendNO(String tag, String responsePattern, Object... args) throws IOException { + protected void sendNO(String tag, String responsePattern, Object... args) throws IOException { sendNO(tag, String.format(responsePattern, args)); } - void sendNO(String tag, String response) throws IOException { + protected void sendNO(String tag, String response) throws IOException { consecutiveError++; sendResponse(tag, "NO " + (Strings.isNullOrEmpty(response) ? " " : response), true); } //Bug 97697 - Move imap "BAD parse error" to debug level logging versus warn. - void sendBAD(String tag, String response) throws IOException { + protected void sendBAD(String tag, String response) throws IOException { consecutiveError++; ZimbraLog.imap.debug("BAD %s", response); sendResponse(tag, "BAD " + (Strings.isNullOrEmpty(response) ? " " : response), true); } - void sendBAD(String response) throws IOException { + protected void sendBAD(String response) throws IOException { consecutiveError++; ZimbraLog.imap.debug("BAD %s", response); sendResponse("*", "BAD " + (Strings.isNullOrEmpty(response) ? " " : response), true); } - void sendUntagged(String response) throws IOException { + protected void sendUntagged(String response) throws IOException { sendResponse("*", response, false); } - void sendUntagged(String response, boolean flush) throws IOException { + protected void sendUntagged(String response, boolean flush) throws IOException { sendResponse("*", response, flush); } - void sendContinuation(String response) throws IOException { + protected void sendContinuation(String response) throws IOException { sendResponse("+", response, true); } - void sendGreeting() throws IOException { + protected void sendGreeting() throws IOException { sendUntagged("OK " + config.getGreeting(), true); } - void sendBYE() { + protected void sendBYE() { sendBYE(config.getGoodbye()); } - void sendBYE(String msg) { + protected void sendBYE(String msg) { try { sendUntagged("BYE " + msg, true); } catch (IOException e) { diff --git a/store/src/java/com/zimbra/cs/imap/ImapMailboxStore.java b/store/src/java/com/zimbra/cs/imap/ImapMailboxStore.java index a41dd26171c..5d9f33b81e2 100644 --- a/store/src/java/com/zimbra/cs/imap/ImapMailboxStore.java +++ b/store/src/java/com/zimbra/cs/imap/ImapMailboxStore.java @@ -84,7 +84,10 @@ public List getFlagList(boolean permanentOnly) { public abstract void resetImapUid(List renumber) throws ServiceException; public abstract void beginTrackingImap() throws ServiceException; public abstract void deleteMessages(OperationContext octxt, List ids); - /** @return List of IMAP UIDs */ + /** + * MUST only be called when the source items and target folder are in the same mailbox + * @return List of IMAP UIDs + */ public abstract List imapCopy(OperationContext octxt, int[] itemIds, MailItemType type, int folderId) throws IOException, ServiceException; public abstract InputStreamWithSize getByImapId(OperationContext octxt, int imapId, String folderId, String resolvedPath) @@ -109,7 +112,6 @@ public abstract List openImapFolder(OperationContext octxt, ItemIde public abstract void registerWithImapServerListener(ImapListener listener); public abstract void unregisterWithImapServerListener(ImapListener listener); - public abstract List getListeners(int folderId); public List getListeners(ItemIdentifier ident) { String acctId = ident.accountId != null ? ident.accountId : getAccountId(); try { diff --git a/store/src/java/com/zimbra/cs/imap/ImapPath.java b/store/src/java/com/zimbra/cs/imap/ImapPath.java index e033f96804b..e4e9a458e97 100644 --- a/store/src/java/com/zimbra/cs/imap/ImapPath.java +++ b/store/src/java/com/zimbra/cs/imap/ImapPath.java @@ -19,6 +19,7 @@ import java.nio.ByteBuffer; import java.nio.charset.Charset; +import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Strings; import com.zimbra.client.ZFolder; import com.zimbra.client.ZMailbox; @@ -31,6 +32,7 @@ import com.zimbra.common.mailbox.MountpointStore; import com.zimbra.common.service.ServiceException; import com.zimbra.common.util.Pair; +import com.zimbra.common.util.SystemUtil; import com.zimbra.common.util.ZimbraLog; import com.zimbra.cs.account.Account; import com.zimbra.cs.account.AccountServiceException; @@ -179,7 +181,8 @@ public boolean isEquivalent(ImapPath other) { return (mOwner == null ? 0 : mOwner.toUpperCase().hashCode()) ^ mPath.toUpperCase().hashCode() ^ (mCredentials == null ? 0 : mCredentials.hashCode()); } - protected ImapPath canonicalize() throws ServiceException { + @VisibleForTesting + public ImapPath canonicalize() throws ServiceException { getFolder(); String path = folder.getPath(); @@ -200,7 +203,6 @@ protected ImapPath canonicalize() throws ServiceException { return this; } - protected String getOwner() { return mOwner; } @@ -254,8 +256,8 @@ protected boolean onLocalServer() throws ServiceException { } protected MailboxStore getOwnerMailbox() throws ServiceException { - getOwnerImapMailboxStore(!onLocalServer()); - return (null == imapMboxStore) ? null : imapMboxStore.getMailboxStore(); + ImapMailboxStore store = getOwnerImapMailboxStore(); + return (null == store) ? null : store.getMailboxStore(); } protected ImapMailboxStore getOwnerImapMailboxStore() throws ServiceException { @@ -307,6 +309,7 @@ private ZMailbox getZMailboxForAccount(Account target) throws ServiceException { ZMailbox.Options options = new ZMailbox.Options(AuthProvider.getAuthToken(acct).getEncoded(), AccountUtil.getSoapUri(target)); options.setTargetAccount(target.getName()); + options.setUserAgent("zclient-imap-onBehalfOf", SystemUtil.getProductVersion()); options.setNoSession(true); options.setAlwaysRefreshFolders(true); ZMailbox zmbx = ZMailbox.getMailbox(options); @@ -345,7 +348,8 @@ private OperationContext getContext() throws ServiceException { return (mCredentials == null ? null : mCredentials.getContext()); } - protected FolderStore getFolder() throws ServiceException { + @VisibleForTesting + public FolderStore getFolder() throws ServiceException { if (useReferent()) { return getReferent().getFolder(); } @@ -420,7 +424,8 @@ protected boolean useReferent() throws ServiceException { * @return If the folder is a mountpoint (i.e. an accepted share), may return an ImapPath representing * that, otherwise, the value is this. */ - protected ImapPath getReferent() throws ServiceException { + @VisibleForTesting + public ImapPath getReferent() throws ServiceException { if (mReferent != null) { return mReferent; } @@ -475,8 +480,36 @@ protected ImapPath getReferent() throws ServiceException { return mReferent; } - String owner = mCredentials != null && mCredentials.getAccountId().equalsIgnoreCase(target.getId()) ? null + ImapMailboxStore imapMailboxStore = setupMailboxStoreForTarget(target, iidRemote); + if (null == imapMailboxStore) { + return mReferent; + } + FolderStore fldr = imapMailboxStore.getMailboxStore().getFolderById( + getContext(), Integer.toString(iidRemote.getId())); + if (fldr == null) { + return mReferent; + } + String owner = getOwner(target); + if (Strings.isNullOrEmpty(subpathRemote)) { + mReferent = new ImapPath(owner, fldr, mCredentials); + } else { + mReferent = ImapPath.get(owner, fldr.getPath() + + (fldr.getPath().equals("/") ? "" : "/") + subpathRemote, mCredentials, imapMailboxStore); + } + + if (mReferent != this) { + mReferent.mScope = Scope.REFERENCE; + } + return mReferent; + } + + private String getOwner(Account target) { + return mCredentials != null && mCredentials.getAccountId().equalsIgnoreCase(target.getId()) ? null : target.getName(); + } + + private ImapMailboxStore setupMailboxStoreForTarget(Account target, ItemId iidRemote) + throws ServiceException { ImapMailboxStore imapMailboxStore = null; // if both target and owner are on local server and using local imap if (Provisioning.onLocalServer(target) && onLocalServer()) { @@ -490,38 +523,20 @@ protected ImapPath getReferent() throws ServiceException { Account acct = mCredentials == null ? null : Provisioning.getInstance().get(AccountBy.id, mCredentials.getAccountId()); if (acct == null) { - return mReferent; + return null; } try { ZMailbox zmbx = getZMailboxForAccount(target); ZFolder zfolder = zmbx.getFolderById(iidRemote.toString(mCredentials.getAccountId())); if (zfolder == null) { - return mReferent; + return null; } imapMailboxStore = ImapMailboxStore.get(zmbx); } catch (ServiceException e) { ZimbraLog.imap.debug("Unexpected exception", e); } } - if (null == imapMailboxStore) { - return mReferent; - } - FolderStore fldr = imapMailboxStore.getMailboxStore().getFolderById( - getContext(), Integer.toString(iidRemote.getId())); - if (fldr == null) { - return mReferent; - } - if (Strings.isNullOrEmpty(subpathRemote)) { - mReferent = new ImapPath(owner, fldr, mCredentials); - } else { - mReferent = ImapPath.get(owner, fldr.getPath() + - (fldr.getPath().equals("/") ? "" : "/") + subpathRemote, mCredentials, imapMailboxStore); - } - - if (mReferent != this) { - mReferent.mScope = Scope.REFERENCE; - } - return mReferent; + return imapMailboxStore; } protected short getFolderRights() throws ServiceException { @@ -535,7 +550,6 @@ protected short getFolderRights() throws ServiceException { } } - protected boolean isCreatable() { String path = mPath.toLowerCase(); return !path.matches("\\s*notebook\\s*(/.*)?") && diff --git a/store/src/java/com/zimbra/cs/imap/ImapProxy.java b/store/src/java/com/zimbra/cs/imap/ImapProxy.java index 20ef01c02ea..c9497475769 100644 --- a/store/src/java/com/zimbra/cs/imap/ImapProxy.java +++ b/store/src/java/com/zimbra/cs/imap/ImapProxy.java @@ -56,7 +56,7 @@ public final class ImapProxy { private ImapConnection connection; private Thread idleThread; - ImapProxy(final ImapHandler handler, final ImapPath path) throws ServiceException { + protected ImapProxy(final ImapHandler handler, final ImapPath path) throws ServiceException { this.handler = handler; this.path = path; @@ -108,7 +108,7 @@ public final class ImapProxy { /** * For testing. */ - ImapProxy(InetSocketAddress remote, String username, String password, ImapHandler handler) + protected ImapProxy(InetSocketAddress remote, String username, String password, ImapHandler handler) throws IOException, LoginException { this.handler = handler; path = null; @@ -135,11 +135,11 @@ private IDInfo createIDInfo(ImapHandler handler) { return id; } - ImapPath getPath() { + protected ImapPath getPath() { return path; } - void dropConnection() { + protected void dropConnection() { ImapConnection conn = connection; connection = null; if (conn == null) @@ -158,7 +158,7 @@ void dropConnection() { * @return whether the SELECT was successful (i.e. it returned a tagged {@code OK} response) * @throws ImapProxyException network error with the remote IMAP server */ - boolean select(final String tag, final byte params, final QResyncInfo qri) + protected boolean select(final String tag, final byte params, final QResyncInfo qri) throws ImapProxyException, ServiceException { // FIXME: may need to send an ENABLE before the SELECT @@ -184,6 +184,21 @@ boolean select(final String tag, final byte params, final QResyncInfo qri) return proxyCommand(select.append("\r\n").toString().getBytes(), true, false); } + /** + * Proxy STATUS command. + * @param params - e.g. "(EXISTS RECENT)" + */ + protected boolean status(final String tag, final String params) + throws ImapProxyException, ServiceException { + String command = "STATUS"; + StringBuilder select = new StringBuilder(100); + select.append(tag).append(' ').append(command).append(' '); + select.append(path.getReferent().asUtf7String()); + select.append(' '); + select.append(params); + return proxyCommand(select.append("\r\n").toString().getBytes(), true, false); + } + /** * Proxy IDLE command. * @@ -193,7 +208,7 @@ boolean select(final String tag, final byte params, final QResyncInfo qri) * @throws ImapProxyException network error with the remote IMAP server * @throws IOException error on reading the request data */ - boolean idle(final ImapRequest req, final boolean begin) throws ImapProxyException, IOException { + protected boolean idle(final ImapRequest req, final boolean begin) throws ImapProxyException, IOException { if (begin == ImapHandler.IDLE_STOP) { // check state -- don't want to send DONE if we're somehow not in IDLE if (handler == null) { @@ -258,7 +273,7 @@ public void run() { * @throws ImapProxyException network error with the remote IMAP server * @throws IOException error on reading the request data */ - boolean proxy(final ImapRequest req) throws ImapProxyException, IOException { + protected boolean proxy(final ImapRequest req) throws ImapProxyException, IOException { proxyCommand(req.toByteArray(), true, false); return true; } @@ -271,7 +286,7 @@ boolean proxy(final ImapRequest req) throws ImapProxyException, IOException { * @return always true * @throws ImapProxyException network error with the remote IMAP server */ - boolean proxy(final String tag, final String command) throws ImapProxyException { + protected boolean proxy(final String tag, final String command) throws ImapProxyException { proxyCommand((tag + ' ' + command + "\r\n").getBytes(), true, false); return true; } @@ -281,7 +296,7 @@ boolean proxy(final String tag, final String command) throws ImapProxyException * * @throws ImapProxyException network error with the remote IMAP server */ - void fetchNotifications() throws ImapProxyException { + protected void fetchNotifications() throws ImapProxyException { String tag = connection == null ? "1" : connection.newTag(); proxyCommand((tag + " NOOP\r\n").getBytes(), false, false); } @@ -330,8 +345,12 @@ private boolean proxyCommand(byte[] payload, boolean includeTaggedResponse, bool StringBuilder debug = proxy && ZimbraLog.imap.isDebugEnabled() ? new StringBuilder(" pxy: ") : null; StringBuilder condition = new StringBuilder(10); - boolean quoted = false, escaped = false, space1 = false, space2 = false; - int c, literal = -1; + boolean quoted = false; + boolean escaped = false; + boolean space1 = false; + boolean space2 = false; + int c; + int literal = -1; while ((c = min.read()) != -1) { // check for success and also determine whether we should be paying attention to structure if (!space2) { @@ -417,7 +436,6 @@ else if (literal != -1 && c >= '0' && c <= '9') return success; } - public static final class ZimbraClientAuthenticator extends Authenticator { private String username, authtoken; private boolean complete; diff --git a/store/src/java/com/zimbra/cs/imap/ImapRemoteSession.java b/store/src/java/com/zimbra/cs/imap/ImapRemoteSession.java index 45db14b704c..10054a74ed3 100644 --- a/store/src/java/com/zimbra/cs/imap/ImapRemoteSession.java +++ b/store/src/java/com/zimbra/cs/imap/ImapRemoteSession.java @@ -21,6 +21,7 @@ import com.zimbra.client.ZMailbox; import com.zimbra.common.mailbox.BaseItemInfo; import com.zimbra.common.mailbox.MailItemType; +import com.zimbra.common.mailbox.MailboxStore; import com.zimbra.common.service.ServiceException; import com.zimbra.common.util.ZimbraLog; import com.zimbra.cs.session.PendingModifications; @@ -92,7 +93,22 @@ protected boolean isRegisteredInCache() { private void unregisterFromRemoteServer() { try { - ImapServerListenerPool.getInstance().getForAccountId(mailbox.getAccountId()).removeListener(this); + MailboxStore mbs = mailbox; + if (mbs == null) { + ZimbraLog.imap.info( + "ImapRemoteSession.unregisterFromRemoteServer called but mailbox=null - %s\n%s", + this, ZimbraLog.getStackTrace(5)); + return; + } + ImapServerListener listener = + ImapServerListenerPool.getInstance().getForAccountId(mbs.getAccountId()); + if (listener == null) { + ZimbraLog.imap.info( + "ImapRemoteSession.unregisterFromRemoteServer called but listener=null - %s\n%s", + this, ZimbraLog.getStackTrace(5)); + return; + } + listener.removeListener(this); } catch (ServiceException e) { ZimbraLog.imap.error(e); } diff --git a/store/src/java/com/zimbra/cs/imap/LocalImapMailboxStore.java b/store/src/java/com/zimbra/cs/imap/LocalImapMailboxStore.java index 734c1e44e36..f9ad8c21ed7 100644 --- a/store/src/java/com/zimbra/cs/imap/LocalImapMailboxStore.java +++ b/store/src/java/com/zimbra/cs/imap/LocalImapMailboxStore.java @@ -40,7 +40,6 @@ import com.zimbra.cs.mailbox.Metadata; import com.zimbra.cs.mailbox.MetadataList; import com.zimbra.cs.mailbox.OperationContext; -import com.zimbra.cs.session.Session; import com.zimbra.cs.util.AccountUtil; public class LocalImapMailboxStore extends ImapMailboxStore { @@ -108,7 +107,10 @@ public void deleteMessages(OperationContext octxt, List ids) { } } - /** @return List of IMAP UIDs */ + /** + * MUST only be called when the source items and target folder are in the same mailbox + * @return List of IMAP UIDs + */ @Override public List imapCopy(OperationContext octxt, int[] itemIds, MailItemType type, int folderId) throws IOException, ServiceException { @@ -178,21 +180,6 @@ public Collection getVisibleFolders(OperationContext octxt, ImapCre return fStores; } - @Override - public List getListeners(int folderId) { - List sessions = mailbox.getListeners(Session.Type.IMAP); - List listeners = Lists.newArrayListWithCapacity(sessions.size()); - for (Session sess : sessions) { - if (sess instanceof ImapSession) { - ImapSession imapSess = (ImapSession) sess; - if (folderId == imapSess.getFolderId()) { - listeners.add((ImapSession)sess); - } - } - } - return listeners; - } - public boolean attachmentsIndexingEnabled() throws ServiceException { return mailbox.attachmentsIndexingEnabled(); } diff --git a/store/src/java/com/zimbra/cs/imap/RemoteImapMailboxStore.java b/store/src/java/com/zimbra/cs/imap/RemoteImapMailboxStore.java index c36b55747a2..6d9f637c112 100644 --- a/store/src/java/com/zimbra/cs/imap/RemoteImapMailboxStore.java +++ b/store/src/java/com/zimbra/cs/imap/RemoteImapMailboxStore.java @@ -17,11 +17,8 @@ package com.zimbra.cs.imap; import java.io.IOException; -import java.io.InputStream; import java.util.ArrayList; import java.util.Collection; -import java.util.Collections; -import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.Set; @@ -45,11 +42,8 @@ import com.zimbra.cs.account.AuthTokenException; import com.zimbra.cs.account.Provisioning; import com.zimbra.cs.imap.ImapFlagCache.ImapFlag; -import com.zimbra.cs.mailbox.Flag; import com.zimbra.cs.mailbox.OperationContext; import com.zimbra.cs.service.UserServlet; -import com.zimbra.cs.service.util.ItemId; -import com.zimbra.cs.store.Blob; import com.zimbra.cs.util.AccountUtil; import com.zimbra.soap.mail.type.ImapMessageInfo; @@ -107,7 +101,10 @@ public void deleteMessages(OperationContext octxt, List ids) { } } - /** @return List of IMAP UIDs */ + /** + * MUST only be called when the source items and target folder are in the same mailbox + * @return List of IMAP UIDs + */ @Override public List imapCopy(OperationContext octxt, int[] itemIds, MailItemType type, int folderId) throws IOException, ServiceException { @@ -172,22 +169,6 @@ public Collection getVisibleFolders(OperationContext octxt, ImapCre return fStores; } - @Override - public List getListeners(int folderId) { - try { - ImapServerListener listener = ImapServerListenerPool.getInstance().get(zMailbox); - List listeners = new ArrayList(); - for (ImapRemoteSession sess: listener.getListeners(zMailbox.getAccountId(), folderId)) { - listeners.add(sess); - } - return listeners; - } catch (ServiceException e) { - ZimbraLog.imap.debug("Problem getting listeners for account %s from ImapServerListener (folderId=%s)", - accountId, folderId, e); - return Collections.emptyList(); - } - } - @Override public boolean addressMatchesAccountOrSendAs(String givenAddress) throws ServiceException { return (AccountUtil.addressMatchesAccountOrSendAs(getAccount(), givenAddress)); @@ -232,16 +213,6 @@ public int getImapRECENT(OperationContext octxt, FolderStore folder) throws Serv return ((ZFolder) folder).getImapRECENT(); } - public int store(String folderId, Blob content, Date date, int msgFlags) - throws ImapSessionClosedException, ServiceException, IOException { - String id; - try (InputStream is = content.getInputStream()) { - id = zMailbox.addMessage(folderId, Flag.toString(msgFlags), (String) null, date.getTime(), is, - content.getRawSize(), true); - } - return new ItemId(id, accountId).getId(); - } - @Override public MailboxStore getMailboxStore() { return zMailbox; diff --git a/store/src/java/com/zimbra/cs/mailbox/Folder.java b/store/src/java/com/zimbra/cs/mailbox/Folder.java index b65af748612..ae6f8c2ab15 100644 --- a/store/src/java/com/zimbra/cs/mailbox/Folder.java +++ b/store/src/java/com/zimbra/cs/mailbox/Folder.java @@ -874,7 +874,7 @@ static Folder create(int id, String uuid, Mailbox mbox, Folder parent, String na data.metadata = encodeMetadata(color, 1, 1, custom, attributes, view, null, new SyncData(url), id + 1, 0, mbox.getOperationChangeID(), -1, 0, 0, 0, null, false, -1); data.contentChanged(mbox); - ZimbraLog.mailop.info("adding folder %s: id=%d, parentId=%d.", name, data.id, data.parentId); + ZimbraLog.mailop.debug("adding folder %s: id=%d, parentId=%d.", name, data.id, data.parentId); new DbMailItem(mbox).create(data); Folder folder = new Folder(mbox, data); diff --git a/store/src/java/com/zimbra/cs/mailbox/MailSender.java b/store/src/java/com/zimbra/cs/mailbox/MailSender.java index 5720c51dfe0..1701cfecff8 100644 --- a/store/src/java/com/zimbra/cs/mailbox/MailSender.java +++ b/store/src/java/com/zimbra/cs/mailbox/MailSender.java @@ -805,7 +805,11 @@ private Object getTargetMailbox(OperationContext octxt, Account authuser, boolea options.setTargetAccount(targetUser.getId()); options.setTargetAccountBy(AccountBy.id); } - return ZMailbox.getMailbox(options); + ZMailbox zmbx = ZMailbox.getMailbox(options); + if (zmbx != null) { + zmbx.setName(targetUser.getName()); + } + return zmbx; } } catch (Exception e) { ZimbraLog.smtp.info("could not fetch home mailbox for delegated send", e); diff --git a/store/src/java/com/zimbra/cs/mailbox/Mailbox.java b/store/src/java/com/zimbra/cs/mailbox/Mailbox.java index 70fa5548916..5275bef2337 100644 --- a/store/src/java/com/zimbra/cs/mailbox/Mailbox.java +++ b/store/src/java/com/zimbra/cs/mailbox/Mailbox.java @@ -236,6 +236,7 @@ import com.zimbra.cs.service.FeedManager; import com.zimbra.cs.service.mail.CopyActionResult; import com.zimbra.cs.service.mail.ItemActionHelper; +import com.zimbra.cs.service.mail.SendDeliveryReport; import com.zimbra.cs.service.util.ItemData; import com.zimbra.cs.service.util.ItemId; import com.zimbra.cs.service.util.SpamHandler; @@ -1877,7 +1878,7 @@ public String getConfig(OperationContext octxt, Pattern pattern) throws ServiceE SortedSet clientIds= new TreeSet(mData.configKeys); for (String key : clientIds) { if (pattern.matcher(key).matches()) { - + previousDeviceId = key; if (previousDeviceId.indexOf(":") != -1) { int index = previousDeviceId.indexOf(":"); @@ -1887,7 +1888,7 @@ public String getConfig(OperationContext octxt, Pattern pattern) throws ServiceE if (!tmp.contains("build")) { break; } - + } } } @@ -5898,6 +5899,7 @@ private void processICalReplies(OperationContext octxt, ZVCalendar cal, String s options.setUri(uri); options.setNoSession(true); ZMailbox zmbox = ZMailbox.getMailbox(options); + zmbox.setAccountId(orgAccount.getId()); zmbox.iCalReply(ical, sender); } catch (IOException e) { throw ServiceException.FAILURE("Error while posting REPLY to organizer mailbox host", e); @@ -5993,7 +5995,6 @@ private Message addMessage(OperationContext octxt, ParsedMessage pm, int folderI String[] tags, int conversationId, String rcptEmail, Message.DraftInfo dinfo, CustomMetadata customData, DeliveryContext dctxt) throws IOException, ServiceException { - // and then actually add the message long start = ZimbraPerf.STOPWATCH_MBOX_ADD_MSG.start(); @@ -6056,11 +6057,25 @@ private Message addMessage(OperationContext octxt, ParsedMessage pm, int folderI StagedBlob staged = sm.stage(blob, this); + Account account = this.getAccount(); + boolean localMsgMarkedRead = false; + if (account.getPrefMailForwardingAddress() != null && account.isFeatureMailForwardingEnabled() + && account.isFeatureMarkMailForwardedAsRead()) { + ZimbraLog.mailbox.debug("Marking forwarded message as read."); + flags = flags & ~Flag.BITMASK_UNREAD; + localMsgMarkedRead = true; + } + + lock.lock(); try { try { - return addMessageInternal(octxt, pm, folderId, noICal, flags, tags, conversationId, + Message message = addMessageInternal(octxt, pm, folderId, noICal, flags, tags, conversationId, rcptEmail, dinfo, customData, dctxt, staged); + if (localMsgMarkedRead && account.getPrefMailSendReadReceipts().isAlways()) { + SendDeliveryReport.sendReport(account, message, true, null, null); + } + return message; } finally { if (deleteIncoming) { sm.quietDelete(dctxt.getIncomingBlob()); @@ -8746,6 +8761,7 @@ public Mountpoint refreshMountpoint(OperationContext octxt, int mountpointId) th zoptions.setTargetAccount(shareOwner.getId()); zoptions.setTargetAccountBy(Key.AccountBy.id); ZMailbox zmbx = ZMailbox.getMailbox(zoptions); + zmbx.setName(shareOwner.getName()); /* need this when logging in using another user's auth */ ZFolder zfolder = zmbx.getFolderByUuid(shloc.getUuid()); if (zfolder != null) { diff --git a/store/src/java/com/zimbra/cs/mailbox/Message.java b/store/src/java/com/zimbra/cs/mailbox/Message.java index b007445e5b0..4445a3dd44d 100644 --- a/store/src/java/com/zimbra/cs/mailbox/Message.java +++ b/store/src/java/com/zimbra/cs/mailbox/Message.java @@ -596,8 +596,10 @@ static Message createInternal(int id, Folder folder, Conversation conv, ParsedMe data.unreadCount = unread ? 1 : 0; data.contentChanged(mbox); - ZimbraLog.mailop.info("Adding Message: id=%d, Message-ID=%s, parentId=%d, folderId=%d, folderName=%s.", - data.id, pm.getMessageID(), data.parentId, folder.getId(), folder.getName()); + ZimbraLog.mailop.debug( + "Adding Message: id=%d, Message-ID=%s, parentId=%d, folderId=%d, folderName=%s acct=%s.", + data.id, pm.getMessageID(), data.parentId, folder.getId(), folder.getName(), + mbox.getAccountId()); new DbMailItem(mbox) .setSender(pm.getParsedSender().getSortString()) .setRecipients(ParsedAddress.getSortString(pm.getParsedRecipients())) diff --git a/store/src/java/com/zimbra/cs/mailbox/calendar/cache/CtagInfoCache.java b/store/src/java/com/zimbra/cs/mailbox/calendar/cache/CtagInfoCache.java index be214bce56e..5ded0853002 100644 --- a/store/src/java/com/zimbra/cs/mailbox/calendar/cache/CtagInfoCache.java +++ b/store/src/java/com/zimbra/cs/mailbox/calendar/cache/CtagInfoCache.java @@ -56,7 +56,7 @@ public class CtagInfoCache { - private MemcachedMap mMemcachedLookup; + private final MemcachedMap mMemcachedLookup; CtagInfoCache() { ZimbraMemcachedClient memcachedClient = MemcachedConnector.getClient(); diff --git a/store/src/java/com/zimbra/cs/mailclient/imap/CAtom.java b/store/src/java/com/zimbra/cs/mailclient/imap/CAtom.java index 9c1f9a38083..d6ea609d0df 100644 --- a/store/src/java/com/zimbra/cs/mailclient/imap/CAtom.java +++ b/store/src/java/com/zimbra/cs/mailclient/imap/CAtom.java @@ -43,6 +43,7 @@ UNSUBSCRIBE, APPEND, CATENATE, URL, F_ANSWERED("\\Answered"), F_NOSELECT("\\Noselect"), F_MARKED("\\Marked"), F_UNMARKED("\\Unmarked"), F_STAR("\\*"), UNKNOWN(""), /* zimbra-specific commands */ + ZIMBRA_ADD_ACCOUNT_LOGGER("X-ZIMBRA-ADD-ACCOUNT-LOGGER"), ZIMBRA_FLUSHCACHE("X-ZIMBRA-FLUSHCACHE"), ZIMBRA_RELOADLC("X-ZIMBRA-RELOADLC"); private final Atom atom; diff --git a/store/src/java/com/zimbra/cs/mailclient/imap/ImapConnection.java b/store/src/java/com/zimbra/cs/mailclient/imap/ImapConnection.java index af4d21fedac..7fc97a55f98 100644 --- a/store/src/java/com/zimbra/cs/mailclient/imap/ImapConnection.java +++ b/store/src/java/com/zimbra/cs/mailclient/imap/ImapConnection.java @@ -743,6 +743,11 @@ public String toString() { config.getHost(), config.getPort(), config.getSecurity(), state, mailbox == null ? "null" : mailbox.getName()); } + public void addAccountLogger(Account account, String category, String level) throws IOException { + ImapRequest req = newRequest(CAtom.ZIMBRA_ADD_ACCOUNT_LOGGER, account.getName(), category, level); + req.sendCheckStatus(); + } + public void flushCache(String cacheTypes) throws IOException { Object[] typeParams = new Object[] { cacheTypes.split(",") }; ImapRequest req = newRequest(CAtom.ZIMBRA_FLUSHCACHE, typeParams); diff --git a/store/src/java/com/zimbra/cs/mime/Mime.java b/store/src/java/com/zimbra/cs/mime/Mime.java index f6edf2b41da..ff799bc9596 100644 --- a/store/src/java/com/zimbra/cs/mime/Mime.java +++ b/store/src/java/com/zimbra/cs/mime/Mime.java @@ -1,7 +1,7 @@ /* * ***** BEGIN LICENSE BLOCK ***** * Zimbra Collaboration Suite Server - * Copyright (C) 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2016 Synacor, Inc. + * Copyright (C) 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2016, 2017 Synacor, Inc. * * This program is free software: you can redistribute it and/or modify it under * the terms of the GNU General Public License as published by the Free Software Foundation, @@ -668,6 +668,13 @@ public static Reader getContentAsReader(MimePart textPart, String defaultCharset public static void recursiveRepairTransferEncoding(MimeMessage mm) throws MessagingException, IOException { for (MPartInfo mpi : listParts(mm, null)) { + String cte = mpi.mPart.getHeader("Content-Transfer-Encoding", null); + String ct = getContentType(mpi.mPart); + if (StringUtil.isNullOrEmpty(cte) && + !ct.equals(MimeConstants.CT_MESSAGE_RFC822) && + !ct.startsWith(MimeConstants.CT_MULTIPART_PREFIX)) { + mpi.mPart.addHeader("Content-Transfer-Encoding", "8bit"); + } repairTransferEncoding(mpi.mPart); } } diff --git a/store/src/java/com/zimbra/cs/service/ExternalUserProvServlet.java b/store/src/java/com/zimbra/cs/service/ExternalUserProvServlet.java index a86668e87b2..f33c11529ee 100644 --- a/store/src/java/com/zimbra/cs/service/ExternalUserProvServlet.java +++ b/store/src/java/com/zimbra/cs/service/ExternalUserProvServlet.java @@ -38,8 +38,10 @@ import com.zimbra.client.ZMailbox; import com.zimbra.client.ZMountpoint; import com.zimbra.common.account.ProvisioningConstants; +import com.zimbra.common.account.ZAttrProvisioning.FeatureAddressVerificationStatus; import com.zimbra.common.localconfig.DebugConfig; import com.zimbra.common.service.ServiceException; +import com.zimbra.common.soap.AccountConstants; import com.zimbra.common.util.BlobMetaData; import com.zimbra.common.util.L10nUtil; import com.zimbra.common.util.Log; @@ -78,9 +80,12 @@ public class ExternalUserProvServlet extends ZimbraServlet { private static final String EXT_USER_PROV_ON_UI_NODE = "/fromservice/extuserprov"; private static final String PUBLIC_LOGIN_ON_UI_NODE = "/fromservice/publiclogin"; public static final String PUBLIC_EXTUSERPROV_JSP = "/public/extuserprov.jsp"; + public static final String PUBLIC_ADDRESS_VERIFICATION_JSP = "/public/addressVerification.jsp"; public static final String PUBLIC_LOGIN_JSP = "/public/login.jsp"; public static final String ERROR_CODE = "errorCode"; + public static final String MESSAGE_KEY = "messageKey"; public static final String ERROR_MESSAGE = "errorMessage"; + public static final String EXPIRED = "expired"; @Override public void init() throws ServletException { @@ -104,123 +109,156 @@ protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws Se } Map tokenMap = validatePrelimToken(param); Map reqHeaders = new HashMap(); - String ownerId = (String) tokenMap.get("aid"); - String folderId = (String) tokenMap.get("fid"); - String extUserEmail = (String) tokenMap.get("email"); - - Provisioning prov = Provisioning.getInstance(); - Account grantee; - try { - Account owner = prov.getAccountById(ownerId); - Domain domain = prov.getDomain(owner); - grantee = prov.getAccountByName(mapExtEmailToAcctName(extUserEmail, domain)); - if (grantee == null) { - // external virtual account not created yet - if (prov.isOctopus() && DebugConfig.skipVirtualAccountRegistrationPage) { - // provision using 'null' password and display name - // UI will ask the user to set these post provisioning - provisionVirtualAccountAndRedirect(req, resp, null, null, ownerId, extUserEmail); + String ownerId = (String) tokenMap.get(AccountConstants.P_ACCOUNT_ID); + String folderId = (String) tokenMap.get(AccountConstants.P_FOLDER_ID); + String extUserEmail = (String) tokenMap.get(AccountConstants.P_EMAIL); + String addressVerification = (String) tokenMap.get(AccountConstants.P_ADDRESS_VERIFICATION); + if ("1".equals(addressVerification)) { + Boolean expired = false; + if (tokenMap.get(EXPIRED) != null) { + expired = (Boolean) tokenMap.get(EXPIRED); + } + Map attributes = handleAddressVerification(req, resp, ownerId, extUserEmail, expired); + redirectRequest(req, resp, attributes, EXT_USER_PROV_ON_UI_NODE, PUBLIC_ADDRESS_VERIFICATION_JSP); + } else { + Provisioning prov = Provisioning.getInstance(); + Account grantee; + try { + Account owner = prov.getAccountById(ownerId); + Domain domain = prov.getDomain(owner); + grantee = prov.getAccountByName(mapExtEmailToAcctName(extUserEmail, domain)); + if (grantee == null) { + // external virtual account not created yet + if (prov.isOctopus() && DebugConfig.skipVirtualAccountRegistrationPage) { + // provision using 'null' password and display name + // UI will ask the user to set these post provisioning + provisionVirtualAccountAndRedirect(req, resp, null, null, ownerId, extUserEmail); + } else { + resp.addCookie(new Cookie("ZM_PRELIM_AUTH_TOKEN", param)); + Map attrs = new HashMap(); + attrs.put("extuseremail", extUserEmail); + reqHeaders.put("ZM_PRELIM_AUTH_TOKEN", param); + redirectRequest(req, resp, attrs, reqHeaders, EXT_USER_PROV_ON_UI_NODE, PUBLIC_EXTUSERPROV_JSP); + } } else { - resp.addCookie(new Cookie("ZM_PRELIM_AUTH_TOKEN", param)); - Map attrs = new HashMap(); - attrs.put("extuseremail", extUserEmail); - reqHeaders.put("ZM_PRELIM_AUTH_TOKEN", param); - redirectRequest(req, resp, attrs, reqHeaders, EXT_USER_PROV_ON_UI_NODE, PUBLIC_EXTUSERPROV_JSP); - } - } else { - // create a new mountpoint in the external user's mailbox if not already created - - String[] sharedItems = owner.getSharedItem(); - int sharedFolderId = Integer.valueOf(folderId); - String sharedFolderPath = null; - MailItem.Type sharedFolderView = null; - for (String sharedItem : sharedItems) { - ShareInfoData sid = AclPushSerializer.deserialize(sharedItem); - if (sid.getItemId() == sharedFolderId && extUserEmail.equalsIgnoreCase(sid.getGranteeId())) { - sharedFolderPath = sid.getPath(); - sharedFolderView = sid.getFolderDefaultViewCode(); - break; + // create a new mountpoint in the external user's mailbox if not already created + + String[] sharedItems = owner.getSharedItem(); + int sharedFolderId = Integer.valueOf(folderId); + String sharedFolderPath = null; + MailItem.Type sharedFolderView = null; + for (String sharedItem : sharedItems) { + ShareInfoData sid = AclPushSerializer.deserialize(sharedItem); + if (sid.getItemId() == sharedFolderId && extUserEmail.equalsIgnoreCase(sid.getGranteeId())) { + sharedFolderPath = sid.getPath(); + sharedFolderView = sid.getFolderDefaultViewCode(); + break; + } } - } - if (sharedFolderPath == null) { - throw new ServletException("share not found"); - } - String mountpointName = getMountpointName(owner, grantee, sharedFolderPath); - - ZMailbox.Options options = new ZMailbox.Options(); - options.setNoSession(true); - options.setAuthToken(AuthProvider.getAuthToken(grantee).toZAuthToken()); - options.setUri(AccountUtil.getSoapUri(grantee)); - ZMailbox zMailbox = new ZMailbox(options); - ZMountpoint zMtpt = null; - try { - zMtpt = zMailbox.createMountpoint( - String.valueOf(getMptParentFolderId(sharedFolderView, prov)), mountpointName, - ZFolder.View.fromString(sharedFolderView.toString()), ZFolder.Color.DEFAULTCOLOR, null, - ZMailbox.OwnerBy.BY_ID, ownerId, ZMailbox.SharedItemBy.BY_ID, folderId, false); - } catch (ServiceException e) { - logger.debug("Error in attempting to create mountpoint. Probably it already exists.", e); - } - if (zMtpt != null) { - if (sharedFolderView == MailItem.Type.APPOINTMENT) { - // make sure that the mountpoint is checked in the UI by default - FolderActionSelector actionSelector = new FolderActionSelector(zMtpt.getId(), "check"); - FolderActionRequest actionRequest = new FolderActionRequest(actionSelector); - try { - zMailbox.invokeJaxb(actionRequest); - } catch (ServiceException e) { - logger.warn("Error in invoking check action on calendar mountpoint", e); + if (sharedFolderPath == null) { + throw new ServletException("share not found"); + } + String mountpointName = getMountpointName(owner, grantee, sharedFolderPath); + + ZMailbox.Options options = new ZMailbox.Options(); + options.setNoSession(true); + options.setAuthToken(AuthProvider.getAuthToken(grantee).toZAuthToken()); + options.setUri(AccountUtil.getSoapUri(grantee)); + ZMailbox zMailbox = new ZMailbox(options); + ZMountpoint zMtpt = null; + try { + zMtpt = zMailbox.createMountpoint( + String.valueOf(getMptParentFolderId(sharedFolderView, prov)), mountpointName, + ZFolder.View.fromString(sharedFolderView.toString()), ZFolder.Color.DEFAULTCOLOR, null, + ZMailbox.OwnerBy.BY_ID, ownerId, ZMailbox.SharedItemBy.BY_ID, folderId, false); + } catch (ServiceException e) { + logger.debug("Error in attempting to create mountpoint. Probably it already exists.", e); + } + if (zMtpt != null) { + if (sharedFolderView == MailItem.Type.APPOINTMENT) { + // make sure that the mountpoint is checked in the UI by default + FolderActionSelector actionSelector = new FolderActionSelector(zMtpt.getId(), "check"); + FolderActionRequest actionRequest = new FolderActionRequest(actionSelector); + try { + zMailbox.invokeJaxb(actionRequest); + } catch (ServiceException e) { + logger.warn("Error in invoking check action on calendar mountpoint", e); + } } + HashSet types = new HashSet(); + types.add(sharedFolderView); + enableAppFeatures(grantee, types); } - HashSet types = new HashSet(); - types.add(sharedFolderView); - enableAppFeatures(grantee, types); - } - // check if the external user is already logged-in - String zAuthTokenCookie = null; - javax.servlet.http.Cookie cookies[] = req.getCookies(); - if (cookies != null) { - for (Cookie cookie : cookies) { - if (cookie.getName().equals("ZM_AUTH_TOKEN")) { - zAuthTokenCookie = cookie.getValue(); - break; + // check if the external user is already logged-in + String zAuthTokenCookie = null; + javax.servlet.http.Cookie cookies[] = req.getCookies(); + if (cookies != null) { + for (Cookie cookie : cookies) { + if (cookie.getName().equals("ZM_AUTH_TOKEN")) { + zAuthTokenCookie = cookie.getValue(); + break; + } } } - } - AuthToken zAuthToken = null; - if (zAuthTokenCookie != null) { - try { - zAuthToken = AuthProvider.getAuthToken(zAuthTokenCookie); - } catch (AuthTokenException ignored) { - // auth token is not valid + AuthToken zAuthToken = null; + if (zAuthTokenCookie != null) { + try { + zAuthToken = AuthProvider.getAuthToken(zAuthTokenCookie); + } catch (AuthTokenException ignored) { + // auth token is not valid + } + } + if (zAuthToken != null && !zAuthToken.isExpired() && zAuthToken.isRegistered() && grantee.getId().equals(zAuthToken.getAccountId())) { + // external virtual account already logged-in + resp.sendRedirect("/"); + } else if (prov.isOctopus() && !grantee.isVirtualAccountInitialPasswordSet() && + DebugConfig.skipVirtualAccountRegistrationPage) { + // seems like the virtual user did not set his password during his last visit, after an account was + // provisioned for him + setCookieAndRedirect(req, resp, grantee); + } else { + Map attrs = new HashMap(); + attrs.put("virtualacctdomain", domain.getName()); + redirectRequest(req, resp, attrs, PUBLIC_LOGIN_ON_UI_NODE, PUBLIC_LOGIN_JSP); } } - if (zAuthToken != null && !zAuthToken.isExpired() && zAuthToken.isRegistered() && grantee.getId().equals(zAuthToken.getAccountId())) { - // external virtual account already logged-in - resp.sendRedirect("/"); - } else if (prov.isOctopus() && !grantee.isVirtualAccountInitialPasswordSet() && - DebugConfig.skipVirtualAccountRegistrationPage) { - // seems like the virtual user did not set his password during his last visit, after an account was - // provisioned for him - setCookieAndRedirect(req, resp, grantee); - } else { - Map attrs = new HashMap(); - attrs.put("virtualacctdomain", domain.getName()); - redirectRequest(req, resp, attrs, PUBLIC_LOGIN_ON_UI_NODE, PUBLIC_LOGIN_JSP); - } + } catch (ServiceException e) { + Map errorAttrs = new HashMap(); + errorAttrs.put(ERROR_CODE, e.getCode()); + errorAttrs.put(ERROR_MESSAGE, e.getMessage()); + redirectRequest(req, resp, errorAttrs, EXT_USER_PROV_ON_UI_NODE, PUBLIC_EXTUSERPROV_JSP); + } catch (Exception e) { + Map errorAttrs = new HashMap(); + errorAttrs.put(ERROR_CODE, ServiceException.FAILURE); + errorAttrs.put(ERROR_MESSAGE, e.getMessage()); + redirectRequest(req, resp, errorAttrs, EXT_USER_PROV_ON_UI_NODE, PUBLIC_EXTUSERPROV_JSP); } + } + } + + public Map handleAddressVerification(HttpServletRequest req, HttpServletResponse resp, String accountId, String emailVerified, Boolean expired) throws ServletException, IOException { + Map attrs = new HashMap(); + HashMap prefs = new HashMap(); + Provisioning prov = Provisioning.getInstance(); + try { + Account acct = prov.getAccountById(accountId); + if (expired) { + prefs.put(Provisioning.A_zimbraFeatureAddressVerificationStatus, FeatureAddressVerificationStatus.expired.toString()); + attrs.put(MESSAGE_KEY, "Expired"); + } else { + prefs.put(Provisioning.A_zimbraPrefMailForwardingAddress, emailVerified); + prefs.put(Provisioning.A_zimbraFeatureAddressVerificationStatus, FeatureAddressVerificationStatus.verified.toString()); + acct.unsetFeatureAddressUnderVerification(); + attrs.put(MESSAGE_KEY, "Success"); + } + prov.modifyAttrs(acct, prefs, true, null); } catch (ServiceException e) { Map errorAttrs = new HashMap(); - errorAttrs.put(ERROR_CODE, e.getCode()); - errorAttrs.put(ERROR_MESSAGE, e.getMessage()); - redirectRequest(req, resp, errorAttrs, EXT_USER_PROV_ON_UI_NODE, PUBLIC_EXTUSERPROV_JSP); - } catch (Exception e) { - Map errorAttrs = new HashMap(); - errorAttrs.put(ERROR_CODE, ServiceException.FAILURE); - errorAttrs.put(ERROR_MESSAGE, e.getMessage()); - redirectRequest(req, resp, errorAttrs, EXT_USER_PROV_ON_UI_NODE, PUBLIC_EXTUSERPROV_JSP); + errorAttrs.put(MESSAGE_KEY, "Failure"); + redirectRequest(req, resp, errorAttrs, EXT_USER_PROV_ON_UI_NODE, PUBLIC_ADDRESS_VERIFICATION_JSP); } + return attrs; } private static String getMountpointName(Account owner, Account grantee, String sharedFolderPath) @@ -501,11 +539,16 @@ public static Map validatePrelimToken(String param) throws Servl } catch (Exception e) { throw new ServletException(e); } - Object expiry = map.get("exp"); + Object expiry = map.get(AccountConstants.P_LINK_EXPIRY); if (expiry != null) { // check validity if (System.currentTimeMillis() > Long.parseLong((String) expiry)) { - throw new ServletException("url no longer valid"); + String addressVerification = (String) map.get(AccountConstants.P_ADDRESS_VERIFICATION); + if ("1".equals(addressVerification)) { + map.put(EXPIRED, true); + } else { + throw new ServletException("url no longer valid"); + } } } return map; diff --git a/store/src/java/com/zimbra/cs/service/account/ModifyPrefs.java b/store/src/java/com/zimbra/cs/service/account/ModifyPrefs.java index 5d161ab913e..86a565d76ba 100644 --- a/store/src/java/com/zimbra/cs/service/account/ModifyPrefs.java +++ b/store/src/java/com/zimbra/cs/service/account/ModifyPrefs.java @@ -20,21 +20,42 @@ */ package com.zimbra.cs.service.account; +import java.text.DateFormat; +import java.text.SimpleDateFormat; +import java.util.Date; import java.util.HashMap; import java.util.HashSet; +import java.util.Locale; import java.util.Map; import java.util.Set; +import java.util.TimeZone; + +import javax.mail.MessagingException; +import javax.mail.internet.MimeMessage; +import javax.mail.internet.MimeMultipart; + +import org.apache.commons.codec.binary.Hex; import com.google.common.base.Strings; +import com.zimbra.common.account.ZAttrProvisioning.FeatureAddressVerificationStatus; +import com.zimbra.common.mime.MimeConstants; import com.zimbra.common.service.ServiceException; import com.zimbra.common.soap.AccountConstants; import com.zimbra.common.soap.Element; import com.zimbra.common.soap.Element.KeyValuePair; +import com.zimbra.common.util.BlobMetaData; +import com.zimbra.common.util.L10nUtil; import com.zimbra.common.util.StringUtil; +import com.zimbra.common.util.ZimbraLog; +import com.zimbra.common.util.L10nUtil.MsgKey; import com.zimbra.cs.account.Account; import com.zimbra.cs.account.AttributeInfo; import com.zimbra.cs.account.AttributeManager; import com.zimbra.cs.account.Provisioning; +import com.zimbra.cs.mailbox.Mailbox; +import com.zimbra.cs.mailbox.MailboxManager; +import com.zimbra.cs.mailbox.OperationContext; +import com.zimbra.cs.util.AccountUtil; import com.zimbra.soap.ZimbraSoapContext; /** @@ -48,6 +69,8 @@ public Element handle(Element request, Map context) throws Servi ZimbraSoapContext zsc = getZimbraSoapContext(context); Account account = getRequestedAccount(zsc); + OperationContext octxt = getOperationContext(zsc, context); + Mailbox mbox = MailboxManager.getInstance().getMailboxByAccount(account, false); if (!canModifyOptions(zsc, account)) throw ServiceException.PERM_DENIED("can not modify options"); @@ -83,8 +106,33 @@ public Element handle(Element request, Map context) throws Servi } if (prefs.containsKey(Provisioning.A_zimbraPrefMailForwardingAddress)) { - if (!account.getBooleanAttr(Provisioning.A_zimbraFeatureMailForwardingEnabled, false)) + if (!account.getBooleanAttr(Provisioning.A_zimbraFeatureMailForwardingEnabled, false)) { throw ServiceException.PERM_DENIED("forwarding not enabled"); + } else { + if (account.getBooleanAttr( + Provisioning.A_zimbraFeatureAddressVerificationEnabled, false)) { + /* + * forwarding address verification enabled, store the email + * ID in 'zimbraFeatureAddressUnderVerification' + * till the time it's verified + */ + String emailIdToVerify = (String) prefs + .get(Provisioning.A_zimbraPrefMailForwardingAddress); + if (!Strings.isNullOrEmpty(emailIdToVerify)) { + prefs.remove(Provisioning.A_zimbraPrefMailForwardingAddress); + prefs.put(Provisioning.A_zimbraFeatureAddressUnderVerification, + emailIdToVerify); + Account authAccount = getAuthenticatedAccount(zsc); + sendEmailVerificationLink(authAccount, account, emailIdToVerify, octxt, + mbox); + prefs.put(Provisioning.A_zimbraFeatureAddressVerificationStatus, + FeatureAddressVerificationStatus.pending.toString()); + } else { + account.unsetFeatureAddressUnderVerification(); + account.unsetFeatureAddressVerificationStatus(); + } + } + } } // call modifyAttrs and pass true to checkImmutable @@ -93,4 +141,58 @@ public Element handle(Element request, Map context) throws Servi Element response = zsc.createElement(AccountConstants.MODIFY_PREFS_RESPONSE); return response; } + + public static void sendEmailVerificationLink(Account authAccount, Account ownerAccount, + String emailIdToVerify, OperationContext octxt, Mailbox mbox) throws ServiceException { + Locale locale = authAccount.getLocale(); + String ownerAcctDisplayName = ownerAccount.getDisplayName(); + if (ownerAcctDisplayName == null) { + ownerAcctDisplayName = ownerAccount.getName(); + } + String subject = L10nUtil.getMessage(MsgKey.verifyEmailSubject, locale, + ownerAcctDisplayName); + String charset = authAccount.getAttr(Provisioning.A_zimbraPrefMailDefaultCharset, + MimeConstants.P_CHARSET_UTF8); + try { + long expiry = ownerAccount.getFeatureAddressVerificationExpiry(); + Date now = new Date(); + long expiryTime = now.getTime() + expiry; + DateFormat format = new SimpleDateFormat("EEE, d MMM yyyy HH:mm:ss z"); + format.setTimeZone(TimeZone.getTimeZone("GMT")); + String gmtDate = format.format(expiryTime); + String url = generateAddressVerificationURL(ownerAccount, expiryTime, emailIdToVerify); + if (ZimbraLog.account.isDebugEnabled()) { + ZimbraLog.account.debug( + "Expiry of Forwarding address verification link sent to %s is %s", + emailIdToVerify, gmtDate); + ZimbraLog.account.debug("Forwarding address verification URL sent to %s is %s", + emailIdToVerify, url); + } + String mimePartText = L10nUtil.getMessage(MsgKey.verifyEmailBodyText, locale, + ownerAcctDisplayName, url, gmtDate); + String mimePartHtml = L10nUtil.getMessage(MsgKey.verifyEmailBodyHtml, locale, + ownerAcctDisplayName, url, gmtDate); + MimeMultipart mmp = AccountUtil.generateMimeMultipart(mimePartText, mimePartHtml, null); + MimeMessage mm = AccountUtil.generateMimeMessage(authAccount, ownerAccount, subject, + charset, null, null, emailIdToVerify, mmp); + mbox.getMailSender().sendMimeMessage(octxt, mbox, false, mm, null, null, null, null, + false); + } catch (MessagingException e) { + ZimbraLog.account + .warn("Failed to send verification link to email ID: '" + emailIdToVerify + "'", e); + throw ServiceException + .FAILURE("Failed to send verification link to email ID: " + emailIdToVerify, e); + } + } + + private static String generateAddressVerificationURL(Account account, long expiry, + String externalUserEmail) throws ServiceException { + StringBuilder encodedBuff = new StringBuilder(); + BlobMetaData.encodeMetaData(AccountConstants.P_ACCOUNT_ID, account.getId(), encodedBuff); + BlobMetaData.encodeMetaData(AccountConstants.P_LINK_EXPIRY, expiry, encodedBuff); + BlobMetaData.encodeMetaData(AccountConstants.P_EMAIL, externalUserEmail, encodedBuff); + BlobMetaData.encodeMetaData(AccountConstants.P_ADDRESS_VERIFICATION, true, encodedBuff); + String data = new String(Hex.encodeHex(encodedBuff.toString().getBytes())); + return AccountUtil.generateExtUserProvURL(account, data); + } } diff --git a/store/src/java/com/zimbra/cs/service/admin/AddAccountLogger.java b/store/src/java/com/zimbra/cs/service/admin/AddAccountLogger.java index a68655927d1..752ca58b077 100644 --- a/store/src/java/com/zimbra/cs/service/admin/AddAccountLogger.java +++ b/store/src/java/com/zimbra/cs/service/admin/AddAccountLogger.java @@ -16,11 +16,13 @@ */ package com.zimbra.cs.service.admin; +import java.io.IOException; import java.util.Arrays; import java.util.Collection; import java.util.List; import java.util.Map; +import com.zimbra.common.localconfig.LC; import com.zimbra.common.service.ServiceException; import com.zimbra.common.soap.AdminConstants; import com.zimbra.common.soap.Element; @@ -32,81 +34,128 @@ import com.zimbra.cs.account.Account; import com.zimbra.cs.account.AccountServiceException; import com.zimbra.cs.account.Provisioning; -import com.zimbra.common.account.Key.AccountBy; import com.zimbra.cs.account.Server; +import com.zimbra.cs.service.AuthProvider; +import com.zimbra.common.account.Key.AccountBy; import com.zimbra.cs.account.accesscontrol.AdminRight; import com.zimbra.cs.account.accesscontrol.Rights.Admin; +import com.zimbra.cs.mailclient.imap.ImapConnection; import com.zimbra.soap.ZimbraSoapContext; /** * Adds a custom logger for the given account. - * + * * @author bburtin */ public class AddAccountLogger extends AdminDocumentHandler { static String CATEGORY_ALL = "all"; - + @Override public Element handle(Element request, Map context) throws ServiceException { ZimbraSoapContext zsc = getZimbraSoapContext(context); - + Server localServer = Provisioning.getInstance().getLocalServer(); checkRight(zsc, context, localServer, Admin.R_manageAccountLogger); - + // Look up account Account account = getAccountFromLoggerRequest(request); - + Element eLogger = request.getElement(AdminConstants.E_LOGGER); String category = eLogger.getAttribute(AdminConstants.A_CATEGORY); String sLevel = eLogger.getAttribute(AdminConstants.A_LEVEL); - + // Handle level. Level level = null; try { level = Level.valueOf(sLevel.toLowerCase()); } catch (IllegalArgumentException e) { String error = String.format("Invalid level: %s. Valid values are %s.", - sLevel, StringUtil.join(",", Level.values())); + sLevel, StringUtil.join(",", Level.values())); throw ServiceException.INVALID_REQUEST(error, null); } - + + if (!category.equalsIgnoreCase(CATEGORY_ALL) && !LogFactory.logExists(category)) { + throw ServiceException.INVALID_REQUEST("Log category " + category + " does not exist.", null); + } + + Collection loggers = addAccountLogger(account, category, level); + + addAccountLoggerOnImapServers(account, category, sLevel); + + // Build response. + Element response = zsc.createElement(AdminConstants.ADD_ACCOUNT_LOGGER_RESPONSE); + for (Log log : loggers) { + response.addElement(AdminConstants.E_LOGGER) + .addAttribute(AdminConstants.A_CATEGORY, log.getCategory()) + .addAttribute(AdminConstants.A_LEVEL, level.name()); + } + + return response; + } + + public static Collection addAccountLogger(Account account, String category, Level level) { // Handle category. Collection loggers; if (category.equalsIgnoreCase(CATEGORY_ALL)) { loggers = LogFactory.getAllLoggers(); } else { - if (!LogFactory.logExists(category)) { - throw ServiceException.INVALID_REQUEST("Log category " + category + " does not exist.", null); - } loggers = Arrays.asList(LogFactory.getLog(category)); } - // Add custom loggers. - Element response = zsc.createElement(AdminConstants.ADD_ACCOUNT_LOGGER_RESPONSE); for (Log log : loggers) { ZimbraLog.misc.info("Adding custom logger: account=%s, category=%s, level=%s", - account.getName(), category, level); + account.getName(), category, level); log.addAccountLogger(account.getName(), level); - response.addElement(AdminConstants.E_LOGGER) - .addAttribute(AdminConstants.A_CATEGORY, log.getCategory()) - .addAttribute(AdminConstants.A_LEVEL, level.name()); + } + + return loggers; + } + + public static void addAccountLoggerOnImapServers(Account account, String category, String level) { + List imapServers; + try { + imapServers = Provisioning.getPreferredIMAPServers(account); + } catch (ServiceException e) { + ZimbraLog.imap.warn("unable to fetch list of imapd servers", e); + return; + } + for (Server server: imapServers) { + addAccountLoggerOnImapServer(server, account, category, level); + } + } + + public static void addAccountLoggerOnImapServer(Server server, Account account, String category, String level) + { + ImapConnection connection = null; + try { + connection = ImapConnection.getZimbraConnection(server, LC.zimbra_ldap_user.value(), AuthProvider.getAdminAuthToken()); + } catch (ServiceException e) { + ZimbraLog.imap.warn("unable to connect to imapd server '%s' to issue X-ZIMBRA-ADD-ACCOUNT-LOGGER request", server.getServiceHostname(), e); + return; + } + try { + ZimbraLog.imap.debug("issuing X-ZIMBRA-ADD-ACCOUNT-LOGGER request to imapd server '%s' for account '%s'", server.getServiceHostname(), account.getName()); + connection.addAccountLogger(account, category, level); + } catch (IOException e) + { + ZimbraLog.imap.warn("failed to enable account level logging for account '%s' on server '%s'", account.getName(), server.getServiceHostname(), e); + } finally { + connection.close(); } - - return response; } - + /** * Returns the Account object based on the <id> or <account> - * element owned by the given request element. + * element owned by the given request element. */ static Account getAccountFromLoggerRequest(Element request) throws ServiceException { Account account = null; Provisioning prov = Provisioning.getInstance(); Element idElement = request.getOptionalElement(AdminConstants.E_ID); - + if (idElement != null) { // Handle deprecated element. ZimbraLog.soap.info("The <%s> element is deprecated for <%s>. Use <%s> instead.", @@ -127,7 +176,7 @@ static Account getAccountFromLoggerRequest(Element request) } return account; } - + @Override public void docRights(List relatedRights, List notes) { relatedRights.add(Admin.R_manageAccountLogger); diff --git a/store/src/java/com/zimbra/cs/service/admin/FlushCache.java b/store/src/java/com/zimbra/cs/service/admin/FlushCache.java index 14356e20ecd..14d41202b12 100644 --- a/store/src/java/com/zimbra/cs/service/admin/FlushCache.java +++ b/store/src/java/com/zimbra/cs/service/admin/FlushCache.java @@ -226,7 +226,6 @@ public static void sendFlushRequest(Map context, String appContex private static void flushCacheOnAllServers(ZimbraSoapContext zsc, FlushCacheRequest req) throws ServiceException { req.getCache().setAllServers(false); // make sure we don't go round in loops - Element request = zsc.jaxbToElement(req); Provisioning prov = Provisioning.getInstance(); String localServerId = prov.getLocalServer().getId(); @@ -235,7 +234,7 @@ private static void flushCacheOnAllServers(ZimbraSoapContext zsc, FlushCacheRequ if (localServerId.equals(server.getId())) { continue; } - + Element request = zsc.jaxbToElement(req); ZimbraLog.misc.debug("Flushing cache on server: %s", server.getName()); String adminUrl = URLUtil.getAdminURL(server, AdminConstants.ADMIN_SERVICE_URI); SoapHttpTransport mTransport = new SoapHttpTransport(adminUrl); diff --git a/store/src/java/com/zimbra/cs/service/admin/GetAdminExtensionZimlets.java b/store/src/java/com/zimbra/cs/service/admin/GetAdminExtensionZimlets.java index a6241bc91b3..3775ecba9ba 100644 --- a/store/src/java/com/zimbra/cs/service/admin/GetAdminExtensionZimlets.java +++ b/store/src/java/com/zimbra/cs/service/admin/GetAdminExtensionZimlets.java @@ -52,15 +52,18 @@ public Element handle(Element request, Map context) throws Servi } private void doExtensionZimlets(ZimbraSoapContext zsc, Map context, Element response) throws ServiceException { - boolean isNGEnabled = true; - boolean isMobileNGEnabled = true; - boolean isNetworkAdminEnabled = true; + + boolean mobileNGEnabled = true; + boolean networkAdminEnabled = true; + boolean networkNGEnabled = true; + try { - isNGEnabled = Provisioning.getInstance().getLocalServer().isNetworkModulesNGEnabled(); - isMobileNGEnabled = Provisioning.getInstance().getLocalServer().isNetworkMobileNGEnabled(); - isNetworkAdminEnabled = Provisioning.getInstance().getLocalServer().isNetworkAdminEnabled(); + networkNGEnabled = Provisioning.getInstance().getLocalServer().isNetworkModulesNGEnabled(); + mobileNGEnabled = Provisioning.getInstance().getLocalServer().isNetworkMobileNGEnabled(); + networkAdminEnabled = Provisioning.getInstance().getLocalServer().isNetworkAdminNGEnabled(); + } catch (ServiceException e) { - ZimbraLog.mailbox.warn("Exception while getting zimbraNetworkModulesNGEnabled.", e); + ZimbraLog.mailbox.warn("Exception while getting zimbraNetworkModulesNG related attributes.", e); } Iterator zimlets = Provisioning.getInstance().listAllZimlets().iterator(); while (zimlets.hasNext()) { @@ -72,25 +75,40 @@ private void doExtensionZimlets(ZimbraSoapContext zsc, Map conte if (z.isExtension()) { boolean include = true; - if ("com_zimbra_hsm".equals(z.getName()) || "com_zimbra_backuprestore".equals(z.getName()) - || "com_zimbra_delegatedadmin".equals(z.getName())) { - include = !isNGEnabled; + if ("com_zimbra_mobilesync".equals(z.getName()) && mobileNGEnabled && networkNGEnabled) { + include = !mobileNGEnabled; + if (!include) { + ZimbraLog.mailbox.info("Disabled '%s' as zimbraNetworkMobileNGEnabled is true.", z.getName()); + } + } + + if ("com_zimbra_hsm".equals(z.getName()) && networkNGEnabled) { + include = !networkNGEnabled; if (!include) { - ZimbraLog.mailbox.info("Disabled '%s' zimbraNetworkModulesNGEnabled is true.", z.getName()); + ZimbraLog.mailbox.info("Disabled '%s' as zimbraNetworNGEnabled is true.", z.getName()); } } - if ("com_zimbra_mobilesync".equals(z.getName()) && isNGEnabled) { - include = !isMobileNGEnabled; + + if ("com_zimbra_backuprestore".equals(z.getName()) && networkNGEnabled) { + include = !networkNGEnabled; if (!include) { - ZimbraLog.mailbox.info("Disabled '%s' zimbraNetworkMobileNGEnabled is true.", z.getName()); + ZimbraLog.mailbox.info("Disabled '%s' as zimbraNetworkNGEnabled is true.", z.getName()); } } - if ("com_zimbra_delegatedadmin".equals(z.getName()) && isNetworkAdminEnabled) + if ("com_zimbra_delegatedadmin".equals(z.getName()) && networkAdminEnabled && networkNGEnabled) { + include = !networkAdminEnabled; + if (!include) { + ZimbraLog.mailbox.info("Disabled '%s' as zimbraNetworkAdminNGEnabled is true.", z.getName()); + } include = include && (AccessManager.getInstance() instanceof ACLAccessManager); - if (include) - ZimletUtil.listZimlet(response, z, -1, Presence.enabled); // admin zimlets are all enabled - } - } + } + + if (include) { + ZimletUtil.listZimlet(response, z, -1, Presence.enabled); + // admin zimlets are all enabled + } + } + } } @Override diff --git a/store/src/java/com/zimbra/cs/service/formatter/ArchiveFormatter.java b/store/src/java/com/zimbra/cs/service/formatter/ArchiveFormatter.java index 7fdd151b6ed..7f9e97065a2 100644 --- a/store/src/java/com/zimbra/cs/service/formatter/ArchiveFormatter.java +++ b/store/src/java/com/zimbra/cs/service/formatter/ArchiveFormatter.java @@ -26,6 +26,7 @@ import java.nio.charset.Charset; import java.nio.charset.CharsetEncoder; import java.text.DateFormat; +import java.text.Normalizer; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Arrays; @@ -635,6 +636,9 @@ private ArchiveOutputStream saveItem(UserServletContext context, MailItem mi, throw MailServiceException.NO_SUCH_PART(part); } name = Mime.getFilename(mp); + if (!Normalizer.isNormalized(name, Normalizer.Form.NFC)) { + name = Normalizer.normalize(name, Normalizer.Form.NFC); + } ext = null; sz = mp.getSize(); if (sz == -1) { @@ -957,6 +961,8 @@ public void saveCallback(UserServletContext context, String contentType, Folder } try { id = new ItemData(readArchiveEntry(ais, aie)); + } catch (IOException e) { + throw ServiceException.FAILURE("Error reading file", e); } catch (Exception e) { addError(errs, FormatterServiceException.INVALID_FORMAT(aie.getName())); } @@ -1058,7 +1064,7 @@ static String[] getTagNames(ItemData id) { return ItemData.getTagNames(id.tags); } - private static byte[] readArchiveEntry(ArchiveInputStream ais, ArchiveInputEntry aie) + public static byte[] readArchiveEntry(ArchiveInputStream ais, ArchiveInputEntry aie) throws IOException { if (aie == null) { return null; diff --git a/store/src/java/com/zimbra/cs/service/mail/CreateDataSource.java b/store/src/java/com/zimbra/cs/service/mail/CreateDataSource.java index 3ba059e20a4..20503ad92b4 100644 --- a/store/src/java/com/zimbra/cs/service/mail/CreateDataSource.java +++ b/store/src/java/com/zimbra/cs/service/mail/CreateDataSource.java @@ -87,10 +87,10 @@ public Element handle(Element request, Map context) LdapUtil.getLdapBooleanString(eDataSource.getAttributeBool(MailConstants.A_DS_IS_ENABLED))); dsAttrs.put(Provisioning.A_zimbraDataSourceImportOnly, LdapUtil.getLdapBooleanString(eDataSource.getAttributeBool(MailConstants.A_DS_IS_IMPORTONLY, false))); - dsAttrs.put(Provisioning.A_zimbraDataSourceHost, eDataSource.getAttribute(MailConstants.A_DS_HOST)); - dsAttrs.put(Provisioning.A_zimbraDataSourcePort, eDataSource.getAttribute(MailConstants.A_DS_PORT)); - dsAttrs.put(Provisioning.A_zimbraDataSourceConnectionType, eDataSource.getAttribute(MailConstants.A_DS_CONNECTION_TYPE)); - dsAttrs.put(Provisioning.A_zimbraDataSourceUsername, eDataSource.getAttribute(MailConstants.A_DS_USERNAME)); + dsAttrs.put(Provisioning.A_zimbraDataSourceHost, eDataSource.getAttribute(MailConstants.A_DS_HOST, null)); + dsAttrs.put(Provisioning.A_zimbraDataSourcePort, eDataSource.getAttribute(MailConstants.A_DS_PORT, null)); + dsAttrs.put(Provisioning.A_zimbraDataSourceConnectionType, eDataSource.getAttribute(MailConstants.A_DS_CONNECTION_TYPE, prov.getConfig().getDataSourceConnectionTypeAsString())); + dsAttrs.put(Provisioning.A_zimbraDataSourceUsername, eDataSource.getAttribute(MailConstants.A_DS_USERNAME, null)); String value = eDataSource.getAttribute(MailConstants.A_DS_PASSWORD, null); if (value != null) { dsAttrs.put(Provisioning.A_zimbraDataSourcePassword, value); diff --git a/store/src/java/com/zimbra/cs/service/mail/GetContactBackupList.java b/store/src/java/com/zimbra/cs/service/mail/GetContactBackupList.java new file mode 100644 index 00000000000..91e123d39db --- /dev/null +++ b/store/src/java/com/zimbra/cs/service/mail/GetContactBackupList.java @@ -0,0 +1,52 @@ +/* + * ***** BEGIN LICENSE BLOCK ***** + * Zimbra Collaboration Suite Server + * Copyright (C) 2017 Synacor, Inc. + * + * This program is free software: you can redistribute it and/or modify it under + * the terms of the GNU General Public License as published by the Free Software Foundation, + * version 2 of the License. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * You should have received a copy of the GNU General Public License along with this program. + * If not, see . + * ***** END LICENSE BLOCK ***** + */ + +package com.zimbra.cs.service.mail; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import com.zimbra.common.service.ServiceException; +import com.zimbra.common.soap.Element; +import com.zimbra.cs.account.Account; +import com.zimbra.soap.ZimbraSoapContext; +import com.zimbra.soap.mail.message.GetContactBackupListResponse; + +public final class GetContactBackupList extends MailDocumentHandler { + + @Override + public Element handle(Element request, Map context) throws ServiceException { + ZimbraSoapContext zsc = getZimbraSoapContext(context); + Account account = getRequestedAccount(zsc); + + List backup = getContactBackupList(account); + + GetContactBackupListResponse res = new GetContactBackupListResponse(backup); + return zsc.jaxbToElement(res); + } + + // TODO : Update this method along with contact backup functionality + private List getContactBackupList(Account account) { + List list = new ArrayList(); + list.add("file1.tgz"); + list.add("file2.tgz"); + list.add("file3.tgz"); + list.add("file4.tgz"); + return list; + } +} diff --git a/store/src/java/com/zimbra/cs/service/mail/GetMiniCal.java b/store/src/java/com/zimbra/cs/service/mail/GetMiniCal.java index 5e2c1fa7ff8..b5755e76ac6 100644 --- a/store/src/java/com/zimbra/cs/service/mail/GetMiniCal.java +++ b/store/src/java/com/zimbra/cs/service/mail/GetMiniCal.java @@ -27,6 +27,11 @@ import java.util.Set; import java.util.TreeSet; +import com.zimbra.client.ZMailbox; +import com.zimbra.client.ZMailbox.ZGetMiniCalResult; +import com.zimbra.client.ZMailbox.ZMiniCalError; +import com.zimbra.common.account.Key; +import com.zimbra.common.account.Key.AccountBy; import com.zimbra.common.calendar.ICalTimeZone; import com.zimbra.common.calendar.WellKnownTimeZones; import com.zimbra.common.localconfig.LC; @@ -39,8 +44,6 @@ import com.zimbra.cs.account.AuthToken; import com.zimbra.cs.account.Provisioning; import com.zimbra.cs.account.Server; -import com.zimbra.common.account.Key; -import com.zimbra.common.account.Key.AccountBy; import com.zimbra.cs.mailbox.Folder; import com.zimbra.cs.mailbox.MailItem; import com.zimbra.cs.mailbox.MailServiceException; @@ -51,17 +54,14 @@ import com.zimbra.cs.mailbox.calendar.IcalXmlStrMap; import com.zimbra.cs.mailbox.calendar.Util; import com.zimbra.cs.mailbox.calendar.cache.CalSummaryCache; +import com.zimbra.cs.mailbox.calendar.cache.CalSummaryCache.CalendarDataResult; import com.zimbra.cs.mailbox.calendar.cache.CalendarCacheManager; import com.zimbra.cs.mailbox.calendar.cache.CalendarData; import com.zimbra.cs.mailbox.calendar.cache.CalendarItemData; import com.zimbra.cs.mailbox.calendar.cache.InstanceData; -import com.zimbra.cs.mailbox.calendar.cache.CalSummaryCache.CalendarDataResult; import com.zimbra.cs.service.util.ItemId; import com.zimbra.cs.service.util.ItemIdFormatter; import com.zimbra.cs.util.AccountUtil; -import com.zimbra.client.ZMailbox; -import com.zimbra.client.ZMailbox.ZGetMiniCalResult; -import com.zimbra.client.ZMailbox.ZMiniCalError; import com.zimbra.soap.ZimbraSoapContext; /* @@ -233,7 +233,7 @@ private static void addBusyDates(Calendar cal, CalendarData calData, long rangeS if (partStat == null) partStat = item.getDefaultData().getPartStat(); if (IcalXmlStrMap.PARTSTAT_DECLINED.equals(partStat)) - continue; + continue; Long start = inst.getDtStart(); if (start != null) { String datestampStart = getDatestamp(cal, start); @@ -272,6 +272,7 @@ private static void doRemoteFolders( zoptions.setTargetAccountBy(AccountBy.id); zoptions.setNoSession(true); ZMailbox zmbx = ZMailbox.getMailbox(zoptions); + zmbx.setName(target.getName()); /* need this when logging in using another user's auth */ String remoteIds[] = new String[remoteFolders.size()]; for (int i=0; i < remoteIds.length; i++) remoteIds[i] = remoteFolders.get(i).toString(); ZGetMiniCalResult result = zmbx.getMiniCal(rangeStart, rangeEnd, remoteIds); diff --git a/store/src/java/com/zimbra/cs/service/mail/ItemAction.java b/store/src/java/com/zimbra/cs/service/mail/ItemAction.java index 46f94174866..b7a20fc60a5 100644 --- a/store/src/java/com/zimbra/cs/service/mail/ItemAction.java +++ b/store/src/java/com/zimbra/cs/service/mail/ItemAction.java @@ -503,6 +503,7 @@ private Account forceRemoteSession(ZimbraSoapContext zsc, Map co zoptions.setTargetAccount(owner.getId()); zoptions.setTargetAccountBy(Key.AccountBy.id); ZMailbox zmbx = ZMailbox.getMailbox(zoptions); + zmbx.setName(owner.getName()); /* need this when logging in using another user's auth */ ZFolder zfolder = zmbx.getFolderById(iidFolder.toString(zsc.getAuthtokenAccountId())); if (!(zfolder instanceof ZMountpoint)) break; diff --git a/store/src/java/com/zimbra/cs/service/mail/ItemActionHelper.java b/store/src/java/com/zimbra/cs/service/mail/ItemActionHelper.java index a77692aed83..5e27683e39d 100644 --- a/store/src/java/com/zimbra/cs/service/mail/ItemActionHelper.java +++ b/store/src/java/com/zimbra/cs/service/mail/ItemActionHelper.java @@ -616,6 +616,7 @@ private ItemActionResult executeRemote() throws ServiceException, IOException { zoptions.setTargetAccount(target.getId()); zoptions.setTargetAccountBy(Key.AccountBy.id); ZMailbox zmbx = ZMailbox.getMailbox(zoptions); + zmbx.setName(target.getName()); /* need this when logging in using another user's auth */ // check for mountpoints before going any further... ZFolder zfolder = zmbx.getFolderById(mIidFolder.toString(mAuthenticatedAccount)); diff --git a/store/src/java/com/zimbra/cs/service/mail/MailService.java b/store/src/java/com/zimbra/cs/service/mail/MailService.java index 51eee417fb8..192961a21ad 100644 --- a/store/src/java/com/zimbra/cs/service/mail/MailService.java +++ b/store/src/java/com/zimbra/cs/service/mail/MailService.java @@ -97,6 +97,7 @@ public void registerHandlers(DocumentDispatcher dispatcher) { dispatcher.registerHandler(MailConstants.CONTACT_ACTION_REQUEST, new ContactAction()); dispatcher.registerHandler(MailConstants.EXPORT_CONTACTS_REQUEST, new ExportContacts()); dispatcher.registerHandler(MailConstants.IMPORT_CONTACTS_REQUEST, new ImportContacts()); + dispatcher.registerHandler(MailConstants.GET_CONTACT_BACKUP_LIST_REQUEST, new GetContactBackupList()); // notes if (LC.notes_enabled.booleanValue()) { @@ -228,5 +229,8 @@ public void registerHandlers(DocumentDispatcher dispatcher) { dispatcher.registerHandler(MailConstants.GET_LAST_ITEM_ID_IN_MAILBOX_REQUEST, new GetLastItemIdInMailbox()); dispatcher.registerHandler(MailConstants.GET_MODIFIED_ITEMS_IDS_REQUEST, new GetModifiedItemsIDs()); dispatcher.registerHandler(MailConstants.RESET_RECENT_MESSAGE_COUNT_REQUEST, new ResetRecentMessageCount()); + + // Contacts API + dispatcher.registerHandler(MailConstants.RESTORE_CONTACTS_REQUEST, new RestoreContacts()); } } diff --git a/store/src/java/com/zimbra/cs/service/mail/RestoreContacts.java b/store/src/java/com/zimbra/cs/service/mail/RestoreContacts.java new file mode 100644 index 00000000000..9a5c6723ffb --- /dev/null +++ b/store/src/java/com/zimbra/cs/service/mail/RestoreContacts.java @@ -0,0 +1,152 @@ +/* + * ***** BEGIN LICENSE BLOCK ***** + * Zimbra Collaboration Suite Server + * Copyright (C) 2017 Synacor, Inc. + * + * This program is free software: you can redistribute it and/or modify it under + * the terms of the GNU General Public License as published by the Free Software Foundation, + * version 2 of the License. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * You should have received a copy of the GNU General Public License along with this program. + * If not, see . + * ***** END LICENSE BLOCK ***** + */ +package com.zimbra.cs.service.mail; + +import java.io.File; +import java.io.IOException; +import java.util.List; +import java.util.Map; + +import javax.servlet.http.HttpServletRequest; + +import org.apache.http.HttpResponse; +import org.apache.http.client.CookieStore; +import org.apache.http.client.HttpClient; +import org.apache.http.client.methods.HttpPost; +import org.apache.http.entity.mime.MultipartEntity; +import org.apache.http.entity.mime.content.FileBody; +import org.apache.http.impl.client.BasicCookieStore; +import org.apache.http.impl.client.HttpClientBuilder; +import org.apache.http.impl.cookie.BasicClientCookie; +import org.eclipse.jetty.http.HttpStatus; + +import com.zimbra.common.mime.MimeConstants; +import com.zimbra.common.service.ServiceException; +import com.zimbra.common.soap.Element; +import com.zimbra.common.soap.MailConstants; +import com.zimbra.common.util.ZimbraCookie; +import com.zimbra.common.util.ZimbraLog; +import com.zimbra.cs.account.AuthToken; +import com.zimbra.cs.account.AuthTokenException; +import com.zimbra.cs.mailbox.Document; +import com.zimbra.cs.mailbox.Folder; +import com.zimbra.cs.mailbox.MailItem; +import com.zimbra.cs.mailbox.Mailbox; +import com.zimbra.cs.mailbox.OperationContext; +import com.zimbra.cs.service.UserServlet; +import com.zimbra.cs.store.file.FileBlobStore; +import com.zimbra.soap.SoapServlet; +import com.zimbra.soap.ZimbraSoapContext; +import com.zimbra.soap.mail.message.RestoreContactsRequest; +import com.zimbra.soap.mail.message.RestoreContactsRequest.Resolve; +import com.zimbra.soap.mail.message.RestoreContactsResponse; + +public class RestoreContacts extends MailDocumentHandler { + + @Override + public Element handle(Element request, Map context) throws ServiceException { + ZimbraSoapContext zsc = getZimbraSoapContext(context); + Mailbox mbox = getRequestedMailbox(zsc); + OperationContext octxt = getOperationContext(zsc, context); + RestoreContactsRequest req = zsc.elementToJaxb(request); + String contactBackupFileName = req.getContactsBackupFileName(); + Resolve resolve = req.getResolve(); + // if folder does not exist, SoapFault is thrown by getFolderByName() itself. + Folder folder = mbox.getFolderByName(octxt, Mailbox.ID_FOLDER_BRIEFCASE, + MailConstants.A_CONTACTS_BACKUP_FOLDER_NAME); + ZimbraLog.contact.debug("Backup folder exists. Searching for %s.", contactBackupFileName); + List itemList = mbox.getItemList(octxt, MailItem.Type.DOCUMENT, folder.getId()); + RestoreContactsResponse response = new RestoreContactsResponse(); + boolean mailItemFound = false; + HttpResponse httpResponse = null; + for (MailItem item : itemList) { + if (item instanceof Document) { + Document doc = (Document) item; + if (doc.getName().equals(contactBackupFileName)) { + mailItemFound = true; + Object servReq = context.get(SoapServlet.SERVLET_REQUEST); + String realm = "https://"; + HttpServletRequest httpRequest = null; + if (servReq instanceof HttpServletRequest) { + httpRequest = (HttpServletRequest) servReq; + realm = httpRequest.isSecure() ? "https://" : "http://"; + } + if(resolve == null) { + resolve = Resolve.reset; + } + String url = realm + getLocalHost() + "/service/home/" + + mbox.getAccount().getName() + "/?" + UserServlet.QP_FMT + "=tgz&" + + UserServlet.QP_TYPES + "=contact&" + MimeConstants.P_CHARSET + "=UTF-8"; + CookieStore cookieStore = new BasicCookieStore(); + if (resolve != Resolve.ignore) { + url = url + "&" + MailConstants.A_CONTACTS_RESTORE_RESOLVE + "=" + resolve; + } + AuthToken authToken = octxt.getAuthToken(); + BasicClientCookie cookie = null; + try { + cookie = new BasicClientCookie(ZimbraCookie.COOKIE_ZM_AUTH_TOKEN, authToken.getEncoded()); + cookie.setPath("/"); + cookie.setDomain(mbox.getAccount().getDomainName()); + cookieStore.addCookie(cookie); + } catch (AuthTokenException e) { + throw ServiceException.FAILURE("Failed to get authentication token", e); + } + File file = new File(FileBlobStore.getBlobPath(mbox, doc.getId(), + doc.getSavedSequence(), Short.valueOf(doc.getLocator()))); + if (!file.exists()) { + throw ServiceException + .INVALID_REQUEST("File does not exist: " + contactBackupFileName, null); + } + ZimbraLog.contact.debug("Backup file found. Restoring contacts in %s.", + contactBackupFileName); + httpResponse = httpPostBackup(file, url, cookieStore); + break; + } + } + } + if (!mailItemFound) { + throw ServiceException.INVALID_REQUEST("No such file: " + contactBackupFileName, null); + } + + if (httpResponse.getStatusLine().getStatusCode() == HttpStatus.OK_200) { + ZimbraLog.contact.debug("Restore operation for %s completed successfully", + contactBackupFileName); + } else { + ZimbraLog.contact.info("Restore operation for %s failed. Http respose status: %s", + contactBackupFileName, httpResponse.getStatusLine()); + throw ServiceException + .FAILURE("Failed to restore contacts backup " + contactBackupFileName, null); + } + return zsc.jaxbToElement(response); + } + + public static HttpResponse httpPostBackup(File file, String url, CookieStore cookieStore) throws ServiceException { + HttpResponse httpResponse = null; + HttpClient http = HttpClientBuilder.create().setDefaultCookieStore(cookieStore) + .build(); + HttpPost post = new HttpPost(url); + MultipartEntity multipart = new MultipartEntity(); + multipart.addPart("file", new FileBody(file)); + post.setEntity(multipart); + try { + httpResponse = http.execute(post); + } catch (IOException e) { + throw ServiceException.FAILURE("Failed to execute contact restore request", null); + } + return httpResponse; + } +} diff --git a/store/src/java/com/zimbra/cs/service/mail/Search.java b/store/src/java/com/zimbra/cs/service/mail/Search.java index afe950ff200..80f7efae634 100644 --- a/store/src/java/com/zimbra/cs/service/mail/Search.java +++ b/store/src/java/com/zimbra/cs/service/mail/Search.java @@ -451,6 +451,7 @@ private static void searchRemoteAccountCalendars( zoptions.setTargetAccountBy(AccountBy.id); zoptions.setNoSession(true); ZMailbox zmbx = ZMailbox.getMailbox(zoptions); + zmbx.setName(target.getName()); /* need this when logging in using another user's auth */ Element resp = zmbx.invoke(req); for (Element hit : resp.listElements()) { diff --git a/store/src/java/com/zimbra/cs/service/mail/SendInviteReply.java b/store/src/java/com/zimbra/cs/service/mail/SendInviteReply.java index 5a9c1b2c49c..41c08cc9092 100644 --- a/store/src/java/com/zimbra/cs/service/mail/SendInviteReply.java +++ b/store/src/java/com/zimbra/cs/service/mail/SendInviteReply.java @@ -476,7 +476,11 @@ private static ZMailbox getRemoteZMailbox(OperationContext octxt, Account authAc zoptions.setNoSession(true); zoptions.setTargetAccount(targetAcct.getId()); zoptions.setTargetAccountBy(Key.AccountBy.id); - return ZMailbox.getMailbox(zoptions); + ZMailbox zmbx = ZMailbox.getMailbox(zoptions); + if (zmbx != null) { + zmbx.setName(targetAcct.getName()); /* need this when logging in using another user's auth */ + } + return zmbx; } private static AddInviteResult sendAddInvite(ZMailbox zmbx, OperationContext octxt, Message msg) diff --git a/store/src/java/com/zimbra/cs/service/mail/SendShareNotification.java b/store/src/java/com/zimbra/cs/service/mail/SendShareNotification.java index e8e580c5654..7dd520fc29b 100644 --- a/store/src/java/com/zimbra/cs/service/mail/SendShareNotification.java +++ b/store/src/java/com/zimbra/cs/service/mail/SendShareNotification.java @@ -16,17 +16,13 @@ */ package com.zimbra.cs.service.mail; -import java.io.ByteArrayOutputStream; -import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; -import java.util.Date; import java.util.Locale; import java.util.Map; import javax.mail.MessagingException; -import javax.mail.internet.AddressException; import javax.mail.internet.MimeMessage; import javax.mail.internet.MimeMultipart; @@ -36,11 +32,9 @@ import com.zimbra.common.account.Key.DistributionListBy; import com.zimbra.common.localconfig.DebugConfig; import com.zimbra.common.mime.MimeConstants; -import com.zimbra.common.mime.shim.JavaMailInternetAddress; import com.zimbra.common.service.ServiceException; import com.zimbra.common.soap.Element; import com.zimbra.common.soap.MailConstants; -import com.zimbra.common.util.CharsetUtil; import com.zimbra.common.util.L10nUtil; import com.zimbra.common.util.L10nUtil.MsgKey; import com.zimbra.common.util.Log; @@ -63,13 +57,10 @@ import com.zimbra.cs.mailbox.MailboxManager; import com.zimbra.cs.mailbox.Mountpoint; import com.zimbra.cs.mailbox.OperationContext; -import com.zimbra.cs.mime.Mime; import com.zimbra.cs.service.UserServlet; import com.zimbra.cs.service.util.ItemId; import com.zimbra.cs.util.AccountUtil; -import com.zimbra.cs.util.JMSession; import com.zimbra.cs.util.Zimbra; -import com.zimbra.soap.JaxbUtil; import com.zimbra.soap.ZimbraSoapContext; import com.zimbra.soap.mail.message.SendShareNotificationRequest; import com.zimbra.soap.mail.message.SendShareNotificationRequest.Action; @@ -550,31 +541,28 @@ private Folder getFolder(OperationContext octxt, Account authAccount, Mailbox mb } protected MimeMessage generateShareNotification(Account authAccount, Account ownerAccount, - ShareInfoData sid, String notes, Action action, - Collection internlaRecipients, String externalRecipient) - throws ServiceException, MessagingException { + ShareInfoData sid, String notes, Action action, Collection internalRecipients, + String externalRecipient) throws ServiceException, MessagingException { Locale locale = authAccount.getLocale(); - String charset = authAccount.getAttr( - Provisioning.A_zimbraPrefMailDefaultCharset, MimeConstants.P_CHARSET_UTF8); - - MimeMessage mm = new Mime.FixedMimeMessage(JMSession.getSmtpSession(authAccount)); + String charset = authAccount.getAttr(Provisioning.A_zimbraPrefMailDefaultCharset, + MimeConstants.P_CHARSET_UTF8); MsgKey subjectKey; if (action == null) { subjectKey = MsgKey.shareNotifSubject; } else { switch (action) { - case edit: - subjectKey = MsgKey.shareModifySubject; - break; - case revoke: - subjectKey = MsgKey.shareRevokeSubject; - break; - case expire: - subjectKey = MsgKey.shareExpireSubject; - break; - default: - subjectKey = MsgKey.shareNotifSubject; + case edit: + subjectKey = MsgKey.shareModifySubject; + break; + case revoke: + subjectKey = MsgKey.shareRevokeSubject; + break; + case expire: + subjectKey = MsgKey.shareExpireSubject; + break; + default: + subjectKey = MsgKey.shareNotifSubject; } } String subject = L10nUtil.getMessage(subjectKey, locale); @@ -582,50 +570,38 @@ protected MimeMessage generateShareNotification(Account authAccount, Account own if (ownerAcctDisplayName == null) { ownerAcctDisplayName = ownerAccount.getName(); } - subject += L10nUtil.getMessage(MsgKey.sharedBySubject, locale, sid.getName(), ownerAcctDisplayName); - mm.setSubject(subject, CharsetUtil.checkCharset(subject, charset)); - mm.setSentDate(new Date()); - - // from the owner - mm.setFrom(AccountUtil.getFriendlyEmailAddress(ownerAccount)); - - // sent by auth account - mm.setSender(AccountUtil.getFriendlyEmailAddress(authAccount)); - - // to the grantee - if (internlaRecipients != null) { - assert(externalRecipient == null); - for (String recipient : internlaRecipients) { - try { - mm.addRecipient(javax.mail.Message.RecipientType.TO, new JavaMailInternetAddress(recipient)); - } catch (AddressException e) { - sLog.warn("Ignoring error while sending share notification to " + recipient, e); - } - } - } else if (externalRecipient != null) { - mm.setRecipient(javax.mail.Message.RecipientType.TO, new JavaMailInternetAddress(externalRecipient)); - } else { - String recipient = sid.getGranteeName(); - mm.setRecipient(javax.mail.Message.RecipientType.TO, new JavaMailInternetAddress(recipient)); + subject += L10nUtil.getMessage(MsgKey.sharedBySubject, locale, sid.getName(), + ownerAcctDisplayName); + String recipient = sid.getGranteeName(); + String extUserShareAcceptUrl = null; + String extUserLoginUrl = null; + String externalGranteeName = null; + if (sid.getGranteeTypeCode() == ACL.GRANTEE_GUEST) { + externalGranteeName = sid.getGranteeName(); + } else if (sid.getGranteeTypeCode() == ACL.GRANTEE_GROUP && externalRecipient != null) { + externalGranteeName = externalRecipient; } - - MimeMultipart mmp = ShareInfo.NotificationSender.genNotifBody(sid, notes, locale, action, externalRecipient); - mm.setContent(mmp); - mm.saveChanges(); - - if (sLog.isDebugEnabled()) { - // log4j.logger.com.zimbra.cs.service.mail=DEBUG - try { - ByteArrayOutputStream buf = new ByteArrayOutputStream(); - mm.writeTo(buf); - String mmDump = new String(buf.toByteArray()); - sLog.debug("********\n" + mmDump); - } catch (MessagingException e) { - sLog.debug("failed log debug share notification message", e); - } catch (IOException e) { - sLog.debug("failed log debug share notification message", e); - } + // this mail will go to external email address + boolean goesToExternalAddr = (externalGranteeName != null); + if (action == null && goesToExternalAddr) { + Account owner = Provisioning.getInstance().getAccountById(sid.getOwnerAcctId()); + extUserShareAcceptUrl = AccountUtil.getShareAcceptURL(owner, sid.getItemId(), externalGranteeName); + extUserLoginUrl = AccountUtil.getExtUserLoginURL(owner); } + String mimePartText = ShareInfo.NotificationSender.getMimePartText(sid, notes, locale, + action, extUserShareAcceptUrl, extUserLoginUrl); + String mimePartHtml = ShareInfo.NotificationSender.getMimePartHtml(sid, notes, locale, + action, extUserShareAcceptUrl, extUserLoginUrl); + + String mimePartXml = null; + if (!goesToExternalAddr) { + mimePartXml = ShareInfo.NotificationSender.genXmlPart(sid, notes, null, action); + } + + MimeMultipart mmp = AccountUtil.generateMimeMultipart(mimePartText, mimePartHtml, + mimePartXml); + MimeMessage mm = AccountUtil.generateMimeMessage(authAccount, ownerAccount, subject, + charset, internalRecipients, externalRecipient, recipient, mmp); return mm; } diff --git a/store/src/java/com/zimbra/cs/service/mail/ToXML.java b/store/src/java/com/zimbra/cs/service/mail/ToXML.java index e4fd476fe5c..7d154db7f4f 100644 --- a/store/src/java/com/zimbra/cs/service/mail/ToXML.java +++ b/store/src/java/com/zimbra/cs/service/mail/ToXML.java @@ -70,6 +70,7 @@ import com.zimbra.common.mime.MimeDetect; import com.zimbra.common.service.ServiceException; import com.zimbra.common.soap.Element; +import com.zimbra.common.soap.Element.ContainerException; import com.zimbra.common.soap.HeaderConstants; import com.zimbra.common.soap.MailConstants; import com.zimbra.common.util.ArrayUtil; @@ -3126,7 +3127,15 @@ public static Element encodeDataSource(Element parent, DataSource ds) { if (ds.getSmtpUsername() != null) { m.addAttribute(MailConstants.A_DS_SMTP_USERNAME, ds.getSmtpUsername()); } - + if(ds.getDataSourceImportClassName() != null) { + m.addAttribute(MailConstants.A_DS_IMPORT_CLASS, ds.getDataSourceImportClassName()); + } + if(ds.getOauthRefreshToken() != null) { + m.addAttribute(MailConstants.A_DS_REFRESH_TOKEN, ds.getOauthRefreshToken()); + } + if(ds.getOauthRefreshTokenUrl() != null) { + m.addAttribute(MailConstants.A_DS_REFRESH_TOKEN_URL, ds.getOauthRefreshTokenUrl()); + } m.addAttribute(MailConstants.A_DS_EMAIL_ADDRESS, ds.getEmailAddress()); m.addAttribute(MailConstants.A_DS_USE_ADDRESS_FOR_FORWARD_REPLY, ds.useAddressForForwardReply()); m.addAttribute(MailConstants.A_DS_DEFAULT_SIGNATURE, ds.getDefaultSignature()); diff --git a/store/src/java/com/zimbra/cs/stats/JmxImapDaemonStats.java b/store/src/java/com/zimbra/cs/stats/JmxImapDaemonStats.java new file mode 100644 index 00000000000..3cf33a79a8b --- /dev/null +++ b/store/src/java/com/zimbra/cs/stats/JmxImapDaemonStats.java @@ -0,0 +1,43 @@ +/* + * ***** BEGIN LICENSE BLOCK ***** + * Zimbra Collaboration Suite Server + * Copyright (C) 2017 Synacor, Inc. + * + * This program is free software: you can redistribute it and/or modify it under + * the terms of the GNU General Public License as published by the Free Software Foundation, + * version 2 of the License. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * You should have received a copy of the GNU General Public License along with this program. + * If not, see . + * ***** END LICENSE BLOCK ***** + */ + +package com.zimbra.cs.stats; + +import com.zimbra.common.stats.DeltaCalculator; + +public class JmxImapDaemonStats implements JmxImapDaemonStatsMBean { + + private final DeltaCalculator imapDeltaCalc = new DeltaCalculator(ZimbraPerf.STOPWATCH_IMAP); + + JmxImapDaemonStats() { + } + + @Override + public long getImapRequests() { + return ZimbraPerf.STOPWATCH_IMAP.getCount(); + } + + @Override + public long getImapResponseMs() { + return (long) imapDeltaCalc.getRealtimeAverage(); + } + + @Override + public void reset() { + imapDeltaCalc.reset(); + } +} \ No newline at end of file diff --git a/store/src/java/com/zimbra/cs/stats/JmxImapDaemonStatsMBean.java b/store/src/java/com/zimbra/cs/stats/JmxImapDaemonStatsMBean.java new file mode 100644 index 00000000000..99116a599a0 --- /dev/null +++ b/store/src/java/com/zimbra/cs/stats/JmxImapDaemonStatsMBean.java @@ -0,0 +1,23 @@ +/* + * ***** BEGIN LICENSE BLOCK ***** + * Zimbra Collaboration Suite Server + * Copyright (C) 2017 Synacor, Inc. + * + * This program is free software: you can redistribute it and/or modify it under + * the terms of the GNU General Public License as published by the Free Software Foundation, + * version 2 of the License. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * You should have received a copy of the GNU General Public License along with this program. + * If not, see . + * ***** END LICENSE BLOCK ***** + */ + +package com.zimbra.cs.stats; + +public interface JmxImapDaemonStatsMBean extends JmxStatsMBeanBase { + long getImapRequests(); + long getImapResponseMs(); +} diff --git a/store/src/java/com/zimbra/cs/stats/JmxServerStatsMBean.java b/store/src/java/com/zimbra/cs/stats/JmxServerStatsMBean.java index c81097815eb..033832538cc 100644 --- a/store/src/java/com/zimbra/cs/stats/JmxServerStatsMBean.java +++ b/store/src/java/com/zimbra/cs/stats/JmxServerStatsMBean.java @@ -17,7 +17,7 @@ package com.zimbra.cs.stats; -public interface JmxServerStatsMBean +public interface JmxServerStatsMBean extends JmxStatsMBeanBase { long getBlobInputStreamReads(); long getBlobInputStreamSeekRate(); diff --git a/store/src/java/com/zimbra/cs/stats/JmxStatsMBeanBase.java b/store/src/java/com/zimbra/cs/stats/JmxStatsMBeanBase.java new file mode 100644 index 00000000000..cc42d0fe4d8 --- /dev/null +++ b/store/src/java/com/zimbra/cs/stats/JmxStatsMBeanBase.java @@ -0,0 +1,22 @@ +/* + * ***** BEGIN LICENSE BLOCK ***** + * Zimbra Collaboration Suite Server + * Copyright (C) 2017 Synacor, Inc. + * + * This program is free software: you can redistribute it and/or modify it under + * the terms of the GNU General Public License as published by the Free Software Foundation, + * version 2 of the License. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * You should have received a copy of the GNU General Public License along with this program. + * If not, see . + * ***** END LICENSE BLOCK ***** + */ + +package com.zimbra.cs.stats; + +public interface JmxStatsMBeanBase { + public void reset(); +} \ No newline at end of file diff --git a/store/src/java/com/zimbra/cs/stats/ZimbraPerf.java b/store/src/java/com/zimbra/cs/stats/ZimbraPerf.java index 2f1bd354ac3..b59128bdbc0 100644 --- a/store/src/java/com/zimbra/cs/stats/ZimbraPerf.java +++ b/store/src/java/com/zimbra/cs/stats/ZimbraPerf.java @@ -29,13 +29,13 @@ import java.util.LinkedHashMap; import java.util.List; import java.util.Map; -import java.util.TreeMap; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.atomic.AtomicInteger; import javax.management.MBeanServer; import javax.management.ObjectName; +import com.google.common.collect.Maps; import com.zimbra.common.service.ServiceException; import com.zimbra.common.stats.Accumulator; import com.zimbra.common.stats.Counter; @@ -59,13 +59,7 @@ */ public class ZimbraPerf { - @Target({ElementType.FIELD}) - @Retention(RetentionPolicy.RUNTIME) - private @interface Description { - String value(); - } - - static Log log = LogFactory.getLog(ZimbraPerf.class); + private static Log log = LogFactory.getLog(ZimbraPerf.class); @Description("Number of database connections in use") public static final String RTS_DB_POOL_SIZE = "db_pool_size"; @@ -216,14 +210,10 @@ public class ZimbraPerf { private static int mailboxCacheSize; private static long mailboxCacheSizeTimestamp = 0; private static JmxServerStats jmxServerStats; - private static Map descriptions = new TreeMap(String.CASE_INSENSITIVE_ORDER); + private static JmxImapDaemonStats jmxImapDaemonStats; + private static Map descriptions = Maps.newTreeMap(String.CASE_INSENSITIVE_ORDER); - public static String getDescription(String statName) { - return descriptions.get(statName); - } - - private static RealtimeStats realtimeStats = - new RealtimeStats(new String[] { + private static String[] mboxRealtimeStatsNames = new String[] { RTS_DB_POOL_SIZE, RTS_INNODB_BP_HIT_RATE, RTS_LMTP_CONN, RTS_LMTP_THREADS, RTS_POP_CONN, RTS_POP_THREADS, RTS_POP_SSL_CONN, RTS_POP_SSL_THREADS, @@ -239,9 +229,13 @@ public static String getDescription(String statName) { RTS_UCSERVICE_CACHE_SIZE, RTS_UCSERVICE_CACHE_HIT_RATE, RTS_ZIMLET_CACHE_SIZE, RTS_ZIMLET_CACHE_HIT_RATE, RTS_GROUP_CACHE_SIZE, RTS_GROUP_CACHE_HIT_RATE, - RTS_XMPP_CACHE_SIZE, RTS_XMPP_CACHE_HIT_RATE, - } - ); + RTS_XMPP_CACHE_SIZE, RTS_XMPP_CACHE_HIT_RATE + }; + private static String[] imapdRealtimeStatsNames = new String[] { + RTS_IMAP_CONN, RTS_IMAP_THREADS, RTS_IMAP_SSL_CONN, RTS_IMAP_SSL_THREADS + }; + + private static RealtimeStats realtimeStats = null; @Description("Number of messages received over LMTP") private static final String DC_LMTP_RCVD_MSGS = "lmtp_rcvd_msgs"; @@ -345,37 +339,28 @@ public static String getDescription(String statName) { @Description("Number of calendars (folders) in the calendar summary cache LRU in Java heap") private static final String DC_CALCACHE_LRU_SIZE = "calcache_lru_size"; - private static CopyOnWriteArrayList sAccumulators = - new CopyOnWriteArrayList( - new Accumulator[] { - new DeltaCalculator(COUNTER_LMTP_RCVD_MSGS).setTotalName(DC_LMTP_RCVD_MSGS), - new DeltaCalculator(COUNTER_LMTP_RCVD_BYTES).setTotalName(DC_LMTP_RCVD_BYTES), - new DeltaCalculator(COUNTER_LMTP_RCVD_RCPT).setTotalName(DC_LMTP_RCVD_RCPT), - new DeltaCalculator(COUNTER_LMTP_DLVD_MSGS).setTotalName(DC_LMTP_DLVD_MSGS), - new DeltaCalculator(COUNTER_LMTP_DLVD_BYTES).setTotalName(DC_LMTP_DLVD_BYTES), - new DeltaCalculator(STOPWATCH_DB_CONN).setCountName(DC_DB_CONN_COUNT).setAverageName(DC_DB_CONN_MS_AVG), - new DeltaCalculator(STOPWATCH_LDAP_DC).setCountName(DC_LDAP_DC_COUNT).setAverageName(DC_LDAP_DC_MS_AVG), - new DeltaCalculator(STOPWATCH_MBOX_ADD_MSG).setCountName(DC_MBOX_ADD_MSG_COUNT).setAverageName(DC_MBOX_ADD_MSG_MS_AVG), - new DeltaCalculator(STOPWATCH_MBOX_GET).setCountName(DC_MBOX_GET_COUNT).setAverageName(DC_MBOX_GET_MS_AVG), - new DeltaCalculator(COUNTER_MBOX_CACHE).setAverageName(DC_MBOX_CACHE), - new DeltaCalculator(COUNTER_MBOX_MSG_CACHE).setAverageName(DC_MBOX_MSG_CACHE), - new DeltaCalculator(COUNTER_MBOX_ITEM_CACHE).setAverageName(DC_MBOX_ITEM_CACHE), - new DeltaCalculator(STOPWATCH_SOAP).setCountName(DC_SOAP_COUNT).setAverageName(DC_SOAP_MS_AVG), - new DeltaCalculator(STOPWATCH_IMAP).setCountName(DC_IMAP_COUNT).setAverageName(DC_IMAP_MS_AVG), - new DeltaCalculator(STOPWATCH_POP).setCountName(DC_POP_COUNT).setAverageName(DC_POP_MS_AVG), - new DeltaCalculator(COUNTER_IDX_WRT).setAverageName(DC_IDX_WRT_AVG), - new DeltaCalculator(COUNTER_IDX_WRT_OPENED).setTotalName(DC_IDX_WRT_OPENED), - new DeltaCalculator(COUNTER_IDX_WRT_OPENED_CACHE_HIT).setTotalName(DC_IDX_WRT_OPENED_CACHE_HIT), - new DeltaCalculator(COUNTER_CALENDAR_CACHE_HIT).setAverageName(DC_CALCACHE_HIT), - new DeltaCalculator(COUNTER_CALENDAR_CACHE_MEM_HIT).setAverageName(DC_CALCACHE_MEM_HIT), - new DeltaCalculator(COUNTER_CALENDAR_CACHE_LRU_SIZE).setAverageName(DC_CALCACHE_LRU_SIZE), - new DeltaCalculator(COUNTER_IDX_BYTES_WRITTEN).setTotalName(DC_IDX_BYTES_WRITTEN).setAverageName(DC_IDX_BYTES_WRITTTEN_AVG), - new DeltaCalculator(COUNTER_IDX_BYTES_READ).setTotalName(DC_IDX_BYTES_READ).setAverageName(DC_IDX_BYTES_READ_AVG), - new DeltaCalculator(COUNTER_BLOB_INPUT_STREAM_READ).setTotalName(DC_BIS_READ), - new DeltaCalculator(COUNTER_BLOB_INPUT_STREAM_SEEK_RATE).setAverageName(DC_BIS_SEEK_RATE), - realtimeStats - } - ); + private static CopyOnWriteArrayList sAccumulators = null; + + private static final long CSV_DUMP_FREQUENCY = Constants.MILLIS_PER_MINUTE; + private static boolean sIsInitialized = false; + private static boolean isPrepared = false; + /** + * The number of statements that were prepared, as reported by + * {@link DbPool.DbConnection#prepareStatement}. + */ + private static AtomicInteger sPrepareCount = new AtomicInteger(0); + + public enum ServerID {ZIMBRA, IMAP_DAEMON}; + + @Target({ElementType.FIELD}) + @Retention(RetentionPolicy.RUNTIME) + private @interface Description { + String value(); + } + + public static String getDescription(String statName) { + return descriptions.get(statName); + } private static void initDescriptions() { descriptions = Collections.synchronizedMap(descriptions); @@ -422,8 +407,9 @@ public static Map getStats() { * names will not be output correctly into the logs */ public static void addRealtimeStatName(String name, String description) { - if (sIsInitialized) - throw new IllegalStateException("Cannot add stat name after ZimbraPerf.initialize() is called"); + if (sIsInitialized) { + throw new IllegalStateException("Cannot add stat name after ZimbraPerf has been initialized"); + } ZimbraLog.perf.debug("Adding realtime stat '%s': %s", name, description); realtimeStats.addName(name); descriptions.put(name, description); @@ -433,12 +419,6 @@ public static JmxServerStatsMBean getMonitoringStats() { return jmxServerStats; } - /** - * The number of statements that were prepared, as reported by - * {@link DbPool.DbConnection#prepareStatement}. - */ - private static AtomicInteger sPrepareCount = new AtomicInteger(0); - public static int getPrepareCount() { return sPrepareCount.get(); } @@ -455,61 +435,148 @@ public static void addStatsCallback(RealtimeStatsCallback callback) { realtimeStats.addCallback(callback); } - private static final long CSV_DUMP_FREQUENCY = Constants.MILLIS_PER_MINUTE; - private static boolean sIsInitialized = false; + /** + * MUST be called before anything else + * + * Some things need to be done before initialize is called but require some static variables to + * have already been setup. + * @param serverID + */ + public synchronized static void prepare(ServerID serverID) { + if (isPrepared) { + log.warn("Detected call to ZimbraPerf.prepare() after already prepared", new Exception()); + return; + } + if (sIsInitialized) { + throw new IllegalStateException( + "Must not call ZimbraPerf.prepare() after ZimbraPerf.initialize()"); + } + switch (serverID) { + case ZIMBRA: + realtimeStats = new RealtimeStats(mboxRealtimeStatsNames); + sAccumulators = new CopyOnWriteArrayList( + new Accumulator[] { + new DeltaCalculator(COUNTER_LMTP_RCVD_MSGS).setTotalName(DC_LMTP_RCVD_MSGS), + new DeltaCalculator(COUNTER_LMTP_RCVD_BYTES).setTotalName(DC_LMTP_RCVD_BYTES), + new DeltaCalculator(COUNTER_LMTP_RCVD_RCPT).setTotalName(DC_LMTP_RCVD_RCPT), + new DeltaCalculator(COUNTER_LMTP_DLVD_MSGS).setTotalName(DC_LMTP_DLVD_MSGS), + new DeltaCalculator(COUNTER_LMTP_DLVD_BYTES).setTotalName(DC_LMTP_DLVD_BYTES), + new DeltaCalculator(STOPWATCH_DB_CONN).setCountName(DC_DB_CONN_COUNT) + .setAverageName(DC_DB_CONN_MS_AVG), + new DeltaCalculator(STOPWATCH_LDAP_DC).setCountName(DC_LDAP_DC_COUNT) + .setAverageName(DC_LDAP_DC_MS_AVG), + new DeltaCalculator(STOPWATCH_MBOX_ADD_MSG).setCountName(DC_MBOX_ADD_MSG_COUNT) + .setAverageName(DC_MBOX_ADD_MSG_MS_AVG), + new DeltaCalculator(STOPWATCH_MBOX_GET).setCountName(DC_MBOX_GET_COUNT) + .setAverageName(DC_MBOX_GET_MS_AVG), + new DeltaCalculator(COUNTER_MBOX_CACHE).setAverageName(DC_MBOX_CACHE), + new DeltaCalculator(COUNTER_MBOX_MSG_CACHE).setAverageName(DC_MBOX_MSG_CACHE), + new DeltaCalculator(COUNTER_MBOX_ITEM_CACHE).setAverageName(DC_MBOX_ITEM_CACHE), + new DeltaCalculator(STOPWATCH_SOAP).setCountName(DC_SOAP_COUNT) + .setAverageName(DC_SOAP_MS_AVG), + new DeltaCalculator(STOPWATCH_IMAP).setCountName(DC_IMAP_COUNT) + .setAverageName(DC_IMAP_MS_AVG), + new DeltaCalculator(STOPWATCH_POP).setCountName(DC_POP_COUNT) + .setAverageName(DC_POP_MS_AVG), + new DeltaCalculator(COUNTER_IDX_WRT).setAverageName(DC_IDX_WRT_AVG), + new DeltaCalculator(COUNTER_IDX_WRT_OPENED).setTotalName(DC_IDX_WRT_OPENED), + new DeltaCalculator(COUNTER_IDX_WRT_OPENED_CACHE_HIT) + .setTotalName(DC_IDX_WRT_OPENED_CACHE_HIT), + new DeltaCalculator(COUNTER_CALENDAR_CACHE_HIT).setAverageName(DC_CALCACHE_HIT), + new DeltaCalculator(COUNTER_CALENDAR_CACHE_MEM_HIT) + .setAverageName(DC_CALCACHE_MEM_HIT), + new DeltaCalculator(COUNTER_CALENDAR_CACHE_LRU_SIZE) + .setAverageName(DC_CALCACHE_LRU_SIZE), + new DeltaCalculator(COUNTER_IDX_BYTES_WRITTEN) + .setTotalName(DC_IDX_BYTES_WRITTEN) + .setAverageName(DC_IDX_BYTES_WRITTTEN_AVG), + new DeltaCalculator(COUNTER_IDX_BYTES_READ) + .setTotalName(DC_IDX_BYTES_READ).setAverageName(DC_IDX_BYTES_READ_AVG), + new DeltaCalculator(COUNTER_BLOB_INPUT_STREAM_READ).setTotalName(DC_BIS_READ), + new DeltaCalculator(COUNTER_BLOB_INPUT_STREAM_SEEK_RATE) + .setAverageName(DC_BIS_SEEK_RATE), + realtimeStats + } + ); + break; + case IMAP_DAEMON: + realtimeStats = new RealtimeStats(imapdRealtimeStatsNames); + sAccumulators = new CopyOnWriteArrayList( + new Accumulator[] { + new DeltaCalculator(STOPWATCH_IMAP) + .setCountName(DC_IMAP_COUNT).setAverageName(DC_IMAP_MS_AVG), + realtimeStats + } + ); + break; + default: + } + isPrepared = true; + } - public synchronized static void initialize() { + public synchronized static void initialize(ServerID serverID) { + if (!isPrepared) { + throw new IllegalStateException("Must call prepare() before ZimbraPerf.initialize()"); + } if (sIsInitialized) { log.warn("Detected a second call to ZimbraPerf.initialize()", new Exception()); return; } initDescriptions(); + switch (serverID) { + case ZIMBRA: + initializeForMainZimbraJVM(); + break; + case IMAP_DAEMON: + initializeForImapDaemon(); + break; + default: + } + sIsInitialized = true; + } + private synchronized static void initializeForMainZimbraJVM() { addStatsCallback(new ServerStatsCallback()); addStatsCallback(new JettyStats()); - - StatsDumper.schedule(new MailboxdStats(), CSV_DUMP_FREQUENCY); - + // Initialize JMX + MBeanServer jmxServer = ManagementFactory.getPlatformMBeanServer(); + jmxServerStats = new JmxServerStats(); + try { + jmxServer.registerMBean(jmxServerStats, + new ObjectName("ZimbraCollaborationSuite:type=ServerStats")); + } catch (Exception e) { + ZimbraLog.perf.warn("Unable to register JMX interface.", e); + } + StatsDumper.schedule(new Stats("mailboxd.csv", sAccumulators, jmxServerStats), CSV_DUMP_FREQUENCY); StatsDumper.schedule(SOAP_TRACKER, CSV_DUMP_FREQUENCY); StatsDumper.schedule(IMAP_TRACKER, CSV_DUMP_FREQUENCY); StatsDumper.schedule(POP_TRACKER, CSV_DUMP_FREQUENCY); StatsDumper.schedule(LDAP_TRACKER, CSV_DUMP_FREQUENCY); StatsDumper.schedule(SYNC_TRACKER, CSV_DUMP_FREQUENCY); StatsDumper.schedule(SQL_TRACKER, CSV_DUMP_FREQUENCY); - ThreadStats threadStats = new ThreadStats("threads.csv"); StatsDumper.schedule(threadStats, CSV_DUMP_FREQUENCY); + } + private synchronized static void initializeForImapDaemon() { // Initialize JMX MBeanServer jmxServer = ManagementFactory.getPlatformMBeanServer(); - jmxServerStats = new JmxServerStats(); + jmxImapDaemonStats = new JmxImapDaemonStats(); try { - jmxServer.registerMBean(jmxServerStats, new ObjectName("ZimbraCollaborationSuite:type=ServerStats")); + jmxServer.registerMBean(jmxImapDaemonStats, new ObjectName("ZimbraImapDaemon:type=ServerStats")); } catch (Exception e) { ZimbraLog.perf.warn("Unable to register JMX interface.", e); } - - sIsInitialized = true; - } - - // feature Imap - public synchronized static void initialize(boolean val) { - if (sIsInitialized) { - log.warn("Detected a second call to ZimbraPerf.initialize()", new Exception()); - return; - } - initDescriptions(); - + StatsDumper.schedule(new Stats("imapd_stats.csv", sAccumulators, jmxImapDaemonStats), + CSV_DUMP_FREQUENCY); StatsDumper.schedule(IMAPD_TRACKER, CSV_DUMP_FREQUENCY); - - sIsInitialized = val; } /** * Returns the mailbox cache size. The real value is reread once a minute so that cache * performance is not affected. */ - static int getMailboxCacheSize() { + protected static int getMailboxCacheSize() { long now = System.currentTimeMillis(); if (now - mailboxCacheSizeTimestamp > Constants.MILLIS_PER_MINUTE) { try { @@ -523,21 +590,28 @@ static int getMailboxCacheSize() { } /** - * Scheduled task that writes a row to zimbrastats.csv with the latest - * Accumulator data. + * Scheduled task that writes a row to a CSV file with the latest Accumulator data. */ - private static final class MailboxdStats - implements StatsDumperDataSource - { + private static final class Stats implements StatsDumperDataSource { + private final String filename; + private final List accumulators; + private final JmxStatsMBeanBase statsBean; + + public Stats(String filename, List accumulators, JmxStatsMBeanBase statsBean) { + this.filename = filename; + this.accumulators = accumulators; + this.statsBean = statsBean; + } + @Override public String getFilename() { - return "mailboxd.csv"; + return filename; } @Override public String getHeader() { List columns = new ArrayList(); - for (Accumulator a : sAccumulators) { + for (Accumulator a : accumulators) { for (String column : a.getNames()) { columns.add(column); } @@ -548,7 +622,7 @@ public String getHeader() { @Override public Collection getDataLines() { List data = new ArrayList(); - for (Accumulator a : sAccumulators) { + for (Accumulator a : accumulators) { synchronized (a) { data.addAll(a.getData()); a.reset(); @@ -568,7 +642,7 @@ public Collection getDataLines() { retVal.add(line); // Piggyback off timer to reset realtime stats. - jmxServerStats.reset(); + statsBean.reset(); return retVal; } diff --git a/store/src/java/com/zimbra/cs/util/AccountUtil.java b/store/src/java/com/zimbra/cs/util/AccountUtil.java index edf96225f9c..7dc0ff53918 100644 --- a/store/src/java/com/zimbra/cs/util/AccountUtil.java +++ b/store/src/java/com/zimbra/cs/util/AccountUtil.java @@ -17,15 +17,29 @@ package com.zimbra.cs.util; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.OutputStreamWriter; import java.io.UnsupportedEncodingException; +import java.util.Collection; +import java.util.Date; import java.util.HashSet; import java.util.List; import java.util.Set; +import javax.activation.DataHandler; import javax.mail.Address; import javax.mail.MessagingException; +import javax.mail.internet.AddressException; import javax.mail.internet.InternetAddress; +import javax.mail.internet.MimeBodyPart; import javax.mail.internet.MimeMessage; +import javax.mail.internet.MimeMultipart; + +import org.apache.commons.codec.binary.Hex; import com.zimbra.common.account.Key; import com.zimbra.common.account.Key.DomainBy; @@ -34,23 +48,31 @@ import com.zimbra.common.mime.shim.JavaMailInternetAddress; import com.zimbra.common.service.ServiceException; import com.zimbra.common.soap.AccountConstants; +import com.zimbra.common.util.BlobMetaData; +import com.zimbra.common.util.CharsetUtil; import com.zimbra.common.util.EmailUtil; import com.zimbra.common.util.StringUtil; import com.zimbra.common.util.SystemUtil; import com.zimbra.common.util.ZimbraLog; +import com.zimbra.common.zmime.ZMimeBodyPart; +import com.zimbra.common.zmime.ZMimeMultipart; import com.zimbra.cs.account.AccessManager; import com.zimbra.cs.account.Account; import com.zimbra.cs.account.AuthToken; import com.zimbra.cs.account.DataSource; import com.zimbra.cs.account.Domain; +import com.zimbra.cs.account.ExtAuthTokenKey; import com.zimbra.cs.account.NamedEntry; import com.zimbra.cs.account.Provisioning; import com.zimbra.cs.account.Server; +import com.zimbra.cs.account.TokenUtil; import com.zimbra.cs.mailbox.MailItem; import com.zimbra.cs.mailbox.MailServiceException; import com.zimbra.cs.mailbox.Mailbox; import com.zimbra.cs.mailbox.Metadata; import com.zimbra.cs.mailbox.MetadataList; +import com.zimbra.cs.mime.Mime; +import com.zimbra.cs.servlet.ZimbraServlet; import com.zimbra.soap.admin.type.DataSourceType; public class AccountUtil { @@ -636,4 +658,177 @@ public static Set parseConfig(Metadata config) throws ServiceException { } return subscriptions; } + + public static MimeMessage generateMimeMessage(Account authAccount, Account ownerAccount, + String subject, String charset, Collection internalRecipients, + String externalRecipient, String recipient, MimeMultipart mmp) throws MessagingException { + MimeMessage mm = new Mime.FixedMimeMessage(JMSession.getSmtpSession(authAccount)); + mm.setSubject(subject, CharsetUtil.checkCharset(subject, charset)); + mm.setSentDate(new Date()); + // from the owner + mm.setFrom(AccountUtil.getFriendlyEmailAddress(ownerAccount)); + // sent by auth account + mm.setSender(AccountUtil.getFriendlyEmailAddress(authAccount)); + if (internalRecipients != null) { + assert (externalRecipient == null); + for (String iRecipient : internalRecipients) { + try { + mm.addRecipient(javax.mail.Message.RecipientType.TO, + new JavaMailInternetAddress(iRecipient)); + } catch (AddressException e) { + ZimbraLog.account.warn( + "Ignoring error while sending notification to " + iRecipient, e); + } + } + } else if (externalRecipient != null) { + mm.setRecipient(javax.mail.Message.RecipientType.TO, + new JavaMailInternetAddress(externalRecipient)); + } else { + mm.setRecipient(javax.mail.Message.RecipientType.TO, + new JavaMailInternetAddress(recipient)); + } + mm.setContent(mmp); + mm.saveChanges(); + + if (ZimbraLog.account.isDebugEnabled()) { + try { + ByteArrayOutputStream buf = new ByteArrayOutputStream(); + mm.writeTo(buf); + String mmDump = new String(buf.toByteArray()); + ZimbraLog.account.debug("********\n" + mmDump); + } catch (MessagingException e) { + ZimbraLog.account.debug("failed log debug share notification message", e); + } catch (IOException e) { + ZimbraLog.account.debug("failed log debug share notification message", e); + } + } + return mm; + } + + public static MimeMultipart generateMimeMultipart(String mimePartText, String mimePartHtml, + String mimePartXml) throws MessagingException { + MimeMultipart mmp = new ZMimeMultipart("alternative"); + + if (mimePartText != null) { + MimeBodyPart textPart = new ZMimeBodyPart(); + textPart.setText(mimePartText, MimeConstants.P_CHARSET_UTF8); + mmp.addBodyPart(textPart); + } + if (mimePartHtml != null) { + MimeBodyPart htmlPart = new ZMimeBodyPart(); + htmlPart.setDataHandler(new DataHandler(new HtmlPartDataSource(mimePartHtml))); + mmp.addBodyPart(htmlPart); + } + if (mimePartXml != null) { + MimeBodyPart xmlPart = new ZMimeBodyPart(); + xmlPart.setDataHandler(new DataHandler(new XmlPartDataSource(mimePartXml))); + mmp.addBodyPart(xmlPart); + } + return mmp; + } + + private static abstract class MimePartDataSource implements javax.activation.DataSource { + + private final String mText; + private byte[] mBuf = null; + + public MimePartDataSource(String text) { + mText = text; + } + + @Override + public InputStream getInputStream() throws IOException { + synchronized(this) { + if (mBuf == null) { + ByteArrayOutputStream buf = new ByteArrayOutputStream(); + OutputStreamWriter wout = + new OutputStreamWriter(buf, MimeConstants.P_CHARSET_UTF8); + String text = mText; + wout.write(text); + wout.flush(); + mBuf = buf.toByteArray(); + } + } + return new ByteArrayInputStream(mBuf); + } + + @Override + public OutputStream getOutputStream() { + throw new UnsupportedOperationException(); + } + } + + public static class HtmlPartDataSource extends MimePartDataSource { + private static final String CONTENT_TYPE = + MimeConstants.CT_TEXT_HTML + "; " + MimeConstants.P_CHARSET + "=" + MimeConstants.P_CHARSET_UTF8; + private static final String NAME = "HtmlDataSource"; + + public HtmlPartDataSource(String text) { + super(text); + } + + @Override + public String getContentType() { + return CONTENT_TYPE; + } + + @Override + public String getName() { + return NAME; + } + } + + public static class XmlPartDataSource extends MimePartDataSource { + private static final String CONTENT_TYPE = + MimeConstants.CT_XML_ZIMBRA_SHARE + "; " + MimeConstants.P_CHARSET + "=" + MimeConstants.P_CHARSET_UTF8; + private static final String NAME = "XmlDataSource"; + + public XmlPartDataSource(String text) { + super(text); + } + + @Override + public String getContentType() { + return CONTENT_TYPE; + } + + @Override + public String getName() { + return NAME; + } + } + + public static String getExtUserLoginURL(Account owner) throws ServiceException { + return ZimbraServlet.getServiceUrl(owner.getServer(), + Provisioning.getInstance().getDomain(owner), + "?virtualacctdomain=" + owner.getDomainName()); + } + + public static String getShareAcceptURL(Account account, int folderId, String externalUserEmail) + throws ServiceException { + StringBuilder encodedBuff = new StringBuilder(); + BlobMetaData.encodeMetaData(AccountConstants.P_ACCOUNT_ID, account.getId(), encodedBuff); + BlobMetaData.encodeMetaData(AccountConstants.P_FOLDER_ID, folderId, encodedBuff); + BlobMetaData.encodeMetaData(AccountConstants.P_EMAIL, externalUserEmail, encodedBuff); + Domain domain = Provisioning.getInstance().getDomain(account); + if (domain != null) { + long urlExpiration = domain.getExternalShareInvitationUrlExpiration(); + if (urlExpiration != 0) { + BlobMetaData.encodeMetaData(AccountConstants.P_LINK_EXPIRY, System.currentTimeMillis() + urlExpiration, + encodedBuff); + } + } + String data = new String(Hex.encodeHex(encodedBuff.toString().getBytes())); + return AccountUtil.generateExtUserProvURL(account, data); + } + + public static String generateExtUserProvURL(Account account, String data) + throws ServiceException { + ExtAuthTokenKey key = ExtAuthTokenKey.getCurrentKey(); + String hmac = TokenUtil.getHmac(data, key.getKey()); + String encoded = key.getVersion() + "_" + hmac + "_" + data; + String path = "/service/extuserprov/?p=" + encoded; + return ZimbraServlet.getServiceUrl(account.getServer(), + Provisioning.getInstance().getDomain(account), path); + } } \ No newline at end of file diff --git a/store/src/java/com/zimbra/cs/util/Zimbra.java b/store/src/java/com/zimbra/cs/util/Zimbra.java index d59bc3f043a..ea0103809a0 100644 --- a/store/src/java/com/zimbra/cs/util/Zimbra.java +++ b/store/src/java/com/zimbra/cs/util/Zimbra.java @@ -65,7 +65,6 @@ import com.zimbra.cs.stats.ZimbraPerf; import com.zimbra.cs.store.StoreManager; import com.zimbra.cs.zookeeper.CuratorManager; -import com.zimbra.cs.zookeeper.Service; import com.zimbra.znative.Util; /** @@ -77,7 +76,8 @@ public final class Zimbra { private static boolean sInited = false; private static boolean sIsMailboxd = false; private static String alwaysOnClusterId = null; - private static Service service = null; + private static final String HEAP_DUMP_JAVA_OPTION = "-xx:heapdumppath="; + public static Timer sTimer = new Timer("Timer-Zimbra", true); /** Sets system properties before the server fully starts up. Note that * there's a potential race condition if {@link FirstServlet} or another @@ -92,8 +92,6 @@ private static void setSystemProperties() { System.setProperty("mail.mime.multipart.allowempty", "true"); } - private static final String HEAP_DUMP_JAVA_OPTION = "-xx:heapdumppath="; - private static void validateJavaOptions() throws ServiceException { String options = LC.mailboxd_java_options.value(); if (options != null && options.toLowerCase().indexOf(HEAP_DUMP_JAVA_OPTION) > -1) { @@ -216,6 +214,8 @@ private static synchronized void startup(boolean forMailboxd) throws ServiceExce ZimbraApplication app = ZimbraApplication.getInstance(); + ZimbraPerf.prepare(ZimbraPerf.ServerID.ZIMBRA); + DbPool.startup(); app.initializeZimbraDb(forMailboxd); @@ -375,7 +375,7 @@ private static synchronized void startup(boolean forMailboxd) throws ServiceExce // should be last, so that other subsystems can add dynamic stats counters if (app.supports(ZimbraPerf.class.getName())) { - ZimbraPerf.initialize(); + ZimbraPerf.initialize(ZimbraPerf.ServerID.ZIMBRA); } } @@ -476,8 +476,6 @@ public static synchronized boolean started() { return sInited; } - public static Timer sTimer = new Timer("Timer-Zimbra", true); - /** * Logs the given message and shuts down the server. * diff --git a/store/src/java/com/zimbra/qa/unittest/ImapTestBase.java b/store/src/java/com/zimbra/qa/unittest/ImapTestBase.java index 1f828dd6494..7016dde65a2 100644 --- a/store/src/java/com/zimbra/qa/unittest/ImapTestBase.java +++ b/store/src/java/com/zimbra/qa/unittest/ImapTestBase.java @@ -10,7 +10,9 @@ import java.io.File; import java.io.FileWriter; import java.io.IOException; +import java.io.UnsupportedEncodingException; import java.util.Arrays; +import java.util.Collections; import java.util.Date; import java.util.Iterator; import java.util.List; @@ -22,6 +24,7 @@ import org.junit.rules.TestName; import com.google.common.base.Joiner; +import com.google.common.collect.Lists; import com.zimbra.common.localconfig.ConfigException; import com.zimbra.common.localconfig.LC; import com.zimbra.common.service.ServiceException; @@ -34,8 +37,10 @@ import com.zimbra.cs.imap.ImapProxy.ZimbraClientAuthenticator; import com.zimbra.cs.mailclient.CommandFailedException; import com.zimbra.cs.mailclient.auth.AuthenticatorFactory; +import com.zimbra.cs.mailclient.imap.AppendMessage; import com.zimbra.cs.mailclient.imap.AppendResult; import com.zimbra.cs.mailclient.imap.Body; +import com.zimbra.cs.mailclient.imap.CAtom; import com.zimbra.cs.mailclient.imap.Envelope; import com.zimbra.cs.mailclient.imap.Flags; import com.zimbra.cs.mailclient.imap.ImapConfig; @@ -132,6 +137,10 @@ public static void saveImapConfigSettings() saved_imap_servers = imapServer.getReverseProxyUpstreamImapServers(); saved_imap_server_enabled = imapServer.isImapServerEnabled(); saved_imap_ssl_server_enabled = imapServer.isImapSSLServerEnabled(); + ZimbraLog.test.debug("Saved ImapConfigSettings %s=%s %s=%s %s=%s", + LC.imap_always_use_remote_store.key(), saved_imap_always_use_remote_store, + Provisioning.A_zimbraImapServerEnabled, saved_imap_server_enabled, + Provisioning.A_zimbraImapSSLServerEnabled, saved_imap_ssl_server_enabled); } /** expect this to be called by subclass @After method */ @@ -139,6 +148,10 @@ public static void restoreImapConfigSettings() throws ServiceException, DocumentException, ConfigException, IOException { getLocalServer(); if (imapServer != null) { + ZimbraLog.test.debug("Restoring ImapConfigSettings %s=%s %s=%s %s=%s", + LC.imap_always_use_remote_store.key(), saved_imap_always_use_remote_store, + Provisioning.A_zimbraImapServerEnabled, saved_imap_server_enabled, + Provisioning.A_zimbraImapSSLServerEnabled, saved_imap_ssl_server_enabled); imapServer.setReverseProxyUpstreamImapServers(saved_imap_servers); imapServer.setImapServerEnabled(saved_imap_server_enabled); imapServer.setImapSSLServerEnabled(saved_imap_ssl_server_enabled); @@ -146,6 +159,11 @@ public static void restoreImapConfigSettings() TestUtil.setLCValue(LC.imap_always_use_remote_store, String.valueOf(saved_imap_always_use_remote_store)); } + public static void checkConnection(ImapConnection conn) { + assertNotNull("ImapConnection object should not be null", conn); + assertFalse("ImapConnection should not be closed", conn.isClosed()); + } + protected ImapConnection connect(String user) throws IOException { ImapConfig config = new ImapConfig(imapHostname); config.setPort(imapPort); @@ -192,6 +210,7 @@ protected ImapConnection getAdminConnection() throws Exception { } protected void doSelectShouldFail(ImapConnection conn, String folderName) throws IOException { + checkConnection(conn); MailboxInfo mbInfo = null; try { mbInfo = conn.select(folderName); @@ -205,6 +224,7 @@ protected void doSelectShouldFail(ImapConnection conn, String folderName) throws } protected MailboxInfo doSelectShouldSucceed(ImapConnection conn, String folderName) throws IOException { + checkConnection(conn); MailboxInfo mbInfo = null; try { mbInfo = conn.select(folderName); @@ -232,6 +252,7 @@ protected static class StatusExecutor { private Long expectedUnseen = null; protected MailboxInfo mbInfo = null; protected StatusExecutor(ImapConnection imapConn) { + checkConnection(imapConn); conn = imapConn; } protected StatusExecutor setExists(long expected) { expectedExists = expected; return this;}; @@ -265,6 +286,7 @@ protected MailboxInfo execShouldSucceed(String folderName, Object... params) thr protected Map doFetchShouldSucceed(ImapConnection conn, String range, String what, List expectedSubjects) throws IOException { + checkConnection(conn); try { Map mdMap = conn.fetch(range, what); assertNotNull(String.format("map returned by 'FETCH %s %s' should not be null", range, what), mdMap); @@ -312,8 +334,27 @@ protected void doRenameShouldSucceed(String origFolderName, String newFolderName } } + protected void doSubscribeShouldSucceed(ImapConnection imapConn, String folderName) { + checkConnection(imapConn); + try { + imapConn.subscribe(folderName); + } catch (Exception e) { + fail(String.format("%s %s failed - %s", CAtom.SUBSCRIBE, folderName, e.getMessage())); + } + } + + protected void doUnsubscribeShouldSucceed(ImapConnection imapConn, String folderName) { + checkConnection(imapConn); + try { + imapConn.unsubscribe(folderName); + } catch (Exception e) { + fail(String.format("%s %s failed - %s", CAtom.UNSUBSCRIBE, folderName, e.getMessage())); + } + } + protected void doListShouldFail(ImapConnection conn, String ref, String mailbox, String expected) throws IOException { + checkConnection(conn); try { conn.list(ref, mailbox); fail(String.format("'LIST \"%s\" \"%s\"' should have failed", ref, mailbox)); @@ -324,32 +365,128 @@ protected void doListShouldFail(ImapConnection conn, String ref, String mailbox, } } - protected List doListShouldSucceed(ImapConnection conn, String ref, String mailbox, int expected) + /** + * Note, due to a slightly strange quirk, the expectedMboxNames should be prefixed '/' in the case + * where the mailboxes are in the Other Users' Namespace (i.e. start with "/home/"), otherwise, they + * should not have the '/' prefix. The "/" in that case comes from the Namespace prefix and NOT + * from the mailbox name. + * + * For another way of looking at it, it is worth comparing NAMESPACE and LIST command output from + * https://tools.ietf.org/html/rfc2342 - IMAP4 Namespace: + * C: A001 NAMESPACE + * S: * NAMESPACE (("" "/")) (("#Users/" "/")) NIL + * S: A001 OK NAMESPACE command completed + * C: A002 LIST "" "#Users/Mike/%" + * S: * LIST () "/" "#Users/Mike/INBOX" + * S: * LIST () "/" "#Users/Mike/Foo" + * S: A002 OK LIST command completed. + * + * with the Zimbra equivalent: + * + * C: ZIMBRA01 NAMESPACE + * S: * NAMESPACE (("" "/")) (("/home/" "/")) NIL + * S: ZIMBRA01 OK NAMESPACE completed + * C: ZIMBRA02 LIST "" "/home/other-user/*" + * S: * LIST (\HasChildren) "/" "/home/other-user/INBOX/shared" + * S: * LIST (\HasNoChildren) "/" "/home/other-user/INBOX/shared/subFolder" + * S: ZIMBRA02 OK LIST completed + */ + protected List doLSubShouldSucceed(ImapConnection conn, String ref, String mailbox, + List expectedMboxNames, String testDesc) + throws IOException { + checkConnection(conn); + String cmdDesc = String.format("'%s \"%s\" \"%s\"'", CAtom.LSUB, ref, mailbox); + try { + List listResult = conn.lsub(ref, mailbox); + checkListDataList(cmdDesc, testDesc, listResult, expectedMboxNames); + return listResult; + } catch (CommandFailedException cfe) { + String err = cfe.getError(); + fail(String.format("%s:%s returned error '%s'", testDesc, cmdDesc, err)); + return null; + } + } + + protected List doListShouldSucceed(ImapConnection conn, String ref, String mailbox, + List expectedMboxNames, String testDesc) throws IOException { + checkConnection(conn); + String cmdDesc = String.format("'%s \"%s\" \"%s\"'", CAtom.LIST, ref, mailbox); try { List listResult = conn.list(ref, mailbox); - assertNotNull(String.format("list result 'list \"%s\" \"%s\"' should not be null", - ref, mailbox), listResult); - assertEquals(String.format( "Number of entries in list returned for 'list \"%s\" \"%s\"'", - ref, mailbox), expected, listResult.size()); + checkListDataList(cmdDesc, testDesc, listResult, expectedMboxNames); return listResult; } catch (CommandFailedException cfe) { String err = cfe.getError(); - fail(String.format("'LIST \"%s\" \"%s\"' returned error '%s'", ref, mailbox, err)); + fail(String.format("%s:%s returned error '%s'", testDesc, cmdDesc, err)); return null; } } - protected boolean listContains(List listData, String folderName) { - for (ListData ld: listData) { - if (ld.getMailbox().equalsIgnoreCase(folderName)) { + public void checkListDataList(String cmdDesc, String testDesc, List listResult, + List expectedMboxNames) throws IOException { + assertNotNull(String.format("%s:list result from %s should not be null", testDesc, cmdDesc), + listResult); + List actualMboxNames = mailboxNames(listResult); + List missingMboxNames = Lists.newArrayList(); + List extraMboxNames = Lists.newArrayList(); + for (String mbox : expectedMboxNames) { + if (!containsIgnoreCase(actualMboxNames, mbox)) { + missingMboxNames.add(mbox); + } + } + for (String mbox : actualMboxNames) { + if (!containsIgnoreCase(expectedMboxNames, mbox)) { + extraMboxNames.add(mbox); + } + } + if (!missingMboxNames.isEmpty() || !extraMboxNames.isEmpty()) { + StringBuilder sb = new StringBuilder(); + if (!missingMboxNames.isEmpty()) { + sb.append("\nMissing mailbox names:"); + sb.append(Joiner.on("\n ").join(missingMboxNames)); + } + if (!extraMboxNames.isEmpty()) { + sb.append("\nExtra mailbox names:"); + sb.append(Joiner.on("\n ").join(extraMboxNames)); + } + fail(String.format("%s:'%s' returned unexpected mailbox names %s\nList Result:%s", + testDesc, cmdDesc, sb, listResult)); + } + // Doubt if can get here but just in case + assertEquals(String.format( "%s:Number of entries in list returned for %s\n%s", + testDesc, cmdDesc, listResult), expectedMboxNames.size(), listResult.size()); + } + + public boolean containsIgnoreCase(List mboxNames, String mboxName) { + if (mboxNames == null) { + return false; + } + for (String mbox: mboxNames) { + if (mboxName.equalsIgnoreCase(mbox)) { return true; } } return false; } + public boolean listDataContains(List listData, String mboxName) { + return containsIgnoreCase(mailboxNames(listData), mboxName); + } + + public List mailboxNames(List listData) { + if (listData == null) { + return Collections.emptyList(); + } + List mboxes = Lists.newArrayListWithExpectedSize(listData.size()); + for (ListData ld: listData) { + mboxes.add(ld.getMailbox()); + } + return mboxes; + } + protected MessageData fetchMessage(ImapConnection conn, long uid) throws IOException { + checkConnection(conn); MessageData md = conn.uidFetch(uid, "(FLAGS BODY.PEEK[])"); assertNotNull(String.format( "`UID FETCH %s (FLAGS BODY.PEEK[])` returned no data - assume message not found", uid), md); @@ -409,8 +546,46 @@ public static Literal message(int size) throws IOException { return new Literal(file, true); } + protected static Literal literal(String s) { + return new Literal(bytes(s)); + } + + protected static byte[] bytes(String s) { + try { + return s.getBytes("UTF8"); + } catch (UnsupportedEncodingException e) { + fail("UTF8 encoding not supported"); + } + return null; + } + + protected AppendResult doAppend(ImapConnection conn, String folderName, String subject, String body, + Flags flags, boolean fetchResult) { + checkConnection(conn); + assertTrue("expecting UIDPLUS capability", conn.hasCapability("UIDPLUS")); + String msg = simpleMessage(subject, body); + Date date = new Date(System.currentTimeMillis()); + AppendMessage am = new AppendMessage(flags, date, literal(msg)); + try { + AppendResult res = conn.append(folderName, am); + assertNotNull("result of append command should not be null", res); + if (fetchResult) { + doSelectShouldSucceed(conn, folderName); + MessageData md = fetchMessage(conn, res.getUid()); + byte[] b = getBody(md); + assertArrayEquals("FETCH content not same as APPENDed content", msg.getBytes(), b); + } + return res; + } catch (IOException e) { + ZimbraLog.test.info("Exception thrown trying to append", e); + fail("Exception thrown trying to append:" + e.getMessage()); + } + return null; + } + protected void doAppend(ImapConnection conn, String folderName, int size, Flags flags, boolean fetchResult) throws IOException { + checkConnection(conn); assertTrue("expecting UIDPLUS capability", conn.hasCapability("UIDPLUS")); Date date = new Date(System.currentTimeMillis()); Literal msg = message(size); diff --git a/store/src/java/com/zimbra/qa/unittest/SharedImapTests.java b/store/src/java/com/zimbra/qa/unittest/SharedImapTests.java index 4722305f96f..b1beb0783e1 100644 --- a/store/src/java/com/zimbra/qa/unittest/SharedImapTests.java +++ b/store/src/java/com/zimbra/qa/unittest/SharedImapTests.java @@ -4,12 +4,13 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNotSame; import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertSame; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import java.io.IOException; -import java.io.UnsupportedEncodingException; import java.util.Date; import java.util.HashMap; import java.util.Iterator; @@ -33,11 +34,15 @@ import com.zimbra.client.ZTag; import com.zimbra.client.ZTag.Color; import com.zimbra.common.localconfig.LC; +import com.zimbra.common.mailbox.FolderStore; +import com.zimbra.common.mailbox.MailboxStore; import com.zimbra.common.mime.MimeConstants; import com.zimbra.common.service.ServiceException; import com.zimbra.common.util.ZimbraLog; import com.zimbra.cs.account.Account; import com.zimbra.cs.account.Provisioning; +import com.zimbra.cs.imap.ImapCredentials; +import com.zimbra.cs.imap.ImapPath; import com.zimbra.cs.mailbox.Mailbox; import com.zimbra.cs.mailclient.CommandFailedException; import com.zimbra.cs.mailclient.imap.AppendMessage; @@ -66,6 +71,86 @@ @SuppressWarnings("PMD.ExcessiveClassLength") public abstract class SharedImapTests extends ImapTestBase { + @Test(timeout=100000) + public void imapPath() throws IOException, ServiceException, MessagingException { + Account userAcct = TestUtil.getAccount(USER); + assertNotNull("Account object for user", userAcct); // Shuts up PMD complaint about missing asserts + Account shareeAcct = TestUtil.createAccount(SHAREE); + ZMailbox shareeZmbox = TestUtil.getZMailbox(SHAREE); + ZMailbox zmbox = TestUtil.getZMailbox(USER); + /* create a few folders to ensure that the sharee account won't have the same folder IDs + * present as are used in the shared folders. + */ + for (int cnt = 0;cnt < 10;cnt++) { + TestUtil.createFolder(zmbox, "/toplevel-" + cnt); + } + String remFolder = String.format("/INBOX/%s-shared", testInfo.getMethodName()); + String underRemFolder = String.format("%s/subFolder", remFolder); + String otherUserFolder = String.format("/home/%s%s", USER, remFolder); + String otherUserSubFolder = String.format("/home/%s%s", USER, underRemFolder); + ZFolder zfolder = TestUtil.createFolder(zmbox, remFolder); + ZFolder underZfolder = TestUtil.createFolder(zmbox, underRemFolder); + String mp = String.format("%s's %s-shared", USER, testInfo.getMethodName()); + String subMp = String.format("%s/subFolder", mp); + TestUtil.createMountpoint(zmbox, remFolder, shareeZmbox, mp); + ImapCredentials creds = new ImapCredentials(shareeAcct); + + checkImapPath("inbox", creds, "INBOX", false /* usingReferent */, + Integer.parseInt(ZFolder.ID_INBOX), shareeAcct.getId()); + checkImapPath(mp, creds, mp, true /* usingReferent */, + zfolder.getFolderItemIdentifier().id, userAcct.getId()); + checkImapPath(subMp, creds, subMp, true /* usingReferent */, + underZfolder.getFolderItemIdentifier().id, userAcct.getId()); + checkImapPath(otherUserFolder, creds, otherUserFolder, false /* usingReferent */, + zfolder.getFolderItemIdentifier().id, userAcct.getId()); + checkImapPath(otherUserSubFolder, creds, otherUserSubFolder, false /* usingReferent */, + underZfolder.getFolderItemIdentifier().id, userAcct.getId()); + } + + private void checkImapPath(String mboxName, ImapCredentials creds, String expectedPathToString, + boolean usingReferent, int expectedReferentFolderId, String expectedReferentFolderAcct) + throws ServiceException { + ImapPath path = new ImapPath(mboxName, creds); + path.canonicalize(); + ImapPath referent = path.getReferent(); + assertEquals(String.format("toString() for ImapPath for mailbox '%s'", mboxName), + expectedPathToString, path.toString()); + if (usingReferent) { + assertNotSame( + String.format("ImapPath=%s and it's getReferent() for mailbox '%s'", path, mboxName), + path, referent); + } else { + assertSame( + String.format("ImapPath=%s and it's getReferent() for mailbox '%s'", path, mboxName), + path, referent); + } + FolderStore folderForPath = path.getFolder(); + assertEquals(String.format( + "Folder ID for path.getReferent().getFolder() for mailbox '%s'", mboxName), + expectedReferentFolderId, folderIdForFolder(folderForPath)); + assertEquals(String.format( + "Account ID for path.getReferent().getFolder() for mailbox '%s'", mboxName), + expectedReferentFolderAcct, + acctIdForFolder(folderForPath)); + } + + private String acctIdForFolder(FolderStore folder) { + MailboxStore mbox = folder.getMailboxStore(); + String acctId; + try { + acctId = mbox.getAccountId(); + } catch (ServiceException e) { + acctId = ""; + } + ZimbraLog.test.debug("Account ID = %s for folder %s", acctId, folder); + return acctId; + } + + private int folderIdForFolder(FolderStore folder) { + ZimbraLog.test.debug("Folder ID = %s for folder %s", folder.getFolderIdInOwnerMailbox(), folder); + return folder.getFolderIdInOwnerMailbox(); + } + @Test(timeout=100000) public void testListFolderContents() throws IOException, ServiceException, MessagingException { String folderName = "SharedImapTests-testOpenFolder"; @@ -220,6 +305,7 @@ public void idleOnInboxNotification() throws IOException, ServiceException, Mess otherConnection = connectAndLogin(USER); String subject = "SharedImapTest-testIdleNotification"; ZMailbox zmbox = TestUtil.getZMailbox(USER); + assertNotNull("ZMailbox for USER", zmbox); TestUtil.addMessage(zmbox, subject, "1", null); doIdleNotificationCheck(connection, otherConnection, "INBOX"); } @@ -229,6 +315,7 @@ public void idleOnMountpoint() throws ServiceException, IOException, MessagingEx TestUtil.createAccount(SHAREE); ZMailbox shareeZmbox = TestUtil.getZMailbox(SHAREE); ZMailbox zmbox = TestUtil.getZMailbox(USER); + assertNotNull("ZMailbox for USER", zmbox); String sharedFolderName = String.format("INBOX/%s-shared", testId); String remoteFolderPath = "/" + sharedFolderName; TestUtil.createFolder(zmbox, remoteFolderPath); @@ -242,7 +329,8 @@ public void idleOnMountpoint() throws ServiceException, IOException, MessagingEx @Test(timeout=100000) public void idleOnFolderViaHome() throws ServiceException, IOException, MessagingException { - TestUtil.createAccount(SHAREE); + Account shareeAcct = TestUtil.createAccount(SHAREE); + assertNotNull("Account for SHAREE", shareeAcct); connection = connectAndSelectInbox(USER); String sharedFolderName = String.format("INBOX/%s-shared", testId); connection.create(sharedFolderName); @@ -275,6 +363,7 @@ public void statusOnInbox() throws ServiceException, IOException, MessagingExcep otherConnection.close(); otherConnection = null; ZMailbox zmbox = TestUtil.getZMailbox(USER); + assertNotNull("ZMailbox for USER", zmbox); new StatusExecutor(connection).setExists(2).setRecent(0) .execShouldSucceed("INBOX", "UIDNEXT", "MESSAGES", "RECENT"); /* Add a message so that the RECENT count will be > 0 */ @@ -288,6 +377,7 @@ public void statusOnMountpoint() throws ServiceException, IOException, Messaging TestUtil.createAccount(SHAREE); ZMailbox shareeZmbox = TestUtil.getZMailbox(SHAREE); ZMailbox zmbox = TestUtil.getZMailbox(USER); + assertNotNull("ZMailbox for USER", zmbox); String sharedFolderName = String.format("INBOX/%s", testInfo.getMethodName()); String remoteFolderPath = "/" + sharedFolderName; ZFolder zfolder = TestUtil.createFolder(zmbox, remoteFolderPath); @@ -844,7 +934,6 @@ public void testStoreTags() throws Exception { assertTrue(tags != null && tags.size() == 1); assertEquals("T1", tags.get(0).getName()); - connection.store(seq+"", "-FLAGS", tagName3); data = connection.fetch(seq+"", "FLAGS"); assertEquals(1, data.size()); @@ -1371,47 +1460,100 @@ public void testUidCopy() throws IOException, ServiceException { @Test(timeout=100000) public void testSubscribeNested() throws IOException, ServiceException { connection = connectAndSelectInbox(); - String folderName = "TestImap-testSubscribeNested"; - ZFolder folder = TestUtil.createFolder(TestUtil.getZMailbox(USER),Integer.toString(Mailbox.ID_FOLDER_INBOX), folderName); - List listResult = connection.lsub("", "*"); - assertNotNull(listResult); - assertEquals("Should have 0 subscriptions before subscribing", 0, listResult.size()); - try { - connection.subscribe(folder.getPath()); - } catch (Exception e) { - fail(e.getMessage()); - } - listResult = connection.lsub("", "*"); - assertNotNull(listResult); - assertEquals("Should have 1 subscription after subscribing", 1, listResult.size()); - assertTrue("Should be subscribed to " + folder.getPath().substring(1) + ". Instead got " + listResult.get(0).getMailbox(), folder.getPath().substring(1).equalsIgnoreCase(listResult.get(0).getMailbox())); + String folderName = testId; + ZFolder folder = TestUtil.createFolder( + TestUtil.getZMailbox(USER),Integer.toString(Mailbox.ID_FOLDER_INBOX), folderName); + assertNotNull("Folder object from createFolder", folder); + doLSubShouldSucceed(connection, "", "*", Lists.newArrayListWithExpectedSize(0), "before subscribe"); + String imapMboxName = folder.getPath().substring(1); + doSubscribeShouldSucceed(connection, imapMboxName); + doLSubShouldSucceed(connection, "", "*", Lists.newArrayList(imapMboxName), "after subscribe"); } @Test(timeout=100000) public void testUnSubscribe() throws IOException, ServiceException { connection = connectAndSelectInbox(); - String folderName = "TestImap-testUnSubscribe"; - TestUtil.createFolder(TestUtil.getZMailbox(USER), folderName); - List listResult = connection.lsub("", "*"); - assertNotNull(listResult); - assertEquals("Should have 0 subscriptions before subscribing", 0, listResult.size()); - try { - connection.subscribe(folderName); - } catch (Exception e) { - fail(e.getMessage()); - } - listResult = connection.lsub("", "*"); - assertNotNull(listResult); - assertEquals("Should have 1 subscription after subscribing", 1, listResult.size()); - assertTrue("Should be subscribed to " + folderName + ". Instead got " + listResult.get(0).getMailbox(), folderName.equalsIgnoreCase(listResult.get(0).getMailbox())); - try { - connection.unsubscribe(folderName); - } catch (Exception e) { - fail(e.getMessage()); - } - listResult = connection.lsub("", "*"); - assertNotNull(listResult); - assertEquals("Should have 0 subscriptions after unsubscribing", 0, listResult.size()); + String folderName = testId; + ZFolder folder = TestUtil.createFolder(TestUtil.getZMailbox(USER), folderName); + assertNotNull("Folder object from createFolder", folder); + doLSubShouldSucceed(connection, "", "*", Lists.newArrayListWithExpectedSize(0), "before subscribe"); + doSubscribeShouldSucceed(connection, folderName); + doLSubShouldSucceed(connection, "", "*", Lists.newArrayList(folderName), "after subscribe"); + doUnsubscribeShouldSucceed(connection, folderName); + doLSubShouldSucceed(connection, "", "*", Lists.newArrayListWithExpectedSize(0), "after unsubscribe"); + } + + @Test(timeout=100000) + public void homeNameSpaceSubscribe() throws ServiceException, IOException, MessagingException { + String sharedFolderName = String.format("INBOX/%s-shared", testId); + String subFolder = sharedFolderName + "/subFolder"; + ZMailbox userZmbox = TestUtil.getZMailbox(USER); + ZFolder folder = TestUtil.createFolder(userZmbox, "/" + sharedFolderName); + assertNotNull("Folder object from createFolder", folder); + TestUtil.createFolder(userZmbox, "/" + subFolder); + TestUtil.createAccount(SHAREE); + connection = connectAndLogin(USER); + connection.setacl(sharedFolderName, SHAREE, "lrswickxteda"); + connection.logout(); + connection = null; + String remFolder = String.format("/home/%s/%s", USER, sharedFolderName); + String underRemFolder = String.format("%s/subFolder", remFolder); + String homePatt = String.format("/home/%s/*", USER); + otherConnection = connectAndLogin(SHAREE); + doLSubShouldSucceed(otherConnection, "", "*", + Lists.newArrayListWithExpectedSize(0), "before 1st subscribe"); + doSubscribeShouldSucceed(otherConnection, "INBOX"); + doSubscribeShouldSucceed(otherConnection, remFolder); + doLSubShouldSucceed(otherConnection, "", "*", Lists.newArrayList("INBOX"), + String.format("after subscribing to INBOX and '%s'", remFolder)); + doLSubShouldSucceed(otherConnection, "", homePatt, Lists.newArrayList(remFolder), + String.format("after subscribing to INBOX and '%s'", remFolder)); + doSubscribeShouldSucceed(otherConnection, underRemFolder); + doLSubShouldSucceed(otherConnection, "", homePatt, Lists.newArrayList(remFolder, underRemFolder), + String.format("after subscribing to INBOX and '%s' and '%s'", remFolder, underRemFolder)); + doUnsubscribeShouldSucceed(otherConnection, remFolder); + doLSubShouldSucceed(otherConnection, "", homePatt, Lists.newArrayList(underRemFolder), + String.format("after unsubscribing from '%s'", remFolder)); + doUnsubscribeShouldSucceed(otherConnection, underRemFolder); + doLSubShouldSucceed(otherConnection, "", homePatt, Lists.newArrayListWithExpectedSize(0), + String.format("after unsubscribing from '%s'", remFolder)); + doLSubShouldSucceed(otherConnection, "", "*", Lists.newArrayList("INBOX"), + String.format("after unsubscribing from '%s'", remFolder)); + otherConnection.logout(); + otherConnection = null; + } + + @Test(timeout=100000) + public void mountpointSubscribe() throws ServiceException, IOException { + TestUtil.createAccount(SHAREE); + ZMailbox shareeZmbox = TestUtil.getZMailbox(SHAREE); + assertNotNull("Sharee ZMailbox", shareeZmbox); + ZMailbox mbox = TestUtil.getZMailbox(USER); + String remFolder = String.format("/INBOX/%s-shared", testId); + String underRemFolder = String.format("%s/subFolder", remFolder); + TestUtil.createFolder(mbox, remFolder); + TestUtil.createFolder(mbox, underRemFolder); + String mp = String.format("%s's %s-shared", USER, testId); + String subMp = String.format("%s/subFolder", mp); + TestUtil.createMountpoint(mbox, remFolder, shareeZmbox, mp); + otherConnection = connectAndLogin(SHAREE); + doLSubShouldSucceed(otherConnection, "", "*", + Lists.newArrayListWithExpectedSize(0), "before 1st subscribe"); + doSubscribeShouldSucceed(otherConnection, "INBOX"); + doSubscribeShouldSucceed(otherConnection, mp); + doLSubShouldSucceed(otherConnection, "", "*", Lists.newArrayList("INBOX", mp), + String.format("after subscribing to INBOX and '%s'", mp)); + doSubscribeShouldSucceed(otherConnection, subMp); + doLSubShouldSucceed(otherConnection, "", "*", Lists.newArrayList("INBOX", mp, subMp), + String.format("after subscribing to '%s'", subMp)); + doUnsubscribeShouldSucceed(otherConnection, mp); + doLSubShouldSucceed(otherConnection, "", "*", Lists.newArrayList("INBOX", subMp), + String.format("after unsubscribing from '%s'", mp)); + doUnsubscribeShouldSucceed(otherConnection, subMp); + doLSubShouldSucceed(otherConnection, "", "*", Lists.newArrayList("INBOX"), + String.format("after unsubscribing from '%s'", subMp)); + otherConnection.logout(); + otherConnection = null; } @SuppressWarnings("PMD.JUnitTestsShouldIncludeAssert") // checking done in called methods @@ -1750,22 +1892,10 @@ private String url(String mbox, AppendResult res) { mbox, res.getUidValidity(), res.getUid()); } - private static Literal literal(String s) { - return new Literal(bytes(s)); - } - - private static byte[] bytes(String s) { - try { - return s.getBytes("UTF8"); - } catch (UnsupportedEncodingException e) { - fail("UTF8 encoding not supported"); - } - return null; - } - @Test(timeout=100000) public void listSharedFolderViaHome() throws ServiceException, IOException { - TestUtil.createAccount(SHAREE); + Account shareeAcct = TestUtil.createAccount(SHAREE); + assertNotNull("Account for SHAREE", shareeAcct); connection = connectAndSelectInbox(); String sharedFolderName = String.format("INBOX/%s-shared", testId); connection.create(sharedFolderName); @@ -1778,72 +1908,49 @@ public void listSharedFolderViaHome() throws ServiceException, IOException { String underRemFolder = String.format("%s/subFolder", remFolder); String homeFilter = String.format("/home/%s", USER); otherConnection = connectAndSelectInbox(SHAREE); - List listResult; - String ref; - String mailbox; doListShouldFail(otherConnection, "/home", "*", "LIST failed: wildcards not permitted in username"); doListShouldFail(otherConnection, "", "/home/*", "LIST failed: wildcards not permitted in username"); doListShouldFail(otherConnection, "", "/home/fred*", "LIST failed: wildcards not permitted in username"); doListShouldFail(otherConnection, "", "/home/*fred", "LIST failed: wildcards not permitted in username"); - doListShouldSucceed(otherConnection, "", "INBOX", 1); // reset zimbraImapMaxConsecutiveError counter + // reset zimbraImapMaxConsecutiveError counter + doListShouldSucceed(otherConnection, "", "INBOX", Lists.newArrayList("INBOX"), "JUST INBOX #1"); doListShouldFail(otherConnection, "", "/home/pete*fred", "LIST failed: wildcards not permitted in username"); doListShouldFail(otherConnection, "", "/home/*/", "LIST failed: wildcards not permitted in username"); doListShouldFail(otherConnection, "", "/home/pete*/", "LIST failed: wildcards not permitted in username"); doListShouldFail(otherConnection, "", "/home/pete*fred/", "LIST failed: wildcards not permitted in username"); - doListShouldSucceed(otherConnection, "", "INBOX", 1); // reset zimbraImapMaxConsecutiveError counter + // reset zimbraImapMaxConsecutiveError counter + doListShouldSucceed(otherConnection, "", "INBOX", Lists.newArrayList("INBOX"), "JUST INBOX #2"); doListShouldFail(otherConnection, "", "/home/*/INBOX", "LIST failed: wildcards not permitted in username"); doListShouldFail(otherConnection, "", "/home/pete*/INBOX", "LIST failed: wildcards not permitted in username"); doListShouldFail(otherConnection, "", "/home/pete*fred/INBOX", "LIST failed: wildcards not permitted in username"); // LIST "" "/home/user/sharedFolderName" - listResult = doListShouldSucceed(otherConnection, "", remFolder, 1); + doListShouldSucceed(otherConnection, "", remFolder, Lists.newArrayList(remFolder), "JUST remFolder"); // 'LIST "/home" "user"' - should get: // * LIST (\NoSelect) "/" "/home/user" - listResult = doListShouldSucceed(otherConnection, "/home", USER, 1); + doListShouldSucceed(otherConnection, "/home", USER, + Lists.newArrayList(String.format("/home/%s", USER)), "JUST /home/user"); // 'LIST "/home" "user/*"' - ref = "/home"; - mailbox = USER + "/*"; - listResult = doListShouldSucceed(otherConnection, ref, mailbox, 2); - assertEquals(String.format( - "'%s' mailbox not in result of 'list \"%s\" \"%s\"'", remFolder, ref, mailbox), - remFolder, listResult.get(0).getMailbox()); - assertEquals(String.format( - "'%s' mailbox not in result of 'list \"%s\" \"%s\"'", underRemFolder, ref, mailbox), - underRemFolder, listResult.get(1).getMailbox()); + doListShouldSucceed(otherConnection, "/home", USER + "/*", + Lists.newArrayList(remFolder, underRemFolder), "all folders for user - 1"); // LIST "/home/user" "*" - ref = homeFilter; - mailbox = "*"; - listResult = doListShouldSucceed(otherConnection, ref, mailbox, 2); - assertEquals(String.format( - "'%s' mailbox not in result of 'list \"%s\" \"%s\"'", remFolder, ref, mailbox), - remFolder, listResult.get(0).getMailbox()); - assertEquals(String.format( - "'%s' mailbox not in result of 'list \"%s\" \"%s\"'", underRemFolder, ref, mailbox), - underRemFolder, listResult.get(1).getMailbox()); + doListShouldSucceed(otherConnection, homeFilter, "*", + Lists.newArrayList(remFolder, underRemFolder), "all folders for user - 2"); // 'LIST "/home" "user/INBOX"' - ref = "/home"; - mailbox = USER + "/INBOX"; - listResult = doListShouldSucceed(otherConnection, ref, mailbox, 0); + doListShouldSucceed(otherConnection, "/home", USER + "/INBOX", + Lists.newArrayList(), "unshared INBOX for user"); // LIST "/home/user" "sharedFolderName" - ref = homeFilter; - mailbox = sharedFolderName; - listResult = doListShouldSucceed(otherConnection, homeFilter, sharedFolderName, 1); - assertEquals(String.format( - "'%s' mailbox not in result of 'list \"%s\" \"%s\"'", remFolder, ref, mailbox), - remFolder, listResult.get(0).getMailbox()); + doListShouldSucceed(otherConnection, homeFilter, sharedFolderName, + Lists.newArrayList(remFolder), "shared folder for user"); // LIST "/home/user" "sharedFolderName/subFolder" - ref = homeFilter; - mailbox = underSharedFolderName; - listResult = doListShouldSucceed(otherConnection, homeFilter, underSharedFolderName, 1); - assertEquals(String.format( - "'%s' mailbox not in result of 'list \"%s\" \"%s\"'", underRemFolder, ref, mailbox), - underRemFolder, listResult.get(0).getMailbox()); + doListShouldSucceed(otherConnection, homeFilter, underSharedFolderName, + Lists.newArrayList(underRemFolder), "shared folder for user"); otherConnection.logout(); otherConnection = null; @@ -1860,13 +1967,11 @@ public void listMountpoint() throws ServiceException, IOException { String mountpointName = String.format("%s's %s-shared", USER, testId); TestUtil.createMountpoint(mbox, remoteFolderPath, shareeZmbox, mountpointName); otherConnection = connectAndLogin(SHAREE); - List listResult; // LIST "" "mountpointName" - listResult = doListShouldSucceed(otherConnection, "", mountpointName, 1); - assertEquals(String.format( - "'%s' mountpoint not in result of 'list \"\" \"%s\"'", mountpointName, mountpointName), - mountpointName, listResult.get(0).getMailbox()); + doListShouldSucceed(otherConnection, "", mountpointName, + Lists.newArrayList(mountpointName), "mountpoint"); + List listResult; listResult = otherConnection.list("", "*"); assertNotNull("list result 'list \"\" \"*\"' should not be null", listResult); boolean seenIt = false; @@ -1906,60 +2011,42 @@ private SubFolderEnv(String sharedFolderName, String subFolder) @Test(timeout=100000) public void mountpointWithSubFolder() throws ServiceException, IOException, MessagingException { + List listResult; + String sharedFolderName = String.format("INBOX/%s-shared", testId); String subFolder = sharedFolderName + "/subFolder"; TestUtil.createAccount(SHAREE); + otherConnection = connectAndLogin(SHAREE); + listResult = otherConnection.list("", "*"); + List baselineMboxNames = mailboxNames(listResult); + SubFolderEnv subFolderEnv = new SubFolderEnv(sharedFolderName, subFolder); ZMailbox userZmbox = TestUtil.getZMailbox(USER); + assertNotNull("ZMailbox for USER", userZmbox); ZMailbox shareeZmbox = TestUtil.getZMailbox(SHAREE); + String mountpointName = String.format("%s's %s-shared", USER, testId); String subMountpoint = mountpointName + "/subFolder"; String remoteFolderPath = "/" + sharedFolderName; TestUtil.createMountpoint(userZmbox, remoteFolderPath, shareeZmbox, mountpointName); - otherConnection = connectAndLogin(SHAREE); - - String ref; - String searchPatt; - List listResult; /* wild card at end should pick up top level and sub-folder */ - searchPatt = mountpointName + "*"; - ref = ""; - listResult = doListShouldSucceed(otherConnection, ref, searchPatt, 2); - assertEquals(String.format( - "'%s' mountpoint not in result of 'list \"\" \"%s\"'", mountpointName, mountpointName), - mountpointName, listResult.get(0).getMailbox()); - assertEquals(String.format( - "'%s' mountpoint not in result of 'list \"\" \"%s\"'", subMountpoint, mountpointName), - subMountpoint, listResult.get(1).getMailbox()); + doListShouldSucceed(otherConnection, "", mountpointName + "*", + Lists.newArrayList(mountpointName, subMountpoint), "wildcard under MP"); /* exact match shouldn't pick up sub-folder */ - searchPatt = mountpointName; - listResult = doListShouldSucceed(otherConnection, ref, searchPatt, 1); - assertEquals(String.format( - "'%s' mountpoint not in result of 'list \"\" \"%s\"'", mountpointName, mountpointName), - mountpointName, listResult.get(0).getMailbox()); + doListShouldSucceed(otherConnection, "", mountpointName, + Lists.newArrayList(mountpointName), "JUST MP"); /* exact match on sub-folder should pick up just sub-folder */ - searchPatt = subMountpoint; - listResult = doListShouldSucceed(otherConnection, ref, searchPatt, 1); - listResult = otherConnection.list("", subMountpoint); - assertEquals(String.format( - "'%s' mountpoint not in result of 'list \"\" \"%s\"'", subMountpoint, subMountpoint), - subMountpoint, listResult.get(0).getMailbox()); + doListShouldSucceed(otherConnection, "", subMountpoint, + Lists.newArrayList(subMountpoint), "JUST subfolder of MP"); + List expectedMboxNames = Lists.newArrayList(baselineMboxNames); + expectedMboxNames.add(mountpointName); + expectedMboxNames.add(subMountpoint); /* sub-folder should be in list of all folders */ - listResult = otherConnection.list("", "*"); - assertNotNull("list result for 'list \"\" \"*\"' should not be null", listResult); - boolean seenIt = false; - for (ListData listEnt : listResult) { - if (subMountpoint.equals(listEnt.getMailbox())) { - seenIt = true; - break; - } - } - assertTrue(String.format("'%s' mountpoint not in result of 'list \"\" \"*\"'", subMountpoint), seenIt); - + doListShouldSucceed(otherConnection, "", "*", expectedMboxNames, "List ALL including mountpoints"); doSelectShouldSucceed(otherConnection, mountpointName); doFetchShouldSucceed(otherConnection, "1:*", "(FLAGS ENVELOPE)", subFolderEnv.subjects); doSelectShouldSucceed(otherConnection, subMountpoint); @@ -1978,12 +2065,12 @@ public void mountpointWithSubFolder() throws ServiceException, IOException, Mess otherConnection = null; } - @SuppressWarnings("PMD.JUnitTestsShouldIncludeAssert") // checking done in called methods @Test(timeout=100000) public void homeNameSpaceWithSubFolder() throws ServiceException, IOException, MessagingException { String sharedFolderName = String.format("INBOX/%s-shared", testId); String subFolder = sharedFolderName + "/subFolder"; - TestUtil.createAccount(SHAREE); + Account shareeAcct = TestUtil.createAccount(SHAREE); + assertNotNull("Account for SHAREE", shareeAcct); SubFolderEnv subFolderEnv = new SubFolderEnv(sharedFolderName, subFolder); connection = connectAndLogin(USER); connection.setacl(sharedFolderName, SHAREE, "lrswickxteda"); @@ -1992,8 +2079,9 @@ public void homeNameSpaceWithSubFolder() throws ServiceException, IOException, M String remFolder = String.format("/home/%s/%s", USER, sharedFolderName); String underRemFolder = String.format("%s/subFolder", remFolder); otherConnection = connectAndLogin(SHAREE); - doListShouldSucceed(otherConnection, "", remFolder, 1); - doListShouldSucceed(otherConnection, "", underRemFolder, 1); + doListShouldSucceed(otherConnection, "", remFolder, Lists.newArrayList(remFolder), "shared folder"); + doListShouldSucceed(otherConnection, "", underRemFolder, Lists.newArrayList(underRemFolder), + "subfolder of shared folder"); doSelectShouldSucceed(otherConnection, remFolder); doFetchShouldSucceed(otherConnection, "1:*", "(FLAGS ENVELOPE)", subFolderEnv.subjects); doSelectShouldSucceed(otherConnection, underRemFolder); @@ -2126,20 +2214,20 @@ public void testRenameParentFolder() throws Exception { connection.login(PASS); connection.create(childFolder2); List listResult = connection.list("", "*"); - assertTrue(listContains(listResult, parentFolder)); - assertTrue(listContains(listResult, childFolder1)); - assertTrue(listContains(listResult, childFolder2)); + assertTrue(listDataContains(listResult, parentFolder)); + assertTrue(listDataContains(listResult, childFolder1)); + assertTrue(listDataContains(listResult, childFolder2)); String newParentFolder = "renamed"; String newChildFolder1 = newParentFolder + "/child1"; String newChildFolder2 = newChildFolder1 + "/child2"; connection.rename(parentFolder, newParentFolder); listResult = connection.list("", "*"); - assertTrue(listContains(listResult, newParentFolder)); - assertTrue(listContains(listResult, newChildFolder1)); - assertTrue(listContains(listResult, newChildFolder2)); - assertFalse(listContains(listResult, parentFolder)); - assertFalse(listContains(listResult, childFolder1)); - assertFalse(listContains(listResult, childFolder2)); + assertTrue(listDataContains(listResult, newParentFolder)); + assertTrue(listDataContains(listResult, newChildFolder1)); + assertTrue(listDataContains(listResult, newChildFolder2)); + assertFalse(listDataContains(listResult, parentFolder)); + assertFalse(listDataContains(listResult, childFolder1)); + assertFalse(listDataContains(listResult, childFolder2)); } @Test(timeout=100000) @@ -2156,10 +2244,8 @@ public void savedSearch() throws ServiceException, IOException, MessagingExcepti connection = this.connectAndLogin(USER); List listResult; // LIST "" "mountpointName" - listResult = doListShouldSucceed(connection, "", folderName, 1); - assertEquals(String.format( - "'%s' mailbox not in result of 'list \"\" \"%s\"'", folderName, folderName), - folderName, listResult.get(0).getMailbox()); + doListShouldSucceed(connection, "", folderName, Lists.newArrayList(folderName), + "Just search folder"); listResult = connection.list("", "*"); assertNotNull("list result 'list \"\" \"*\"' should not be null", listResult); boolean seenIt = false; @@ -2211,8 +2297,8 @@ public void clashingHomeSubFolders() throws ServiceException, IOException, Messa otherConnection.create(remFolder2); doSelectShouldSucceed(otherConnection, remFolder1); doSelectShouldSucceed(otherConnection, remFolder2); - doListShouldSucceed(otherConnection, "", remFolder1, 1); - doListShouldSucceed(otherConnection, "", remFolder2, 1); + doListShouldSucceed(otherConnection, "", remFolder1, Lists.newArrayList(remFolder1), "shared 1"); + doListShouldSucceed(otherConnection, "", remFolder2, Lists.newArrayList(remFolder2), "shared 2"); otherConnection = connectAndLogin(USER); doSelectShouldSucceed(otherConnection, underSharedFolderName); otherConnection.logout(); @@ -2221,6 +2307,119 @@ public void clashingHomeSubFolders() throws ServiceException, IOException, Messa doSelectShouldSucceed(otherConnection, underSharedFolderName); } + @Test(timeout=100000) + public void searchBodyHomeShare() throws ServiceException, IOException { + List matches; + TestUtil.createAccount(SHAREE); + connection = super.connectAndLogin(USER); + String sharedFolderName = "INBOX/share"; + connection.create(sharedFolderName); + String underSharedFolderName = String.format("%s/subFolder", sharedFolderName); + connection.create(underSharedFolderName); + String topBody = "Orange\nApple\nPear\nPlum Nectarine"; + String subBody = "Green\nBlack\nBlue\nPurple Silver"; + doAppend(connection, sharedFolderName, "in share directly under inbox", topBody, (Flags) null, + true /* do fetch to check content */); + doAppend(connection, sharedFolderName, "in share directly under inbox", "nothing much", (Flags) null, + true /* do fetch to check content */); + doAppend(connection, underSharedFolderName, "in subFolder", subBody, (Flags) null, + true /* do fetch to check content */); + doAppend(connection, underSharedFolderName, "in subFolder", "even less interesting", (Flags) null, + true /* do fetch to check content */); + doSelectShouldSucceed(connection, sharedFolderName); + matches = connection.search((Object[]) new String[] { "BODY Pear" } ); + assertEquals("Number of matches in top level for owner", 1, matches.size()); + connection.setacl(sharedFolderName, SHAREE, "lrswickxteda"); + connection.logout(); + connection = null; + String remFolder = String.format("/home/%s/%s", USER, sharedFolderName); + String underRemFolder = String.format("%s/subFolder", remFolder); + otherConnection = connectAndLogin(SHAREE); + doSelectShouldSucceed(otherConnection, remFolder); + matches = otherConnection.search((Object[]) new String[] { "BODY Pear" } ); + assertEquals("Number of matches in top level", 1, matches.size()); + assertEquals("ID of matching message in top level", Long.valueOf(1), matches.get(0)); + doSelectShouldSucceed(otherConnection, underRemFolder); + matches = otherConnection.search((Object[]) new String[] { "BODY Purple" } ); + assertEquals("Number of matches in subFolder", 1, matches.size()); + assertEquals("ID of matching message in subFolder", Long.valueOf(1), matches.get(0)); + otherConnection.logout(); + otherConnection = null; + } + + @Test(timeout=100000) + public void searchBodyMountpoint() throws ServiceException, IOException { + String sharedFolder = "INBOX/share"; + String subFolder = sharedFolder + "/subFolder"; + String mountpoint = String.format("shared-", testInfo.getMethodName()); + String subMountpoint = mountpoint + "/subFolder"; + TestUtil.createAccount(SHAREE); + ZMailbox userZmbox = TestUtil.getZMailbox(USER); + ZMailbox shareeZmbox = TestUtil.getZMailbox(SHAREE); + + TestUtil.createMountpoint(userZmbox, "/" + sharedFolder, shareeZmbox, mountpoint); + TestUtil.createFolder(userZmbox, "/" + subFolder); + List matches; + connection = super.connectAndLogin(USER); + String topBody = "Orange\nApple\nPear\nPlum Nectarine"; + String subBody = "Green\nBlack\nBlue\nPurple Silver"; + doAppend(connection, sharedFolder, "in share directly under inbox", topBody, (Flags) null, + true /* do fetch to check content */); + doAppend(connection, sharedFolder, "in share directly under inbox", "nothing much", (Flags) null, + true /* do fetch to check content */); + doAppend(connection, subFolder, "in subFolder", subBody, (Flags) null, + true /* do fetch to check content */); + doAppend(connection, subFolder, "in subFolder", "even less interesting", (Flags) null, + true /* do fetch to check content */); + doSelectShouldSucceed(connection, subFolder); + matches = connection.search((Object[]) new String[] { "BODY Black" } ); + assertEquals("Number of matches in top level for owner", 1, matches.size()); + connection.logout(); + connection = null; + otherConnection = connectAndLogin(SHAREE); + doSelectShouldSucceed(otherConnection, mountpoint); + matches = otherConnection.search((Object[]) new String[] { "BODY Pear" } ); + assertEquals("Number of matches in top level", 1, matches.size()); + assertEquals("ID of matching message in top level", Long.valueOf(1), matches.get(0)); + doSelectShouldSucceed(otherConnection, subMountpoint); + matches = otherConnection.search((Object[]) new String[] { "BODY Black" } ); + assertEquals("Number of matches in subFolder", 1, matches.size()); + assertEquals("ID of matching message in subFolder", Long.valueOf(1), matches.get(0)); + otherConnection.logout(); + otherConnection = null; + } + + @Test + public void copyToMountpoint() throws Exception { + TestUtil.createAccount(SHAREE); + ZMailbox userZmbox = TestUtil.getZMailbox(USER); + ZMailbox shareeZmbox = TestUtil.getZMailbox(SHAREE); + String sharedFolder = "INBOX/share"; + String mountpoint = String.format("shared-", testInfo.getMethodName()); + String subject = "SharedImapTests-testMessage"; + TestUtil.createMountpoint(userZmbox, "/" + sharedFolder, shareeZmbox, mountpoint); + TestUtil.addMessage(shareeZmbox, subject, Integer.toString(Mailbox.ID_FOLDER_INBOX), null); + connection = connectAndSelectInbox(SHAREE); + CopyResult copyResult = connection.copy("1", mountpoint); + assertNotNull("copyResult.getFromUids()", copyResult.getFromUids()); + assertNotNull("copyResult.getToUids()", copyResult.getToUids()); + assertEquals("Number of fromUIDs", 1, copyResult.getFromUids().length); + assertEquals("Number of toUIDs", 1, copyResult.getToUids().length); + MailboxInfo selectMboxInfo = connection.select(mountpoint); + assertNotNull(String.format("Select result for folder=%s", mountpoint), selectMboxInfo); + assertEquals("Select result Folder Name folder", mountpoint, selectMboxInfo.getName()); + assertEquals(String.format("Number of exists for folder=%s after copy", mountpoint), + 1, selectMboxInfo.getExists()); + Map mdMap = this.doFetchShouldSucceed(connection, "1:*", "(ENVELOPE)", + Lists.newArrayList(subject)); + MessageData md = mdMap.values().iterator().next(); + assertNull("Internal date was NOT requested and should be NULL", md.getInternalDate()); + BodyStructure bs = md.getBodyStructure(); + assertNull("Body Structure was not requested and should be NULL", bs); + Body[] body = md.getBodySections(); + assertNull("body sections were not requested and should be null", body); + } + protected void flushCacheIfNecessary() throws Exception { // overridden by tests running against imapd } diff --git a/store/src/java/com/zimbra/qa/unittest/TestCalDav.java b/store/src/java/com/zimbra/qa/unittest/TestCalDav.java index 84e53166f91..5637f817ce2 100644 --- a/store/src/java/com/zimbra/qa/unittest/TestCalDav.java +++ b/store/src/java/com/zimbra/qa/unittest/TestCalDav.java @@ -16,6 +16,12 @@ */ package com.zimbra.qa.unittest; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + import java.io.ByteArrayInputStream; import java.io.File; import java.io.IOException; @@ -38,9 +44,6 @@ import javax.xml.xpath.XPathExpressionException; import javax.xml.xpath.XPathFactory; -import net.fortuna.ical4j.model.TimeZoneRegistry; -import net.fortuna.ical4j.model.TimeZoneRegistryFactory; - import org.apache.commons.httpclient.Header; import org.apache.commons.httpclient.HttpClient; import org.apache.commons.httpclient.HttpMethod; @@ -51,19 +54,21 @@ import org.apache.commons.httpclient.methods.GetMethod; import org.apache.commons.httpclient.methods.PostMethod; import org.apache.commons.httpclient.methods.PutMethod; +import org.junit.After; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; import org.w3c.dom.Document; import org.w3c.dom.Node; import org.w3c.dom.NodeList; +import com.google.common.base.Joiner; import com.google.common.base.Strings; import com.google.common.collect.Lists; import com.google.common.collect.Maps; import com.google.common.io.Closeables; -import com.zimbra.client.ZFolder; -import com.zimbra.client.ZFolder.View; import com.zimbra.client.ZMailbox; import com.zimbra.client.ZMessage; -import com.zimbra.common.account.Key.AccountBy; import com.zimbra.common.account.ZAttrProvisioning; import com.zimbra.common.calendar.ICalTimeZone; import com.zimbra.common.calendar.ParsedDateTime; @@ -102,26 +107,10 @@ import com.zimbra.soap.account.type.Pref; import com.zimbra.soap.mail.message.CreateMountpointRequest; import com.zimbra.soap.mail.message.CreateMountpointResponse; -import com.zimbra.soap.mail.message.SearchRequest; -import com.zimbra.soap.mail.message.SearchResponse; -import com.zimbra.soap.mail.type.ContactInfo; import com.zimbra.soap.mail.type.NewMountpointSpec; -import com.zimbra.soap.type.SearchHit; - -import org.junit.After; -import org.junit.Before; -import org.junit.BeforeClass; -import org.junit.Test; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.fail; public class TestCalDav { - static final TimeZoneRegistry tzRegistry = TimeZoneRegistryFactory.getInstance().createRegistry(); private static String TEST_NAME = "TestCalDav"; private static String USER_NAME = TEST_NAME + "-user1"; private static String DAV1 = TEST_NAME + "dav1"; @@ -138,6 +127,220 @@ public class TestCalDav { private Account dav4; private Account user1; + private final String[] componentsForBothTasksAndEvents = {"VEVENT", "VTODO", "VFREEBUSY"}; + private final String[] eventComponents = {"VEVENT", "VFREEBUSY"}; + private final String[] todoComponents = {"VTODO", "VFREEBUSY"}; + + private static final Map caldavNSMap; + static { + Map aMap = Maps.newHashMapWithExpectedSize(2); + aMap.put("D", DavElements.WEBDAV_NS_STRING); + aMap.put("C", DavElements.CALDAV_NS_STRING); + aMap.put("CS", DavElements.CS_NS_STRING); + caldavNSMap = Collections.unmodifiableMap(aMap); + } + + public static final String expandPropertyGroupMemberSet = + "" + + "" + + " " + + " " + + " " + + " " + + " " + + ""; + + public static final String expandPropertyDelegateFor = + "" + + " " + + " " + + " " + + " " + + " " + + " " + + " " + + " " + + " " + + " " + + ""; + + public static String propFindSupportedReportSet = + "\n" + + " \n" + + " \n" + + " \n" + + ""; + + public static String propFindEtagResType = "" + + " " + + " " + + " " + + " " + + ""; + + public static String propPatchGroupMemberSetTemplate = + "" + + "" + + " " + + " " + + " " + + " %%MEMBER%%" + + " " + + " " + + " " + + ""; + + public static final String calendar_query_etags_by_vevent = + "\n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + ""; + + public static final String propFindSupportedCalendarComponentSet = + "\n" + + " \n" + + " \n" + + " \n" + + ""; + + private static String androidSeriesMeetingTemplate = + "BEGIN:VCALENDAR\n" + + "VERSION:2.0\n" + + "PRODID:-//dmfs.org//mimedir.icalendar//EN\n" + + "BEGIN:VTIMEZONE\n" + + "TZID:Europe/London\n" + + "X-LIC-LOCATION:Europe/London\n" + + "BEGIN:DAYLIGHT\n" + + "TZOFFSETFROM:+0000\n" + + "TZOFFSETTO:+0100\n" + + "TZNAME:BST\n" + + "DTSTART:19700329T010000\n" + + "RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU\n" + + "END:DAYLIGHT\n" + + "BEGIN:STANDARD\n" + + "TZOFFSETFROM:+0100\n" + + "TZOFFSETTO:+0000\n" + + "TZNAME:GMT\n" + + "DTSTART:19701025T020000\n" + + "RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU\n" + + "END:STANDARD\n" + + "END:VTIMEZONE\n" + + "BEGIN:VEVENT\n" + + "DTSTART;TZID=Europe/London:20141022T190000\n" + + "DESCRIPTION:Giggle\n" + + "SUMMARY:testAndroidMeetingSeries\n" + + "RRULE:FREQ=DAILY;COUNT=15;WKST=MO\n" + + "LOCATION:Egham Leisure Centre\\, Vicarage Road\\, Egham\\, United Kingdom\n" + + "TRANSP:OPAQUE\n" + + "STATUS:CONFIRMED\n" + + "ATTENDEE;PARTSTAT=ACCEPTED;RSVP=TRUE;ROLE=REQ-PARTICIPANT:mailto:%%ORG%%\n" + + "ATTENDEE;PARTSTAT=NEEDS-ACTION;RSVP=TRUE;ROLE=REQ-PARTICIPANT:mailto:%%ATT%%\n" + + "DURATION:PT1H\n" + + "LAST-MODIFIED:20141021T145905Z\n" + + "DTSTAMP:20141021T145905Z\n" + + "ORGANIZER:mailto:%%ORG%%\n" + + "CREATED:20141021T145905Z\n" + + "UID:%%UID%%\n" + + "BEGIN:VALARM\n" + + "TRIGGER;VALUE=DURATION:-PT15M\n" + + "ACTION:DISPLAY\n" + + "DESCRIPTION:Default Event Notification\n" + + "X-WR-ALARMUID:790cd474-6135-4705-b1a0-24d4b4fc3cf5\n" + + "END:VALARM\n" + + "END:VEVENT\n" + + "END:VCALENDAR\n"; + + public String androidSeriesMeetingUid = "6db50587-d283-49a1-9cf4-63aa27406829"; + + private static String VtimeZoneGMT_0600_0500 = + "BEGIN:VCALENDAR\n" + + "BEGIN:VTIMEZONE\n" + + "TZID:GMT-06.00/-05.00\n" + + "BEGIN:STANDARD\n" + + "DTSTART:16010101T010000\n" + + "TZOFFSETTO:-0600\n" + + "TZOFFSETFROM:-0500\n" + + "RRULE:FREQ=YEARLY;INTERVAL=1;BYMONTH=11;BYDAY=1SU;WKST=MO\n" + + "END:STANDARD\n" + + "BEGIN:DAYLIGHT\n" + + "DTSTART:16010101T030000\n" + + "TZOFFSETTO:-0500\n" + + "TZOFFSETFROM:-0600\n" + + "RRULE:FREQ=YEARLY;INTERVAL=1;BYMONTH=3;BYDAY=2SU;WKST=MO\n" + + "END:DAYLIGHT\n" + + "END:VTIMEZONE\n" + + "END:VCALENDAR\n"; + + private static String VtimeZoneGMT_0800_0700 = + "BEGIN:VCALENDAR\n" + + "BEGIN:VTIMEZONE\n" + + "TZID:GMT-08.00/-07.00\n" + + "BEGIN:STANDARD\n" + + "DTSTART:16010101T010000\n" + + "TZOFFSETTO:-0800\n" + + "TZOFFSETFROM:-0700\n" + + "RRULE:FREQ=YEARLY;INTERVAL=1;BYMONTH=11;BYDAY=1SU;WKST=MO\n" + + "END:STANDARD\n" + + "BEGIN:DAYLIGHT\n" + + "DTSTART:16010101T030000\n" + + "TZOFFSETTO:-0700\n" + + "TZOFFSETFROM:-0800\n" + + "RRULE:FREQ=YEARLY;INTERVAL=1;BYMONTH=3;BYDAY=2SU;WKST=MO\n" + + "END:DAYLIGHT\n" + + "END:VTIMEZONE\n" + + "END:VCALENDAR\n"; + + public static String LOTUS_NOTES_WITH_BAD_GMT_TZID = + "BEGIN:VCALENDAR\r\n" + + "X-LOTUS-CHARSET:UTF-8\r\n" + + "VERSION:2.0\r\n" + + "PRODID:-//Lotus Development Corporation//NONSGML Notes 8.5.3//EN_C\r\n" + + "METHOD:REQUEST\r\n" + + "BEGIN:VTIMEZONE\r\n" + + "TZID:GMT\r\n" + + "BEGIN:STANDARD\r\n" + + "DTSTART:19501029T020000\r\n" + + "TZOFFSETFROM:+0100\r\n" + + "TZOFFSETTO:+0000\r\n" + + "RRULE:FREQ=YEARLY;BYMINUTE=0;BYHOUR=2;BYDAY=-1SU;BYMONTH=10\r\n" + + "END:STANDARD\r\n" + + "BEGIN:DAYLIGHT\r\n" + + "DTSTART:19500326T020000\r\n" + + "TZOFFSETFROM:+0000\r\n" + + "TZOFFSETTO:+0100\r\n" + + "RRULE:FREQ=YEARLY;BYMINUTE=0;BYHOUR=2;BYDAY=-1SU;BYMONTH=3\r\n" + + "END:DAYLIGHT\r\n" + + "END:VTIMEZONE\r\n" + + "BEGIN:VEVENT\r\n" + + "DTSTART;TZID=\"GMT\":20150721T140000\r\n" + + "DTEND;TZID=\"GMT\":20150721T150000\r\n" + + "TRANSP:OPAQUE\r\n" + + "DTSTAMP:20150721T072350Z\r\n" + + "SEQUENCE:0\r\n" + + "ATTENDEE;ROLE=CHAIR;PARTSTAT=ACCEPTED;CN=\"Administrator/zimbra\"\r\n" + + " ;RSVP=FALSE:mailto:administrator@example.com\r\n" + + "ATTENDEE;ROLE=REQ-PARTICIPANT;PARTSTAT=NEEDS-ACTION;RSVP=TRUE\r\n" + + " :mailto:fred.flintstone@example.com\r\n" + + "CLASS:PUBLIC\r\n" + + "SUMMARY:new meeting\r\n" + + "ORGANIZER;CN=\"Administrator/zimbra\":mailto:administrator@example.com\r\n" + + "UID:F0197AA9F439EFC888257E890026367E-Lotus_Notes_Generated\r\n" + + "X-LOTUS-BROADCAST:FALSE\r\n" + + "X-LOTUS-UPDATE-SEQ:1\r\n" + + "X-LOTUS-UPDATE-WISL:$S:1;$L:1;$B:1;$R:1;$E:1;$W:1;$O:1;$M:1;RequiredAttendees:1;INetRequiredNames:1;AltRequiredNames:1;StorageRequiredNames:1;OptionalAttendees:1;INetOptionalNames:1;AltOptionalNames:1;StorageOptionalNames:1\r\n" + + "X-LOTUS-NOTESVERSION:2\r\n" + + "X-LOTUS-NOTICETYPE:I\r\n" + + "X-LOTUS-APPTTYPE:3\r\n" + + "X-LOTUS-CHILD-UID:F0197AA9F439EFC888257E890026367E\r\n" + + "END:VEVENT\r\n" + + "END:VCALENDAR\r\n"; + public static class MkColMethod extends EntityEnclosingMethod { @Override public String getName() { @@ -178,15 +381,6 @@ public ReportMethod(String uri) { } } - private static final Map caldavNSMap; - static { - Map aMap = Maps.newHashMapWithExpectedSize(2); - aMap.put("D", DavElements.WEBDAV_NS_STRING); - aMap.put("C", DavElements.CALDAV_NS_STRING); - aMap.put("CS", DavElements.CS_NS_STRING); - caldavNSMap = Collections.unmodifiableMap(aMap); - } - public static class NamespaceContextForXPath implements javax.xml.namespace.NamespaceContext { private final Map nsMap; @@ -197,6 +391,10 @@ public static NamespaceContextForXPath forCalDAV() { return new NamespaceContextForXPath(caldavNSMap); } + public static NamespaceContextForXPath forCardDAV() { + return new NamespaceContextForXPath(TestCardDav.carddavNSMap); + } + @Override public String getNamespaceURI(String prefix) { if (prefix == null) { @@ -297,8 +495,10 @@ public static class HttpMethodExecutor { public String statusLine; public Header[] respHeaders; public byte[] responseBodyBytes; + public final String methodName; public HttpMethodExecutor(HttpClient client, HttpMethod method, int expectedCode) throws IOException { + methodName = method.getName(); try { respCode = HttpClientUtil.executeMethod(client, method); statusCode = method.getStatusCode(); @@ -335,14 +535,48 @@ public static HttpMethodExecutor execute(HttpClient client, HttpMethod method, i return new HttpMethodExecutor(client, method, expectedCode); } - public String getResponseAsString() { - return new String(responseBodyBytes); + public String getHeaderValue(String hdrName) { + for (Header hdr : respHeaders) { + if (hdrName.equals(hdr.getName())) { + return hdr.getValue(); + } + } + return null; + } + + public String getNonNullHeaderValue(String hdrName, String desc) { + String val = getHeaderValue(hdrName); + assertNotNull(String.format("%s:response for method '%s' missing header '%s'", + desc, methodName, hdrName)); + return val; + } + + public String getResponseAsString() throws UnsupportedEncodingException { + return new String(responseBodyBytes, MimeConstants.P_CHARSET_UTF8); + } + + public Document getResponseDoc() { + try { + return W3cDomUtil.parseXMLToDoc(getResponseAsString()); + } catch (XmlParseException | UnsupportedEncodingException e) { + ZimbraLog.test.info("Problem parsing response for method %s", methodName, e); + fail(String.format("Problem parsing response for method %s %s", methodName, e)); + return null; + } + } + + public Document getResponseDoc(String topDocElementName) { + Document doc = getResponseDoc(); + org.w3c.dom.Element docElement = doc.getDocumentElement(); + assertEquals("response doc element node name", topDocElementName, docElement.getLocalName()); + return doc; } } @Test public void testBadBasicAuth() throws Exception { - String calFolderUrl = getFolderUrl(dav1, "Calendar").replaceAll("@", "%40"); + assertNotNull("Test account object", dav1); + String calFolderUrl = getFolderUrl(dav1, "Calendar"); HttpClient client = new HttpClient(); GetMethod method = new GetMethod(calFolderUrl); addBasicAuthHeaderForUser(method, dav1, "badPassword"); @@ -351,6 +585,7 @@ public void testBadBasicAuth() throws Exception { @Test public void testPostToSchedulingOutbox() throws Exception { + assertNotNull("Test account object", dav1); String url = getSchedulingOutboxUrl(dav1, dav1); HttpClient client = new HttpClient(); PostMethod method = new PostMethod(url); @@ -368,6 +603,7 @@ public void testPostToSchedulingOutbox() throws Exception { @Test public void testBadPostToSchedulingOutbox() throws Exception { + assertNotNull("Test account object", dav2); String url = getSchedulingOutboxUrl(dav2, dav2); HttpClient client = new HttpClient(); PostMethod method = new PostMethod(url); @@ -393,74 +629,62 @@ public static void addBasicAuthHeaderForUser(HttpMethod method, Account acct) th addBasicAuthHeaderForUser(method, acct, "test123"); } - public static StringBuilder getLocalServerRoot() throws ServiceException { + public static StringBuilder getLocalServerRoot() { StringBuilder sb = new StringBuilder(); - sb.append(TestUtil.getBaseUrl(localServer)); + try { + sb.append(TestUtil.getBaseUrl(localServer)); + } catch (ServiceException e) { + ZimbraLog.test.error("Problem getting local server root", e); + fail("Problem getting local server root " + e.getMessage()); + } return sb; } - public static String getSchedulingOutboxUrl(Account auth, Account target) throws ServiceException { + public static String getFullUrl(String url) { StringBuilder sb = getLocalServerRoot(); - sb.append(UrlNamespace.getSchedulingOutboxUrl(auth.getName(), target.getName())); - return sb.toString(); + sb.append(url); + return sb.toString().replaceAll(" ", "%20").replaceAll("@", "%40"); } - public static String getSchedulingInboxUrl(Account auth, Account target) throws ServiceException { - StringBuilder sb = getLocalServerRoot(); - sb.append(UrlNamespace.getSchedulingInboxUrl(auth.getName(), target.getName())); - return sb.toString(); + public static String getSchedulingOutboxUrl(Account auth, Account target) { + return getFullUrl(UrlNamespace.getSchedulingOutboxUrl(auth.getName(), target.getName())); } - public static String getFolderUrl(Account auth, String folderName) throws ServiceException { - StringBuilder sb = getLocalServerRoot(); - sb.append(UrlNamespace.getFolderUrl(auth.getName(), folderName)); - return sb.toString(); + public static String getSchedulingInboxUrl(Account auth, Account target) { + return getFullUrl(UrlNamespace.getSchedulingInboxUrl(auth.getName(), target.getName())); } - public static String getPrincipalUrl(Account auth) throws ServiceException { - StringBuilder sb = getLocalServerRoot(); - sb.append(UrlNamespace.getPrincipalUrl(auth)); - return sb.toString(); + public static String getFolderUrl(Account auth, String folderName) { + return getFullUrl(UrlNamespace.getFolderUrl(auth.getName(), folderName)); } - public static String getCalendarProxyReadUrl(Account target) throws ServiceException { - StringBuilder sb = getLocalServerRoot(); - sb.append(UrlNamespace.getCalendarProxyReadUrl(target, target)); - return sb.toString(); + public static String getPrincipalUrl(Account auth) { + return getFullUrl(UrlNamespace.getPrincipalUrl(auth)); } - public static String getCalendarProxyWriteUrl(Account target) throws ServiceException { - StringBuilder sb = getLocalServerRoot(); - sb.append(UrlNamespace.getCalendarProxyWriteUrl(target, target)); - return sb.toString(); + public static String getCalendarProxyReadUrl(Account target) { + return getFullUrl(UrlNamespace.getCalendarProxyReadUrl(target, target)); } - static final String calendar_query_etags_by_vevent = - "\n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - ""; + public static String getCalendarProxyWriteUrl(Account target) { + return getFullUrl(UrlNamespace.getCalendarProxyWriteUrl(target, target)); + } - public static Document calendarQuery(String url, Account acct) throws IOException, XmlParseException { - ReportMethod method = new ReportMethod(url); + public static Document doMethodYieldingMultiStatus(EntityEnclosingMethod method, Account acct, + String body) throws IOException, XmlParseException { addBasicAuthHeaderForUser(method, acct); HttpClient client = new HttpClient(); TestCalDav.HttpMethodExecutor executor; method.addRequestHeader("Content-Type", MimeConstants.CT_TEXT_XML); - method.setRequestEntity(new ByteArrayRequestEntity(calendar_query_etags_by_vevent.getBytes(), + method.setRequestEntity(new ByteArrayRequestEntity(body.getBytes(), MimeConstants.CT_TEXT_XML)); executor = new TestCalDav.HttpMethodExecutor(client, method, HttpStatus.SC_MULTI_STATUS); - String respBody = new String(executor.responseBodyBytes, MimeConstants.P_CHARSET_UTF8); - Document doc = W3cDomUtil.parseXMLToDoc(respBody); - org.w3c.dom.Element docElement = doc.getDocumentElement(); - assertEquals("Report node name", DavElements.P_MULTISTATUS, docElement.getLocalName()); - return doc; + return executor.getResponseDoc(DavElements.P_MULTISTATUS); + } + + public static Document calendarQuery(String url, Account acct) throws IOException, XmlParseException { + ReportMethod method = new ReportMethod(url); + return doMethodYieldingMultiStatus(method, acct, calendar_query_etags_by_vevent); } /** @@ -581,22 +805,18 @@ public void testCalendarQueryOnOutbox() throws Exception { @Test public void testPropFindSupportedReportSetOnInbox() throws Exception { + assertNotNull("Test account object", user1); checkPropFindSupportedReportSet(user1, getSchedulingInboxUrl(user1, user1), UrlNamespace.getSchedulingInboxUrl(user1.getName(), user1.getName())); } @Test public void testPropFindSupportedReportSetOnOutbox() throws Exception { + assertNotNull("Test account object", user1); checkPropFindSupportedReportSet(user1, getSchedulingOutboxUrl(user1, user1), UrlNamespace.getSchedulingOutboxUrl(user1.getName(), user1.getName())); } - String propFindSupportedReportSet = - "\n" + - " \n" + - " \n" + - " \n" + - ""; public void checkPropFindSupportedReportSet(Account user, String fullurl, String shorturl) throws Exception { PropFindMethod method = new PropFindMethod(fullurl); addBasicAuthHeaderForUser(method, user); @@ -660,12 +880,6 @@ private void checkSupportedReportSet(Element respElem, String shorturl) { assertTrue("calendar-query report should be advertised", supportsCalendarQuery); } - final String propFindSupportedCalendarComponentSet = - "\n" + - " \n" + - " \n" + - " \n" + - ""; public void checkPropFindSupportedCalendarComponentSet(Account user, String fullurl, String shorturl, String[] compNames) throws Exception { @@ -673,13 +887,11 @@ public void checkPropFindSupportedCalendarComponentSet(Account user, String full addBasicAuthHeaderForUser(method, user); HttpClient client = new HttpClient(); TestCalDav.HttpMethodExecutor executor; - String respBody; method.addRequestHeader("Content-Type", MimeConstants.CT_TEXT_XML); method.setRequestEntity(new ByteArrayRequestEntity(propFindSupportedCalendarComponentSet.getBytes(), MimeConstants.CT_TEXT_XML)); executor = new TestCalDav.HttpMethodExecutor(client, method, HttpStatus.SC_MULTI_STATUS); - respBody = new String(executor.responseBodyBytes, MimeConstants.P_CHARSET_UTF8); - Document doc = W3cDomUtil.parseXMLToDoc(respBody); + Document doc = executor.getResponseDoc(); XPath xpath = XPathFactory.newInstance().newXPath(); xpath.setNamespaceContext(TestCalDav.NamespaceContextForXPath.forCalDAV()); XPathExpression xPathExpr; @@ -705,30 +917,30 @@ public void checkPropFindSupportedCalendarComponentSet(Account user, String full } } - private final String[] componentsForBothTasksAndEvents = {"VEVENT", "VTODO", "VFREEBUSY"}; - private final String[] eventComponents = {"VEVENT", "VFREEBUSY"}; - private final String[] todoComponents = {"VTODO", "VFREEBUSY"}; - @Test public void testPropFindSupportedCalendarComponentSetOnInbox() throws Exception { + assertNotNull("Test account object", user1); checkPropFindSupportedCalendarComponentSet(user1, getSchedulingInboxUrl(user1, user1), UrlNamespace.getSchedulingInboxUrl(user1.getName(), user1.getName()), componentsForBothTasksAndEvents); } @Test public void testPropFindSupportedCalendarComponentSetOnOutbox() throws Exception { + assertNotNull("Test account object", user1); checkPropFindSupportedCalendarComponentSet(user1, getSchedulingOutboxUrl(user1, user1), UrlNamespace.getSchedulingOutboxUrl(user1.getName(), user1.getName()), componentsForBothTasksAndEvents); } @Test public void testPropFindSupportedCalendarComponentSetOnCalendar() throws Exception { + assertNotNull("Test account object", user1); checkPropFindSupportedCalendarComponentSet(user1, getFolderUrl(user1, "Calendar"), UrlNamespace.getFolderUrl(user1.getName(), "Calendar"), eventComponents); } @Test public void testPropFindSupportedCalendarComponentSetOnTasks() throws Exception { + assertNotNull("Test account object", user1); checkPropFindSupportedCalendarComponentSet(user1, getFolderUrl(user1, "Tasks"), UrlNamespace.getFolderUrl(user1.getName(), "Tasks"), todoComponents); } @@ -780,20 +992,27 @@ public void testCreateUsingClientChosenName() throws ServiceException, IOExcepti Iterator iter = respElem.elementIterator(); boolean hasCalendarHref = false; boolean hasCalItemHref = false; + List hrefs = Lists.newArrayList(); while (iter.hasNext()) { Element child = iter.next(); if (DavElements.P_RESPONSE.equals(child.getName())) { Iterator hrefIter = child.elementIterator(DavElements.P_HREF); while (hrefIter.hasNext()) { Element href = hrefIter.next(); - calFolderUrl.endsWith(href.getText()); - hasCalendarHref = hasCalendarHref || calFolderUrl.endsWith(href.getText()); - hasCalItemHref = hasCalItemHref || url.endsWith(href.getText()); + String hrefText = href.getText(); + hrefs.add(hrefText); + calFolderUrl.endsWith(hrefText); + hasCalendarHref = hasCalendarHref || calFolderUrl.endsWith(hrefText); + hasCalItemHref = hasCalItemHref || url.endsWith(hrefText); } } } - assertTrue("propfind response contained entry for calendar", hasCalendarHref); - assertTrue("propfind response contained entry for calendar entry ", hasCalItemHref); + assertTrue( + String.format("PROPFIND RESPONSE should contain href for '%s' - only contained hrefs:%s", + calFolderUrl, Joiner.on(',').join(hrefs)), hasCalendarHref); + assertTrue( + String.format("PROPFIND RESPONSE should contain href for '%s' - only contained hrefs:%s", + url, Joiner.on(',').join(hrefs)), hasCalItemHref); doDeleteMethod(url, dav1, HttpStatus.SC_NO_CONTENT); } @@ -882,58 +1101,10 @@ public void testCreateModifyDeleteAttendeeModifyAndCancel() throws ServiceExcept doGetMethod(dav2Url, dav2, HttpStatus.SC_NOT_FOUND); } - private static String androidSeriesMeetingTemplate = - "BEGIN:VCALENDAR\n" + - "VERSION:2.0\n" + - "PRODID:-//dmfs.org//mimedir.icalendar//EN\n" + - "BEGIN:VTIMEZONE\n" + - "TZID:Europe/London\n" + - "X-LIC-LOCATION:Europe/London\n" + - "BEGIN:DAYLIGHT\n" + - "TZOFFSETFROM:+0000\n" + - "TZOFFSETTO:+0100\n" + - "TZNAME:BST\n" + - "DTSTART:19700329T010000\n" + - "RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU\n" + - "END:DAYLIGHT\n" + - "BEGIN:STANDARD\n" + - "TZOFFSETFROM:+0100\n" + - "TZOFFSETTO:+0000\n" + - "TZNAME:GMT\n" + - "DTSTART:19701025T020000\n" + - "RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU\n" + - "END:STANDARD\n" + - "END:VTIMEZONE\n" + - "BEGIN:VEVENT\n" + - "DTSTART;TZID=Europe/London:20141022T190000\n" + - "DESCRIPTION:Giggle\n" + - "SUMMARY:testAndroidMeetingSeries\n" + - "RRULE:FREQ=DAILY;COUNT=15;WKST=MO\n" + - "LOCATION:Egham Leisure Centre\\, Vicarage Road\\, Egham\\, United Kingdom\n" + - "TRANSP:OPAQUE\n" + - "STATUS:CONFIRMED\n" + - "ATTENDEE;PARTSTAT=ACCEPTED;RSVP=TRUE;ROLE=REQ-PARTICIPANT:mailto:%%ORG%%\n" + - "ATTENDEE;PARTSTAT=NEEDS-ACTION;RSVP=TRUE;ROLE=REQ-PARTICIPANT:mailto:%%ATT%%\n" + - "DURATION:PT1H\n" + - "LAST-MODIFIED:20141021T145905Z\n" + - "DTSTAMP:20141021T145905Z\n" + - "ORGANIZER:mailto:%%ORG%%\n" + - "CREATED:20141021T145905Z\n" + - "UID:%%UID%%\n" + - "BEGIN:VALARM\n" + - "TRIGGER;VALUE=DURATION:-PT15M\n" + - "ACTION:DISPLAY\n" + - "DESCRIPTION:Default Event Notification\n" + - "X-WR-ALARMUID:790cd474-6135-4705-b1a0-24d4b4fc3cf5\n" + - "END:VALARM\n" + - "END:VEVENT\n" + - "END:VCALENDAR\n"; - public String androidSeriesMeetingUid = "6db50587-d283-49a1-9cf4-63aa27406829"; - @Test public void testAndroidMeetingSeries() throws Exception { - ZMailbox dav2MB = TestUtil.getZMailbox(DAV2); // Force creation of mailbox - shouldn't be needed - String calFolderUrl = getFolderUrl(dav1, "Calendar").replaceAll("@", "%40"); + TestUtil.getZMailbox(DAV2); // Force creation of mailbox - shouldn't be needed + String calFolderUrl = getFolderUrl(dav1, "Calendar"); String url = String.format("%s%s.ics", calFolderUrl, androidSeriesMeetingUid); HttpClient client = new HttpClient(); PutMethod putMethod = new PutMethod(url); @@ -952,13 +1123,7 @@ public void testAndroidMeetingSeries() throws Exception { GetMethod getMethod = new GetMethod(url); addBasicAuthHeaderForUser(getMethod, dav1); HttpMethodExecutor exe = HttpMethodExecutor.execute(client, getMethod, HttpStatus.SC_OK); - String etag = null; - for (Header hdr : exe.respHeaders) { - if (DavProtocol.HEADER_ETAG.equals(hdr.getName())) { - etag = hdr.getValue(); - } - } - assertNotNull("ETag from get", etag); + exe.getNonNullHeaderValue(DavProtocol.HEADER_ETAG, "GET of Calendar item"); // Check that we fail if the etag is wrong putMethod = new PutMethod(url); @@ -1005,13 +1170,6 @@ public void testAndroidMeetingSeries() throws Exception { HttpMethodExecutor.execute(client, deleteMethod, HttpStatus.SC_NO_CONTENT); } - static String propFindEtagResType = "" + - " " + - " " + - " " + - " " + - ""; - public String zvcalendarToString(ZVCalendar vcal) throws IOException { StringWriter calWriter = new StringWriter(); vcal.toICalendar(calWriter); @@ -1082,6 +1240,7 @@ public ZVCalendar simpleMeeting(Account organizer, List attendees, S @Test public void testSimpleMkcol() throws Exception { + assertNotNull("Test account object", dav1); StringBuilder url = getLocalServerRoot(); url.append(DavServlet.DAV_PATH).append("/").append(dav1.getName()).append("/simpleMkcol/"); MkColMethod method = new MkColMethod(url.toString()); @@ -1090,41 +1249,6 @@ public void testSimpleMkcol() throws Exception { HttpMethodExecutor.execute(client, method, HttpStatus.SC_CREATED); } - @Test - public void testMkcol4addressBook() throws Exception { - String xml = "" + - " " + - " " + - " " + - " " + - " " + - " " + - " OtherContacts" + - " Extra Contacts" + - " " + - " " + - ""; - StringBuilder url = getLocalServerRoot(); - url.append(DavServlet.DAV_PATH).append("/").append(dav1.getName()).append("/OtherContacts/"); - MkColMethod method = new MkColMethod(url.toString()); - addBasicAuthHeaderForUser(method, dav1); - HttpClient client = new HttpClient(); - method.addRequestHeader("Content-Type", MimeConstants.CT_TEXT_XML); - method.setRequestEntity(new ByteArrayRequestEntity(xml.getBytes(), MimeConstants.CT_TEXT_XML)); - HttpMethodExecutor.execute(client, method, HttpStatus.SC_MULTI_STATUS); - - ZMailbox.Options options = new ZMailbox.Options(); - options.setAccount(dav1.getName()); - options.setAccountBy(AccountBy.name); - options.setPassword(TestUtil.DEFAULT_PASSWORD); - options.setUri(TestUtil.getSoapUrl()); - options.setNoSession(true); - ZMailbox mbox = ZMailbox.getMailbox(options); - ZFolder folder = mbox.getFolderByPath("/OtherContacts"); - assertEquals("OtherContacts", folder.getName()); - assertEquals("OtherContacts default view", View.contact, folder.getDefaultView()); - } - public String makeFreeBusyRequestIcal(Account organizer, List attendees, Date start, Date end) throws IOException { ZVCalendar vcal = new ZVCalendar(); @@ -1260,27 +1384,8 @@ public void testPropPatchCalendarFreeBusySetSettingUsingEscapedUrls() throws Exc fbResponse, busyTentativeMarker), fbResponse.contains(busyTentativeMarker)); } - private static String VtimeZoneGMT_0600_0500 = - "BEGIN:VCALENDAR\n" + - "BEGIN:VTIMEZONE\n" + - "TZID:GMT-06.00/-05.00\n" + - "BEGIN:STANDARD\n" + - "DTSTART:16010101T010000\n" + - "TZOFFSETTO:-0600\n" + - "TZOFFSETFROM:-0500\n" + - "RRULE:FREQ=YEARLY;INTERVAL=1;BYMONTH=11;BYDAY=1SU;WKST=MO\n" + - "END:STANDARD\n" + - "BEGIN:DAYLIGHT\n" + - "DTSTART:16010101T030000\n" + - "TZOFFSETTO:-0500\n" + - "TZOFFSETFROM:-0600\n" + - "RRULE:FREQ=YEARLY;INTERVAL=1;BYMONTH=3;BYDAY=2SU;WKST=MO\n" + - "END:DAYLIGHT\n" + - "END:VTIMEZONE\n" + - "END:VCALENDAR\n"; - @Test - public void testFuzzyTimeZoneMatchGMT_06() throws Exception { + public void testFuzzyTimeZoneMatchGMT06() throws Exception { try (ByteArrayInputStream bais = new ByteArrayInputStream(VtimeZoneGMT_0600_0500.getBytes())) { ZVCalendar tzcal = ZCalendar.ZCalendarBuilder.build(bais, MimeConstants.P_CHARSET_UTF8); assertNotNull("tzcal", tzcal); @@ -1293,27 +1398,8 @@ public void testFuzzyTimeZoneMatchGMT_06() throws Exception { } } - private static String VtimeZoneGMT_0800_0700 = - "BEGIN:VCALENDAR\n" + - "BEGIN:VTIMEZONE\n" + - "TZID:GMT-08.00/-07.00\n" + - "BEGIN:STANDARD\n" + - "DTSTART:16010101T010000\n" + - "TZOFFSETTO:-0800\n" + - "TZOFFSETFROM:-0700\n" + - "RRULE:FREQ=YEARLY;INTERVAL=1;BYMONTH=11;BYDAY=1SU;WKST=MO\n" + - "END:STANDARD\n" + - "BEGIN:DAYLIGHT\n" + - "DTSTART:16010101T030000\n" + - "TZOFFSETTO:-0700\n" + - "TZOFFSETFROM:-0800\n" + - "RRULE:FREQ=YEARLY;INTERVAL=1;BYMONTH=3;BYDAY=2SU;WKST=MO\n" + - "END:DAYLIGHT\n" + - "END:VTIMEZONE\n" + - "END:VCALENDAR\n"; - @Test - public void testFuzzyTimeZoneMatchGMT_08() throws Exception { + public void testFuzzyTimeZoneMatchGMT08() throws Exception { try (ByteArrayInputStream bais = new ByteArrayInputStream(VtimeZoneGMT_0800_0700.getBytes())) { ZVCalendar tzcal = ZCalendar.ZCalendarBuilder.build(bais, MimeConstants.P_CHARSET_UTF8); assertNotNull("tzcal", tzcal); @@ -1326,51 +1412,6 @@ public void testFuzzyTimeZoneMatchGMT_08() throws Exception { } } - public static String LOTUS_NOTES_WITH_BAD_GMT_TZID = - "BEGIN:VCALENDAR\r\n" + - "X-LOTUS-CHARSET:UTF-8\r\n" + - "VERSION:2.0\r\n" + - "PRODID:-//Lotus Development Corporation//NONSGML Notes 8.5.3//EN_C\r\n" + - "METHOD:REQUEST\r\n" + - "BEGIN:VTIMEZONE\r\n" + - "TZID:GMT\r\n" + - "BEGIN:STANDARD\r\n" + - "DTSTART:19501029T020000\r\n" + - "TZOFFSETFROM:+0100\r\n" + - "TZOFFSETTO:+0000\r\n" + - "RRULE:FREQ=YEARLY;BYMINUTE=0;BYHOUR=2;BYDAY=-1SU;BYMONTH=10\r\n" + - "END:STANDARD\r\n" + - "BEGIN:DAYLIGHT\r\n" + - "DTSTART:19500326T020000\r\n" + - "TZOFFSETFROM:+0000\r\n" + - "TZOFFSETTO:+0100\r\n" + - "RRULE:FREQ=YEARLY;BYMINUTE=0;BYHOUR=2;BYDAY=-1SU;BYMONTH=3\r\n" + - "END:DAYLIGHT\r\n" + - "END:VTIMEZONE\r\n" + - "BEGIN:VEVENT\r\n" + - "DTSTART;TZID=\"GMT\":20150721T140000\r\n" + - "DTEND;TZID=\"GMT\":20150721T150000\r\n" + - "TRANSP:OPAQUE\r\n" + - "DTSTAMP:20150721T072350Z\r\n" + - "SEQUENCE:0\r\n" + - "ATTENDEE;ROLE=CHAIR;PARTSTAT=ACCEPTED;CN=\"Administrator/zimbra\"\r\n" + - " ;RSVP=FALSE:mailto:administrator@example.com\r\n" + - "ATTENDEE;ROLE=REQ-PARTICIPANT;PARTSTAT=NEEDS-ACTION;RSVP=TRUE\r\n" + - " :mailto:fred.flintstone@example.com\r\n" + - "CLASS:PUBLIC\r\n" + - "SUMMARY:new meeting\r\n" + - "ORGANIZER;CN=\"Administrator/zimbra\":mailto:administrator@example.com\r\n" + - "UID:F0197AA9F439EFC888257E890026367E-Lotus_Notes_Generated\r\n" + - "X-LOTUS-BROADCAST:FALSE\r\n" + - "X-LOTUS-UPDATE-SEQ:1\r\n" + - "X-LOTUS-UPDATE-WISL:$S:1;$L:1;$B:1;$R:1;$E:1;$W:1;$O:1;$M:1;RequiredAttendees:1;INetRequiredNames:1;AltRequiredNames:1;StorageRequiredNames:1;OptionalAttendees:1;INetOptionalNames:1;AltOptionalNames:1;StorageOptionalNames:1\r\n" + - "X-LOTUS-NOTESVERSION:2\r\n" + - "X-LOTUS-NOTICETYPE:I\r\n" + - "X-LOTUS-APPTTYPE:3\r\n" + - "X-LOTUS-CHILD-UID:F0197AA9F439EFC888257E890026367E\r\n" + - "END:VEVENT\r\n" + - "END:VCALENDAR\r\n"; - @Test public void testLondonTimeZoneCalledGMTkeepSameName() throws Exception { try (ByteArrayInputStream bais = new ByteArrayInputStream(LOTUS_NOTES_WITH_BAD_GMT_TZID.getBytes())) { @@ -1409,7 +1450,7 @@ private void attendeeDeleteFromCalendar(boolean suppressReply) throws Exception addBasicAuthHeaderForUser(method, dav1); ZMailbox organizer = TestUtil.getZMailbox(DAV2); - ZMailbox dav1MB = TestUtil.getZMailbox(DAV1); // Force creation of mailbox - shouldn't be needed + TestUtil.getZMailbox(DAV1); // Force creation of mailbox - shouldn't be needed String subject = String.format("%s %s", TEST_NAME, suppressReply ? "testInvite which shouldNOT be replied to" : "testInvite to be auto-declined"); Date startDate = new Date(System.currentTimeMillis() + Constants.MILLIS_PER_DAY); @@ -1449,300 +1490,16 @@ private void attendeeDeleteFromCalendar(boolean suppressReply) throws Exception @Test public void testAttendeeAutoDecline() throws Exception { + assertNotNull("Test account object", dav1); attendeeDeleteFromCalendar(false /* suppressReply */); } @Test public void testAttendeeSuppressedAutoDecline() throws Exception { + assertNotNull("Test account object", dav1); attendeeDeleteFromCalendar(true /* suppressReply */); } - public static String simpleVcard = "BEGIN:VCARD\r\n" + - "VERSION:3.0\r\n" + - "FN:TestCal\r\n" + - "N:Dog;Scruffy\r\n" + - "EMAIL;TYPE=INTERNET,PREF:scruffy@example.com\r\n" + - "UID:SCRUFF1\r\n" + - "END:VCARD\r\n"; - - @Test - public void testCreateContactWithIfNoneMatchTesting() throws ServiceException, IOException { - String davBaseName = "SCRUFF1.vcf"; // Based on UID - String contactsFolderUrl = getFolderUrl(dav1, "Contacts"); - String url = String.format("%s%s", contactsFolderUrl, davBaseName); - HttpClient client = new HttpClient(); - PutMethod putMethod = new PutMethod(url); - addBasicAuthHeaderForUser(putMethod, dav1); - putMethod.addRequestHeader("Content-Type", "text/vcard"); - - putMethod.setRequestEntity(new ByteArrayRequestEntity(simpleVcard.getBytes(), MimeConstants.CT_TEXT_VCARD)); - // Bug 84246 this used to fail with 409 Conflict because we used to require an If-None-Match header - HttpMethodExecutor.execute(client, putMethod, HttpStatus.SC_CREATED); - - // Check that trying to put the same thing again when we don't expect it to exist (i.e. Using If-None-Match - // header) will fail. - putMethod = new PutMethod(url); - addBasicAuthHeaderForUser(putMethod, dav1); - putMethod.addRequestHeader("Content-Type", "text/vcard"); - putMethod.addRequestHeader(DavProtocol.HEADER_IF_NONE_MATCH, "*"); - putMethod.setRequestEntity(new ByteArrayRequestEntity(simpleVcard.getBytes(), MimeConstants.CT_TEXT_VCARD)); - HttpMethodExecutor.execute(client, putMethod, HttpStatus.SC_PRECONDITION_FAILED); - } - - private static String rachelVcard = - "BEGIN:VCARD\n" + - "VERSION:3.0\n" + - "PRODID:-//BusyMac LLC//BusyContacts 1.0.2//EN\n" + - "FN:La Rochelle\n" + - "N:Rochelle;La;;;\n" + - "EMAIL;TYPE=internet:rachel@fun.org\n" + - "CATEGORIES:BlueGroup\n" + - "REV:2015-04-04T13:55:56Z\n" + - "UID:07139DE2-EA7B-46CB-A970-C4DF7F72D9AE\n" + - "X-BUSYMAC-MODIFIED-BY:Gren Elliot\n" + - "X-CREATED:2015-04-04T13:55:25Z\n" + - "END:VCARD\n"; - - private static String blueGroupCreate = - "BEGIN:VCARD\n" + - "VERSION:3.0\n" + - "PRODID:-//BusyMac LLC//BusyContacts 1.0.2//EN\n" + - "FN:BlueGroup\n" + - "N:BlueGroup\n" + - "REV:2015-04-04T13:55:56Z\n" + - "UID:F53A6F96-566F-46CC-8D48-A5263FAB5E38\n" + - "X-ADDRESSBOOKSERVER-KIND:group\n" + - "X-ADDRESSBOOKSERVER-MEMBER:urn:uuid:07139DE2-EA7B-46CB-A970-C4DF7F72D9AE\n" + - "END:VCARD\n"; - - private static String parisVcard = - "BEGIN:VCARD\n" + - "VERSION:3.0\n" + - "FN:Paris Match\n" + - "N:Match;Paris;;;\n" + - "EMAIL;TYPE=internet:match@paris.fr\n" + - "CATEGORIES:BlueGroup\n" + - "REV:2015-04-04T13:56:50Z\n" + - "UID:BE43F16D-336E-4C3E-BAE6-22B8F245A986\n" + - "END:VCARD\n"; - - private static String blueGroupModify = - "BEGIN:VCARD\n" + - "VERSION:3.0\n" + - "PRODID:-//BusyMac LLC//BusyContacts 1.0.2//EN\n" + - "FN:BlueGroup\n" + - "N:BlueGroup\n" + - "REV:2015-04-04T13:56:50Z\n" + - "UID:F53A6F96-566F-46CC-8D48-A5263FAB5E38\n" + - "X-ADDRESSBOOKSERVER-KIND:group\n" + - "X-ADDRESSBOOKSERVER-MEMBER:urn:uuid:BE43F16D-336E-4C3E-BAE6-22B8F245A986\n" + - "X-ADDRESSBOOKSERVER-MEMBER:urn:uuid:07139DE2-EA7B-46CB-A970-C4DF7F72D9AE\n" + - "END:VCARD\n"; - - @Test - public void testAppleStyleGroup() throws ServiceException, IOException { - String contactsFolderUrl = getFolderUrl(dav1, "Contacts"); - HttpClient client = new HttpClient(); - - PostMethod postMethod = new PostMethod(contactsFolderUrl); - addBasicAuthHeaderForUser(postMethod, dav1); - postMethod.addRequestHeader("Content-Type", "text/vcard"); - postMethod.setRequestEntity(new ByteArrayRequestEntity(rachelVcard.getBytes(), MimeConstants.CT_TEXT_VCARD)); - HttpMethodExecutor.execute(client, postMethod, HttpStatus.SC_CREATED); - - postMethod = new PostMethod(contactsFolderUrl); - addBasicAuthHeaderForUser(postMethod, dav1); - postMethod.addRequestHeader("Content-Type", "text/vcard"); - postMethod.setRequestEntity(new ByteArrayRequestEntity(blueGroupCreate.getBytes(), MimeConstants.CT_TEXT_VCARD)); - HttpMethodExecutor exe = HttpMethodExecutor.execute(client, postMethod, HttpStatus.SC_CREATED); - String groupLocation = null; - for (Header hdr : exe.respHeaders) { - if ("Location".equals(hdr.getName())) { - groupLocation = hdr.getValue(); - } - } - assertNotNull("Location Header returned when creating Group", groupLocation); - - postMethod = new PostMethod(contactsFolderUrl); - addBasicAuthHeaderForUser(postMethod, dav1); - postMethod.addRequestHeader("Content-Type", "text/vcard"); - postMethod.setRequestEntity(new ByteArrayRequestEntity(parisVcard.getBytes(), MimeConstants.CT_TEXT_VCARD)); - HttpMethodExecutor.execute(client, postMethod, HttpStatus.SC_CREATED); - - String url = String.format("%s%s", contactsFolderUrl, "F53A6F96-566F-46CC-8D48-A5263FAB5E38.vcf"); - PutMethod putMethod = new PutMethod(url); - addBasicAuthHeaderForUser(putMethod, dav1); - putMethod.addRequestHeader("Content-Type", "text/vcard"); - putMethod.setRequestEntity(new ByteArrayRequestEntity(blueGroupModify.getBytes(), MimeConstants.CT_TEXT_VCARD)); - HttpMethodExecutor.execute(client, putMethod, HttpStatus.SC_NO_CONTENT); - - GetMethod getMethod = new GetMethod(url); - addBasicAuthHeaderForUser(getMethod, dav1); - getMethod.addRequestHeader("Content-Type", "text/vcard"); - exe = HttpMethodExecutor.execute(client, getMethod, HttpStatus.SC_OK); - String respBody = new String(exe.responseBodyBytes, MimeConstants.P_CHARSET_UTF8); - String [] expecteds = { - "X-ADDRESSBOOKSERVER-KIND:group", - "X-ADDRESSBOOKSERVER-MEMBER:urn:uuid:BE43F16D-336E-4C3E-BAE6-22B8F245A986", - "X-ADDRESSBOOKSERVER-MEMBER:urn:uuid:07139DE2-EA7B-46CB-A970-C4DF7F72D9AE" }; - for (String expected : expecteds) { - assertTrue(String.format("GET should contain '%s'\nBODY=%s", expected, respBody), - respBody.contains(expected)); - } - - // members are actually stored in a different way. Make sure it isn't a fluke - // that the GET response contained the correct members by checking that the members - // appear where expected in a search hit. - SearchRequest searchRequest = new SearchRequest(); - searchRequest.setSortBy("dateDesc"); - searchRequest.setLimit(8); - searchRequest.setSearchTypes("contact"); - searchRequest.setQuery("in:Contacts"); - ZMailbox mbox = TestUtil.getZMailbox(DAV1); - SearchResponse searchResp = mbox.invokeJaxb(searchRequest); - assertNotNull("JAXB SearchResponse object", searchResp); - List hits = searchResp.getSearchHits(); - assertNotNull("JAXB SearchResponse hits", hits); - assertEquals("JAXB SearchResponse hits", 3, hits.size()); - boolean seenGroup = false; - for (SearchHit hit : hits) { - ContactInfo contactInfo = (ContactInfo) hit; - if ("BlueGroup".equals(contactInfo.getFileAs())) { - seenGroup = true; - assertEquals("Number of members of group in search hit", 2, contactInfo.getContactGroupMembers().size()); - } - ZimbraLog.test.info("Hit %s class=%s", hit, hit.getClass().getName()); - } - assertTrue("Seen group", seenGroup); - } - - private static String smallBusyMacAttach = - "BEGIN:VCARD\r\n" + - "VERSION:3.0\r\n" + - "PRODID:-//BusyMac LLC//BusyContacts 1.0.2//EN\r\n" + - "FN:John Smith\r\n" + - "N:Smith;John;;;\r\n" + - "REV:2015-04-05T09:51:09Z\r\n" + - "UID:99E01E16-03B3-4487-AAEF-AEB496852C06\r\n" + - "X-BUSYMAC-ATTACH;ENCODING=b;X-FILENAME=favicon.ico:AAABAAEAEBAAAAEAIABoBAAA\r\n" + - " FgAAACgAAAAQAAAAIAAAAAEAIAAAAAAAQAQAABMLAAATCwAAAAAAAAAAAAAAAAAAw4cAY8OHAM\r\n" + - " nDhwD8w4cA/8OHAP/DhwD/w4cA/8OHAP/DhwD/w4cA/8OHAP/DhwD8w4cAycOHAGMAAAAAw4cA\r\n" + - " Y8OHAP/DhwD/w4cA/8OHAP/DhwD/w4cA/8OHAP/DhwD/w4cA/8OHAP/DhwD/w4cA/8OHAP/Dhw\r\n" + - " D/w4cAY8OHAMnDhwD/w4cA/7yYSv/y5Mb/8uXH//Llx//z5sr/8+bK//Pmyv/z58v/8+bK/8qq\r\n" + - " Y//DhwD/w4cA/8OHAMnDhwDhw4cA/8OHAP++q4D///////////////7////+//////////////\r\n" + - " /////////Yyan/w4cA/8OHAP/DhwDhw4cA4cOHAP/DhwD/t4QR/9/azv//////5t3K/9StVv/b\r\n" + - " t2b/27dm/9u3Z//cuGn/wpAh/8OHAP/DhwD/w4cA4cOHAOHDhwD/w4cA/8OHAP+2jzr/+fj2//\r\n" + - " n49f/BnU7/w4cA/8OHAP/DhwD/w4cA/8OHAP/DhwD/w4cA/8OHAOHDhwDhw4cA/8OHAP/DhwD/\r\n" + - " w4cA/7ihbf//////8u/p/8GRJv/DhwD/w4cA/8OHAP/DhwD/w4cA/8OHAP/DhwDhw4cA4cOHAP\r\n" + - " /DhwD/w4cA/8OHAP/BhgP/0siz///////d1L//wYgI/8OHAP/DhwD/w4cA/8OHAP/DhwD/w4cA\r\n" + - " 4cOHAOHDhwD/w4cA/8OHAP/DhwD/w4cA/7eIIP/n49v//////8e0iP/DhwD/w4cA/8OHAP/Dhw\r\n" + - " D/w4cA/8OHAOHDhwDhw4cA/8OHAP/DhwD/w4cA/8OHAP/DhwD/rItA//39/P/6+vj/w6BQ/8OH\r\n" + - " AP/DhwD/w4cA/8OHAP/DhwDhw4cA4cOHAP/DhwD/w4cA/8OHAP/DhwD/w4cA/8OHAP+8p3r//v\r\n" + - " 79/+3p4v+8ix3/w4cA/8OHAP/DhwD/w4cA4cOHAOHDhwD/w4cA/8CHB//VsFz/3rxx/926bf/c\r\n" + - " uWv/xadh//Ht5///////1suz/7+HCv/DhwD/w4cA/8OHAOHDhwDhw4cA/8OHAP+wjT//+/r5//\r\n" + - " /////////////////////+/v7///////7+/v+8n17/w4cA/8OHAP/DhwDhw4cAycOHAP/DhwD/\r\n" + - " t4gd/+bYuP/16tP/9OjP//Toz//06M//8+fN//Pozv/t4MH/vZIx/8OHAP/DhwD/w4cAycOHAG\r\n" + - " DDhwD/w4cA/8OHAP/DhwD/w4cA/8OHAP/DhwD/w4cA/8OHAP/DhwD/w4cA/8OHAP/DhwD/w4cA\r\n" + - " /8OHAGAAAAAAw4cAWsOHAMnDhwD8w4cA/8OHAP/DhwD/w4cA/8OHAP/DhwD/w4cA/8OHAP/Dhw\r\n" + - " D8w4cAycOHAFoAAAAAgAEAAAAAAAAAAAAAAABoQAAAAAAAAPC/AAAAAAAAAAAAAAAAAAAiQAAA\r\n" + - " AAAAAAAAAAAAAAAAAAAAAAAAgAEAAA==\r\n" + - "X-BUSYMAC-MODIFIED-BY:Gren Elliot\r\n" + - "X-CUSTOM:one two three four five six seven eight nine ten eleven twelve thirteen fourteen fifteen\r\n" + - "X-CUSTOM:Here are my simple\\nmultiline\\nnotes\r\n" + - "X-CUSTOM;TYPE=pref:semi-colon\\;seperated\\;\"stuff\"\\;here\r\n" + - "X-CUSTOM:comma\\,\"stuff\"\\,'there'\\,too\r\n" + - "X-HOBBY:my backslash\\\\ hobbies\r\n" + - "X-CREATED:2015-04-05T09:50:44Z\r\n" + - "END:VCARD\r\n"; - - @Test - public void testXBusyMacAttach() throws ServiceException, IOException { - String contactsFolderUrl = getFolderUrl(dav1, "Contacts"); - HttpClient client = new HttpClient(); - - PostMethod postMethod = new PostMethod(contactsFolderUrl); - addBasicAuthHeaderForUser(postMethod, dav1); - postMethod.addRequestHeader("Content-Type", "text/vcard"); - postMethod.setRequestEntity(new ByteArrayRequestEntity(smallBusyMacAttach.getBytes(), - MimeConstants.CT_TEXT_VCARD)); - HttpMethodExecutor exe = HttpMethodExecutor.execute(client, postMethod, HttpStatus.SC_CREATED); - String location = null; - for (Header hdr : exe.respHeaders) { - if ("Location".equals(hdr.getName())) { - location = hdr.getValue(); - } - } - assertNotNull("Location Header returned when creating", location); - String url = String.format("%s%s", contactsFolderUrl, location.substring(location.lastIndexOf('/') + 1)); - GetMethod getMethod = new GetMethod(url); - addBasicAuthHeaderForUser(getMethod, dav1); - getMethod.addRequestHeader("Content-Type", "text/vcard"); - exe = HttpMethodExecutor.execute(client, getMethod, HttpStatus.SC_OK); - String respBody = new String(exe.responseBodyBytes, MimeConstants.P_CHARSET_UTF8); - String [] expecteds = { - "\r\nX-BUSYMAC-ATTACH;X-FILENAME=favicon.ico;ENCODING=B:AAABAAEAEBAAAAEAIABoBA\r\n", - "\r\n AAFgAAACgAAAAQAAAAIAAAAAEAIAAAAAAAQAQAABMLAAATCwAAAAAAAAAAAAAAAAAAw4cAY8\r\n", - "\r\nX-BUSYMAC-MODIFIED-BY:Gren Elliot\r\n", - "\r\nX-CUSTOM:one two three four five six seven eight nine ten eleven twelve t\r\n hirteen fourteen fifteen", - "\r\nX-CUSTOM:Here are my simple\\Nmultiline\\Nnotes\r\n", - "\r\nX-CUSTOM;TYPE=pref:semi-colon\\;seperated\\;\"stuff\"\\;here\r\n", - "\r\nX-CUSTOM:comma\\,\"stuff\"\\,'there'\\,too\r\n", - "\r\nX-HOBBY:my backslash\\\\ hobbies\r\n", - "\r\nX-CREATED:2015-04-05T09:50:44Z\r\n" }; - for (String expected : expecteds) { - assertTrue(String.format("GET should contain '%s'\nBODY=%s", expected, respBody), - respBody.contains(expected)); - } - - SearchRequest searchRequest = new SearchRequest(); - searchRequest.setSortBy("dateDesc"); - searchRequest.setLimit(8); - searchRequest.setSearchTypes("contact"); - searchRequest.setQuery("in:Contacts"); - ZMailbox mbox = TestUtil.getZMailbox(DAV1); - SearchResponse searchResp = mbox.invokeJaxb(searchRequest); - assertNotNull("JAXB SearchResponse object", searchResp); - List hits = searchResp.getSearchHits(); - assertNotNull("JAXB SearchResponse hits", hits); - assertEquals("JAXB SearchResponse hits", 1, hits.size()); - } - - public static final String expandPropertyGroupMemberSet = - "" + - "" + - " " + - " " + - " " + - " " + - " " + - ""; - - public static final String expandPropertyDelegateFor = - "" + - " " + - " " + - " " + - " " + - " " + - " " + - " " + - " " + - " " + - " " + - ""; - - public static String propPatchGroupMemberSetTemplate = - "" + - "" + - " " + - " " + - " " + - " %%MEMBER%%" + - " " + - " " + - " " + - ""; - private void setZimbraPrefAppleIcalDelegationEnabled(ZMailbox mbox, Boolean val) throws ServiceException { ModifyPrefsRequest modPrefsReq = new ModifyPrefsRequest(); @@ -1817,18 +1574,18 @@ public void testAppleCaldavProxyFunctions() throws ServiceException, IOException // Check that proxy read has sharee2 in it doc = groupMemberSetExpandProperty(sharer, sharee2, false); String davBaseName = "notAllowed@There"; - String url = String.format("%s%s", - getFolderUrl(sharee1, "Shared Calendar").replaceAll(" ", "%20").replaceAll("@", "%40"), davBaseName); - HttpMethodExecutor exe = doIcalPut(url, sharee1, simpleEvent(sharer), HttpStatus.SC_MOVED_TEMPORARILY); - String location = null; - for (Header hdr : exe.respHeaders) { - if ("Location".equals(hdr.getName())) { - location = hdr.getValue(); - } + String url = String.format("%s%s", getFolderUrl(sharee1, "Shared Calendar"), davBaseName); + HttpMethodExecutor exe; + if (DebugConfig.enableDAVclientCanChooseResourceBaseName) { + exe = doIcalPut(url, sharee1, simpleEvent(sharer), HttpStatus.SC_CREATED); + return; // rest of test deals with how redirecting to new name works - not needed here + } else { + exe = doIcalPut(url, sharee1, simpleEvent(sharer), HttpStatus.SC_MOVED_TEMPORARILY); } - assertNotNull("Location Header not returned when creating", location); + String location = + exe.getNonNullHeaderValue("Location", "When creating in shared calendar"); url = String.format("%s%s", - getFolderUrl(sharee1, "Shared Calendar").replaceAll(" ", "%20").replaceAll("@", "%40"), + getFolderUrl(sharee1, "Shared Calendar"), location.substring(location.lastIndexOf('/') + 1)); doIcalPut(url, sharee1, simpleEvent(sharer), HttpStatus.SC_CREATED); } @@ -1847,10 +1604,7 @@ public static Document groupMemberSetExpandProperty(Account acct, Account member method.setRequestEntity( new ByteArrayRequestEntity(TestCalDav.expandPropertyGroupMemberSet.getBytes(), MimeConstants.CT_TEXT_XML)); executor = new TestCalDav.HttpMethodExecutor(client, method, HttpStatus.SC_MULTI_STATUS); - String respBody = new String(executor.responseBodyBytes, MimeConstants.P_CHARSET_UTF8); - Document doc = W3cDomUtil.parseXMLToDoc(respBody); - org.w3c.dom.Element docElement = doc.getDocumentElement(); - assertEquals("Report node name", DavElements.P_MULTISTATUS, docElement.getLocalName()); + Document doc = executor.getResponseDoc(DavElements.P_MULTISTATUS); XPath xpath = XPathFactory.newInstance().newXPath(); xpath.setNamespaceContext(TestCalDav.NamespaceContextForXPath.forCalDAV()); XPathExpression xPathExpr; @@ -1880,11 +1634,7 @@ public static Document delegateForExpandProperty(Account acct) method.setRequestEntity( new ByteArrayRequestEntity(expandPropertyDelegateFor.getBytes(), MimeConstants.CT_TEXT_XML)); executor = new TestCalDav.HttpMethodExecutor(client, method, HttpStatus.SC_MULTI_STATUS); - String respBody = new String(executor.responseBodyBytes, MimeConstants.P_CHARSET_UTF8); - Document doc = W3cDomUtil.parseXMLToDoc(respBody); - org.w3c.dom.Element docElement = doc.getDocumentElement(); - assertEquals("Report node name", DavElements.P_MULTISTATUS, docElement.getLocalName()); - return doc; + return executor.getResponseDoc(DavElements.P_MULTISTATUS); } public static CreateMountpointResponse createCalendarMountPoint(ZMailbox mboxSharee, Account sharer) @@ -1916,11 +1666,7 @@ public static Document setGroupMemberSet(String url, Account acct, Account membe method.setRequestEntity( new ByteArrayRequestEntity(body.getBytes(), MimeConstants.CT_TEXT_XML)); executor = new TestCalDav.HttpMethodExecutor(client, method, HttpStatus.SC_MULTI_STATUS); - String respBody = new String(executor.responseBodyBytes, MimeConstants.P_CHARSET_UTF8); - Document doc = W3cDomUtil.parseXMLToDoc(respBody); - org.w3c.dom.Element docElement = doc.getDocumentElement(); - assertEquals("Report node name", DavElements.P_MULTISTATUS, docElement.getLocalName()); - return doc; + return executor.getResponseDoc( DavElements.P_MULTISTATUS); } @BeforeClass @@ -1950,21 +1696,11 @@ public void tearDown() throws Exception { } private void cleanUp() throws Exception { - if(TestUtil.accountExists(DAV1)) { - TestUtil.deleteAccount(DAV1); - } - if(TestUtil.accountExists(DAV2)) { - TestUtil.deleteAccount(DAV2); - } - if(TestUtil.accountExists(DAV3)) { - TestUtil.deleteAccount(DAV3); - } - if(TestUtil.accountExists(DAV4)) { - TestUtil.deleteAccount(DAV4); - } - if(TestUtil.accountExists(USER_NAME)) { - TestUtil.deleteAccount(USER_NAME); - } + TestUtil.deleteAccountIfExists(DAV1); + TestUtil.deleteAccountIfExists(DAV2); + TestUtil.deleteAccountIfExists(DAV3); + TestUtil.deleteAccountIfExists(DAV4); + TestUtil.deleteAccountIfExists(USER_NAME); try { TestUtil.deleteDistributionList(DL1); } catch (Exception e) { diff --git a/store/src/java/com/zimbra/qa/unittest/TestCardDav.java b/store/src/java/com/zimbra/qa/unittest/TestCardDav.java new file mode 100644 index 00000000000..0d0600e5544 --- /dev/null +++ b/store/src/java/com/zimbra/qa/unittest/TestCardDav.java @@ -0,0 +1,525 @@ +/* + * ***** BEGIN LICENSE BLOCK ***** + * Zimbra Collaboration Suite Server + * Copyright (C) 2017 Synacor, Inc. + * + * This program is free software: you can redistribute it and/or modify it under + * the terms of the GNU General Public License as published by the Free Software Foundation, + * version 2 of the License. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * You should have received a copy of the GNU General Public License along with this program. + * If not, see . + * ***** END LICENSE BLOCK ***** + */ +package com.zimbra.qa.unittest; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import java.io.IOException; +import java.util.Collections; +import java.util.List; +import java.util.Map; + +import javax.xml.xpath.XPath; +import javax.xml.xpath.XPathConstants; +import javax.xml.xpath.XPathExpression; +import javax.xml.xpath.XPathExpressionException; +import javax.xml.xpath.XPathFactory; + +import org.apache.commons.httpclient.HttpClient; +import org.apache.commons.httpclient.HttpStatus; +import org.apache.commons.httpclient.methods.ByteArrayRequestEntity; +import org.apache.commons.httpclient.methods.GetMethod; +import org.apache.commons.httpclient.methods.PostMethod; +import org.apache.commons.httpclient.methods.PutMethod; +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TestName; +import org.w3c.dom.Document; +import org.w3c.dom.NodeList; + +import com.google.common.collect.Maps; +import com.zimbra.client.ZFolder; +import com.zimbra.client.ZFolder.View; +import com.zimbra.client.ZMailbox; +import com.zimbra.common.account.Key.AccountBy; +import com.zimbra.common.mime.MimeConstants; +import com.zimbra.common.service.ServiceException; +import com.zimbra.common.soap.XmlParseException; +import com.zimbra.common.util.ZimbraLog; +import com.zimbra.cs.account.Account; +import com.zimbra.cs.dav.DavElements; +import com.zimbra.cs.dav.DavProtocol; +import com.zimbra.cs.dav.resource.UrlNamespace; +import com.zimbra.cs.dav.service.DavServlet; +import com.zimbra.qa.unittest.TestCalDav.HttpMethodExecutor; +import com.zimbra.qa.unittest.TestCalDav.MkColMethod; +import com.zimbra.soap.mail.message.SearchRequest; +import com.zimbra.soap.mail.message.SearchResponse; +import com.zimbra.soap.mail.type.ContactInfo; +import com.zimbra.soap.type.SearchHit; + +public class TestCardDav { + + @Rule + public TestName testInfo = new TestName(); + private static String DAV1; + private static String DAV2; + + private Account dav1; + + public static final Map carddavNSMap; + static { + Map aMap = Maps.newHashMapWithExpectedSize(2); + aMap.put("D", DavElements.WEBDAV_NS_STRING); + aMap.put("C", DavElements.CARDDAV_NS_STRING); + aMap.put("CS", DavElements.CS_NS_STRING); + aMap.put("A", DavElements.APPLE_NS_STRING); + aMap.put("Y", DavElements.YAHOO_NS_STRING); + carddavNSMap = Collections.unmodifiableMap(aMap); + } + + private static String rachelVcard = + "BEGIN:VCARD\n" + + "VERSION:3.0\n" + + "PRODID:-//BusyMac LLC//BusyContacts 1.0.2//EN\n" + + "FN:La Rochelle\n" + + "N:Rochelle;La;;;\n" + + "EMAIL;TYPE=internet:rachel@fun.org\n" + + "CATEGORIES:BlueGroup\n" + + "REV:2015-04-04T13:55:56Z\n" + + "UID:07139DE2-EA7B-46CB-A970-C4DF7F72D9AE\n" + + "X-BUSYMAC-MODIFIED-BY:Gren Elliot\n" + + "X-CREATED:2015-04-04T13:55:25Z\n" + + "END:VCARD\n"; + + private static String blueGroupCreate = + "BEGIN:VCARD\n" + + "VERSION:3.0\n" + + "PRODID:-//BusyMac LLC//BusyContacts 1.0.2//EN\n" + + "FN:BlueGroup\n" + + "N:BlueGroup\n" + + "REV:2015-04-04T13:55:56Z\n" + + "UID:F53A6F96-566F-46CC-8D48-A5263FAB5E38\n" + + "X-ADDRESSBOOKSERVER-KIND:group\n" + + "X-ADDRESSBOOKSERVER-MEMBER:urn:uuid:07139DE2-EA7B-46CB-A970-C4DF7F72D9AE\n" + + "END:VCARD\n"; + + private static String parisVcard = + "BEGIN:VCARD\n" + + "VERSION:3.0\n" + + "FN:Paris Match\n" + + "N:Match;Paris;;;\n" + + "EMAIL;TYPE=internet:match@paris.fr\n" + + "CATEGORIES:BlueGroup\n" + + "REV:2015-04-04T13:56:50Z\n" + + "UID:BE43F16D-336E-4C3E-BAE6-22B8F245A986\n" + + "END:VCARD\n"; + + private static String blueGroupModify = + "BEGIN:VCARD\n" + + "VERSION:3.0\n" + + "PRODID:-//BusyMac LLC//BusyContacts 1.0.2//EN\n" + + "FN:BlueGroup\n" + + "N:BlueGroup\n" + + "REV:2015-04-04T13:56:50Z\n" + + "UID:F53A6F96-566F-46CC-8D48-A5263FAB5E38\n" + + "X-ADDRESSBOOKSERVER-KIND:group\n" + + "X-ADDRESSBOOKSERVER-MEMBER:urn:uuid:BE43F16D-336E-4C3E-BAE6-22B8F245A986\n" + + "X-ADDRESSBOOKSERVER-MEMBER:urn:uuid:07139DE2-EA7B-46CB-A970-C4DF7F72D9AE\n" + + "END:VCARD\n"; + + public static String simpleVcard = "BEGIN:VCARD\r\n" + + "VERSION:3.0\r\n" + + "FN:TestCal\r\n" + + "N:Dog;Scruffy\r\n" + + "EMAIL;TYPE=INTERNET,PREF:scruffy@example.com\r\n" + + "UID:SCRUFF1\r\n" + + "END:VCARD\r\n"; + + private static String smallBusyMacAttach = + "BEGIN:VCARD\r\n" + + "VERSION:3.0\r\n" + + "PRODID:-//BusyMac LLC//BusyContacts 1.0.2//EN\r\n" + + "FN:John Smith\r\n" + + "N:Smith;John;;;\r\n" + + "REV:2015-04-05T09:51:09Z\r\n" + + "UID:99E01E16-03B3-4487-AAEF-AEB496852C06\r\n" + + "X-BUSYMAC-ATTACH;ENCODING=b;X-FILENAME=favicon.ico:AAABAAEAEBAAAAEAIABoBAAA\r\n" + + " FgAAACgAAAAQAAAAIAAAAAEAIAAAAAAAQAQAABMLAAATCwAAAAAAAAAAAAAAAAAAw4cAY8OHAM\r\n" + + " nDhwD8w4cA/8OHAP/DhwD/w4cA/8OHAP/DhwD/w4cA/8OHAP/DhwD8w4cAycOHAGMAAAAAw4cA\r\n" + + " Y8OHAP/DhwD/w4cA/8OHAP/DhwD/w4cA/8OHAP/DhwD/w4cA/8OHAP/DhwD/w4cA/8OHAP/Dhw\r\n" + + " D/w4cAY8OHAMnDhwD/w4cA/7yYSv/y5Mb/8uXH//Llx//z5sr/8+bK//Pmyv/z58v/8+bK/8qq\r\n" + + " Y//DhwD/w4cA/8OHAMnDhwDhw4cA/8OHAP++q4D///////////////7////+//////////////\r\n" + + " /////////Yyan/w4cA/8OHAP/DhwDhw4cA4cOHAP/DhwD/t4QR/9/azv//////5t3K/9StVv/b\r\n" + + " t2b/27dm/9u3Z//cuGn/wpAh/8OHAP/DhwD/w4cA4cOHAOHDhwD/w4cA/8OHAP+2jzr/+fj2//\r\n" + + " n49f/BnU7/w4cA/8OHAP/DhwD/w4cA/8OHAP/DhwD/w4cA/8OHAOHDhwDhw4cA/8OHAP/DhwD/\r\n" + + " w4cA/7ihbf//////8u/p/8GRJv/DhwD/w4cA/8OHAP/DhwD/w4cA/8OHAP/DhwDhw4cA4cOHAP\r\n" + + " /DhwD/w4cA/8OHAP/BhgP/0siz///////d1L//wYgI/8OHAP/DhwD/w4cA/8OHAP/DhwD/w4cA\r\n" + + " 4cOHAOHDhwD/w4cA/8OHAP/DhwD/w4cA/7eIIP/n49v//////8e0iP/DhwD/w4cA/8OHAP/Dhw\r\n" + + " D/w4cA/8OHAOHDhwDhw4cA/8OHAP/DhwD/w4cA/8OHAP/DhwD/rItA//39/P/6+vj/w6BQ/8OH\r\n" + + " AP/DhwD/w4cA/8OHAP/DhwDhw4cA4cOHAP/DhwD/w4cA/8OHAP/DhwD/w4cA/8OHAP+8p3r//v\r\n" + + " 79/+3p4v+8ix3/w4cA/8OHAP/DhwD/w4cA4cOHAOHDhwD/w4cA/8CHB//VsFz/3rxx/926bf/c\r\n" + + " uWv/xadh//Ht5///////1suz/7+HCv/DhwD/w4cA/8OHAOHDhwDhw4cA/8OHAP+wjT//+/r5//\r\n" + + " /////////////////////+/v7///////7+/v+8n17/w4cA/8OHAP/DhwDhw4cAycOHAP/DhwD/\r\n" + + " t4gd/+bYuP/16tP/9OjP//Toz//06M//8+fN//Pozv/t4MH/vZIx/8OHAP/DhwD/w4cAycOHAG\r\n" + + " DDhwD/w4cA/8OHAP/DhwD/w4cA/8OHAP/DhwD/w4cA/8OHAP/DhwD/w4cA/8OHAP/DhwD/w4cA\r\n" + + " /8OHAGAAAAAAw4cAWsOHAMnDhwD8w4cA/8OHAP/DhwD/w4cA/8OHAP/DhwD/w4cA/8OHAP/Dhw\r\n" + + " D8w4cAycOHAFoAAAAAgAEAAAAAAAAAAAAAAABoQAAAAAAAAPC/AAAAAAAAAAAAAAAAAAAiQAAA\r\n" + + " AAAAAAAAAAAAAAAAAAAAAAAAgAEAAA==\r\n" + + "X-BUSYMAC-MODIFIED-BY:Gren Elliot\r\n" + + "X-CUSTOM:one two three four five six seven eight nine ten eleven twelve thirteen fourteen fifteen\r\n" + + "X-CUSTOM:Here are my simple\\nmultiline\\nnotes\r\n" + + "X-CUSTOM;TYPE=pref:semi-colon\\;seperated\\;\"stuff\"\\;here\r\n" + + "X-CUSTOM:comma\\,\"stuff\"\\,'there'\\,too\r\n" + + "X-HOBBY:my backslash\\\\ hobbies\r\n" + + "X-CREATED:2015-04-05T09:50:44Z\r\n" + + "END:VCARD\r\n"; + + // iOS/11.0 (15A372) + private static String iosContactsPropfind = + "\n" + + "\n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + "\n"; + + @Before + public void setUp() throws Exception { + if (!TestUtil.fromRunUnitTests) { + TestUtil.cliSetup(); + } + DAV1 = "carddav-" + testInfo.getMethodName().toLowerCase() + "-dav1"; + DAV2 = "carddav-" + testInfo.getMethodName().toLowerCase() + "-dav2"; + cleanUp(); + dav1 = TestUtil.createAccount(DAV1); + } + + @After + public void cleanUp() throws Exception { + TestUtil.deleteAccountIfExists(DAV1); + TestUtil.deleteAccountIfExists(DAV2); + } + + public static Document doIosContactsPropOnAddressbookHomeSet(Account acct) + throws IOException, XmlParseException { + TestCalDav.PropFindMethod method = new TestCalDav.PropFindMethod( + TestCalDav.getFullUrl(UrlNamespace.getAddressbookHomeSetUrl(acct.getName()))); + method.addRequestHeader("Depth", "1"); + method.addRequestHeader("Brief", "t"); + method.addRequestHeader("Prefer", "return=minimal"); + method.addRequestHeader("Accept", "*/*"); + Document doc = TestCalDav.doMethodYieldingMultiStatus(method, acct, iosContactsPropfind); + return doc; + } + + @Test + public void badBasicAuthToContacts() throws Exception { + assertNotNull("Test account object", dav1); + String calFolderUrl = TestCalDav.getFolderUrl(dav1, "Contacts"); + HttpClient client = new HttpClient(); + GetMethod method = new GetMethod(calFolderUrl); + TestCalDav.addBasicAuthHeaderForUser(method, dav1, "badPassword"); + HttpMethodExecutor.execute(client, method, HttpStatus.SC_UNAUTHORIZED); + } + + @Test + public void mkcol4addressBook() throws Exception { + String xml = "" + + " " + + " " + + " " + + " " + + " " + + " " + + " OtherContacts" + + " Extra Contacts" + + " " + + " " + + ""; + StringBuilder url = TestCalDav.getLocalServerRoot(); + url.append(DavServlet.DAV_PATH).append("/").append(dav1.getName()).append("/OtherContacts/"); + MkColMethod method = new MkColMethod(url.toString()); + TestCalDav.addBasicAuthHeaderForUser(method, dav1); + HttpClient client = new HttpClient(); + method.addRequestHeader("Content-Type", MimeConstants.CT_TEXT_XML); + method.setRequestEntity(new ByteArrayRequestEntity(xml.getBytes(), MimeConstants.CT_TEXT_XML)); + HttpMethodExecutor.execute(client, method, HttpStatus.SC_MULTI_STATUS); + + ZMailbox.Options options = new ZMailbox.Options(); + options.setAccount(dav1.getName()); + options.setAccountBy(AccountBy.name); + options.setPassword(TestUtil.DEFAULT_PASSWORD); + options.setUri(TestUtil.getSoapUrl()); + options.setNoSession(true); + ZMailbox mbox = ZMailbox.getMailbox(options); + ZFolder folder = mbox.getFolderByPath("/OtherContacts"); + assertEquals("OtherContacts", folder.getName()); + assertEquals("OtherContacts default view", View.contact, folder.getDefaultView()); + } + + @Test + public void createContactWithIfNoneMatchTesting() throws ServiceException, IOException { + assertNotNull("Test account object", dav1); + String davBaseName = "SCRUFF1.vcf"; // Based on UID + String contactsFolderUrl = TestCalDav.getFolderUrl(dav1, "Contacts"); + String url = String.format("%s%s", contactsFolderUrl, davBaseName); + HttpClient client = new HttpClient(); + PutMethod putMethod = new PutMethod(url); + TestCalDav.addBasicAuthHeaderForUser(putMethod, dav1); + putMethod.addRequestHeader("Content-Type", "text/vcard"); + + putMethod.setRequestEntity( + new ByteArrayRequestEntity(simpleVcard.getBytes(), MimeConstants.CT_TEXT_VCARD)); + // Bug 84246 this used to fail with 409 Conflict because we used to require an If-None-Match header + HttpMethodExecutor.execute(client, putMethod, HttpStatus.SC_CREATED); + + // Check that trying to put the same thing again when we don't expect it to exist (i.e. Using If-None-Match + // header) will fail. + putMethod = new PutMethod(url); + TestCalDav.addBasicAuthHeaderForUser(putMethod, dav1); + putMethod.addRequestHeader("Content-Type", "text/vcard"); + putMethod.addRequestHeader(DavProtocol.HEADER_IF_NONE_MATCH, "*"); + putMethod.setRequestEntity( + new ByteArrayRequestEntity(simpleVcard.getBytes(), MimeConstants.CT_TEXT_VCARD)); + HttpMethodExecutor.execute(client, putMethod, HttpStatus.SC_PRECONDITION_FAILED); + } + + @Test + public void appleStyleGroup() throws ServiceException, IOException { + String contactsFolderUrl = TestCalDav.getFolderUrl(dav1, "Contacts"); + HttpClient client = new HttpClient(); + + PostMethod postMethod = new PostMethod(contactsFolderUrl); + TestCalDav.addBasicAuthHeaderForUser(postMethod, dav1); + postMethod.addRequestHeader("Content-Type", "text/vcard"); + postMethod.setRequestEntity( + new ByteArrayRequestEntity(rachelVcard.getBytes(), MimeConstants.CT_TEXT_VCARD)); + HttpMethodExecutor.execute(client, postMethod, HttpStatus.SC_CREATED); + + postMethod = new PostMethod(contactsFolderUrl); + TestCalDav.addBasicAuthHeaderForUser(postMethod, dav1); + postMethod.addRequestHeader("Content-Type", "text/vcard"); + postMethod.setRequestEntity( + new ByteArrayRequestEntity(blueGroupCreate.getBytes(), MimeConstants.CT_TEXT_VCARD)); + HttpMethodExecutor exe = HttpMethodExecutor.execute(client, postMethod, HttpStatus.SC_CREATED); + exe.getNonNullHeaderValue("Location", "When creating Group"); + + postMethod = new PostMethod(contactsFolderUrl); + TestCalDav.addBasicAuthHeaderForUser(postMethod, dav1); + postMethod.addRequestHeader("Content-Type", "text/vcard"); + postMethod.setRequestEntity(new ByteArrayRequestEntity(parisVcard.getBytes(), + MimeConstants.CT_TEXT_VCARD)); + HttpMethodExecutor.execute(client, postMethod, HttpStatus.SC_CREATED); + + String url = String.format("%s%s", contactsFolderUrl, "F53A6F96-566F-46CC-8D48-A5263FAB5E38.vcf"); + PutMethod putMethod = new PutMethod(url); + TestCalDav.addBasicAuthHeaderForUser(putMethod, dav1); + putMethod.addRequestHeader("Content-Type", "text/vcard"); + putMethod.setRequestEntity(new ByteArrayRequestEntity(blueGroupModify.getBytes(), + MimeConstants.CT_TEXT_VCARD)); + HttpMethodExecutor.execute(client, putMethod, HttpStatus.SC_NO_CONTENT); + + GetMethod getMethod = new GetMethod(url); + TestCalDav.addBasicAuthHeaderForUser(getMethod, dav1); + getMethod.addRequestHeader("Content-Type", "text/vcard"); + exe = HttpMethodExecutor.execute(client, getMethod, HttpStatus.SC_OK); + String respBody = exe.getResponseAsString(); + String [] expecteds = { + "X-ADDRESSBOOKSERVER-KIND:group", + "X-ADDRESSBOOKSERVER-MEMBER:urn:uuid:BE43F16D-336E-4C3E-BAE6-22B8F245A986", + "X-ADDRESSBOOKSERVER-MEMBER:urn:uuid:07139DE2-EA7B-46CB-A970-C4DF7F72D9AE" }; + for (String expected : expecteds) { + assertTrue(String.format("GET should contain '%s'\nBODY=%s", expected, respBody), + respBody.contains(expected)); + } + + // members are actually stored in a different way. Make sure it isn't a fluke + // that the GET response contained the correct members by checking that the members + // appear where expected in a search hit. + SearchRequest searchRequest = new SearchRequest(); + searchRequest.setSortBy("dateDesc"); + searchRequest.setLimit(8); + searchRequest.setSearchTypes("contact"); + searchRequest.setQuery("in:Contacts"); + ZMailbox mbox = TestUtil.getZMailbox(DAV1); + SearchResponse searchResp = mbox.invokeJaxb(searchRequest); + assertNotNull("JAXB SearchResponse object", searchResp); + List hits = searchResp.getSearchHits(); + assertNotNull("JAXB SearchResponse hits", hits); + assertEquals("JAXB SearchResponse hits", 3, hits.size()); + boolean seenGroup = false; + for (SearchHit hit : hits) { + ContactInfo contactInfo = (ContactInfo) hit; + if ("BlueGroup".equals(contactInfo.getFileAs())) { + seenGroup = true; + assertEquals("Number of members of group in search hit", + 2, contactInfo.getContactGroupMembers().size()); + } + ZimbraLog.test.info("Hit %s class=%s", hit, hit.getClass().getName()); + } + assertTrue("Seen group", seenGroup); + } + + @Test + public void xBusyMacAttach() throws ServiceException, IOException { + String contactsFolderUrl = TestCalDav.getFolderUrl(dav1, "Contacts"); + HttpClient client = new HttpClient(); + + PostMethod postMethod = new PostMethod(contactsFolderUrl); + TestCalDav.addBasicAuthHeaderForUser(postMethod, dav1); + postMethod.addRequestHeader("Content-Type", "text/vcard"); + postMethod.setRequestEntity(new ByteArrayRequestEntity(smallBusyMacAttach.getBytes(), + MimeConstants.CT_TEXT_VCARD)); + HttpMethodExecutor exe = HttpMethodExecutor.execute(client, postMethod, HttpStatus.SC_CREATED); + String location = exe.getNonNullHeaderValue("Location", "When creating VCARD"); + String url = String.format("%s%s", contactsFolderUrl, location.substring(location.lastIndexOf('/') + 1)); + GetMethod getMethod = new GetMethod(url); + TestCalDav.addBasicAuthHeaderForUser(getMethod, dav1); + getMethod.addRequestHeader("Content-Type", "text/vcard"); + exe = HttpMethodExecutor.execute(client, getMethod, HttpStatus.SC_OK); + String respBody = exe.getResponseAsString(); + String [] expecteds = { + "\r\nX-BUSYMAC-ATTACH;X-FILENAME=favicon.ico;ENCODING=B:AAABAAEAEBAAAAEAIABoBA\r\n", + "\r\n AAFgAAACgAAAAQAAAAIAAAAAEAIAAAAAAAQAQAABMLAAATCwAAAAAAAAAAAAAAAAAAw4cAY8\r\n", + "\r\nX-BUSYMAC-MODIFIED-BY:Gren Elliot\r\n", + "\r\nX-CUSTOM:one two three four five six seven eight nine ten eleven twelve t\r\n hirteen fourteen fifteen", + "\r\nX-CUSTOM:Here are my simple\\Nmultiline\\Nnotes\r\n", + "\r\nX-CUSTOM;TYPE=pref:semi-colon\\;seperated\\;\"stuff\"\\;here\r\n", + "\r\nX-CUSTOM:comma\\,\"stuff\"\\,'there'\\,too\r\n", + "\r\nX-HOBBY:my backslash\\\\ hobbies\r\n", + "\r\nX-CREATED:2015-04-05T09:50:44Z\r\n" }; + for (String expected : expecteds) { + assertTrue(String.format("GET should contain '%s'\nBODY=%s", expected, respBody), + respBody.contains(expected)); + } + + SearchRequest searchRequest = new SearchRequest(); + searchRequest.setSortBy("dateDesc"); + searchRequest.setLimit(8); + searchRequest.setSearchTypes("contact"); + searchRequest.setQuery("in:Contacts"); + ZMailbox mbox = TestUtil.getZMailbox(DAV1); + SearchResponse searchResp = mbox.invokeJaxb(searchRequest); + assertNotNull("JAXB SearchResponse object", searchResp); + List hits = searchResp.getSearchHits(); + assertNotNull("JAXB SearchResponse hits", hits); + assertEquals("JAXB SearchResponse hits", 1, hits.size()); + } + + private String sharedContactFolderName() throws ServiceException { + ZMailbox sharerZmbox = TestUtil.getZMailbox(DAV2); + return String.format("%s's Contacts", sharerZmbox.getName()); + } + + private void shareContacts() throws ServiceException { + ZMailbox sharerZmbox = TestUtil.getZMailbox(DAV2); + ZMailbox shareeZmbox = TestUtil.getZMailbox(DAV1); + TestUtil.createMountpoint(sharerZmbox, "Contacts", shareeZmbox, sharedContactFolderName()); + } + + @Test(timeout=100000) + public void createInSharedAddressBook() throws ServiceException, IOException { + Account dav2 = TestUtil.createAccount(DAV2); + assertNotNull("Test account object", dav2); + shareContacts(); + String contactsFolderUrl = TestCalDav.getFolderUrl(dav1, sharedContactFolderName()); + HttpClient client = new HttpClient(); + + PostMethod postMethod = new PostMethod(contactsFolderUrl); + TestCalDav.addBasicAuthHeaderForUser(postMethod, dav1); + postMethod.addRequestHeader("Content-Type", "text/vcard"); + postMethod.setRequestEntity(new ByteArrayRequestEntity(parisVcard.getBytes(), + MimeConstants.CT_TEXT_VCARD)); + HttpMethodExecutor exe = HttpMethodExecutor.execute(client, postMethod, HttpStatus.SC_CREATED); + String location = + exe.getNonNullHeaderValue("Location", "When creating VCARD in shared address book"); + String url = String.format("%s%s",contactsFolderUrl, + location.substring(location.lastIndexOf('/') + 1)); + GetMethod getMethod = new GetMethod(url); + TestCalDav.addBasicAuthHeaderForUser(getMethod, dav1); + getMethod.addRequestHeader("Content-Type", "text/vcard"); + exe = HttpMethodExecutor.execute(client, getMethod, HttpStatus.SC_OK); + } + + @Test(timeout=100000) + public void iosContactsPropfindABHome() throws ServiceException, IOException { + Account dav2 = TestUtil.createAccount(DAV2); + assertNotNull("Test account object", dav2); + shareContacts(); + XPath xpath = XPathFactory.newInstance().newXPath(); + xpath.setNamespaceContext(TestCalDav.NamespaceContextForXPath.forCardDAV()); + Document propfindResponseDoc = doIosContactsPropOnAddressbookHomeSet(dav1); + try { + String mpResp = "/D:multistatus/D:response"; + String collectionXp = "D:propstat/D:prop/D:resourcetype/D:collection"; + String abXp = "D:propstat/D:prop/D:resourcetype/C:addressbook"; + String mpXp = "D:propstat/D:prop/D:resourcetype/Y:mountpoint"; + XPathExpression respNodesExpression = xpath.compile(mpResp); + XPathExpression hrefTextExpression = xpath.compile("D:href/text()"); + XPathExpression collectionExpression = xpath.compile(collectionXp); + XPathExpression abExpression = xpath.compile(abXp); + XPathExpression mpExpression = xpath.compile(mpXp); + NodeList responseNodes = (NodeList) respNodesExpression.evaluate( + propfindResponseDoc, XPathConstants.NODESET); + assertNotNull("No response nodes found in multistatus response to PROPFIND", responseNodes); + boolean seenShare = false; + String expectedShareHref = UrlNamespace.getFolderUrl(dav1.getName(), sharedContactFolderName()) + .replaceAll(" ", "%20").replaceAll("@", "%40"); + for (int ndx = 0; ndx < responseNodes.getLength(); ndx++) { + org.w3c.dom.Element respNode = (org.w3c.dom.Element) responseNodes.item(ndx); + String text = (String) hrefTextExpression.evaluate(respNode, XPathConstants.STRING); + if (expectedShareHref.equals(text)) { + seenShare = true; + NodeList colNodes = (NodeList) collectionExpression.evaluate( + respNode, XPathConstants.NODESET); + assertNotNull(String.format("No %s/%s elements present for shared addressbook", + mpResp, collectionXp), colNodes); + assertEquals(String.format("Number of %s/%s elements present for shared addressbook", + mpResp, collectionXp), 1, colNodes.getLength()); + + NodeList abNodes = (NodeList) abExpression.evaluate(respNode, XPathConstants.NODESET); + assertNotNull(String.format("No %s/%s elements present for shared addressbook", + mpResp, abXp), abNodes); + assertEquals(String.format("Number of %s/%s elements present for shared addressbook", + mpResp, abXp), 1, abNodes.getLength()); + + NodeList mpNodes = (NodeList) mpExpression.evaluate(respNode, XPathConstants.NODESET); + assertNotNull(String.format("No %s/%s elements present for shared addressbook", + mpResp, mpXp), mpNodes); + assertEquals(String.format("Number of %s/%s elements present for shared addressbook", + mpResp, mpXp), 1, mpNodes.getLength()); + break; + } + } + assertTrue("Should have been response node in multistatus for shared addressbook", seenShare); + } catch (XPathExpressionException e) { + ZimbraLog.test.warn("xpath problem", e); + fail("Problem with XPath expression"); + } + } +} diff --git a/store/src/java/com/zimbra/qa/unittest/TestDataSource.java b/store/src/java/com/zimbra/qa/unittest/TestDataSource.java index 78362afc48f..216c24cd827 100644 --- a/store/src/java/com/zimbra/qa/unittest/TestDataSource.java +++ b/store/src/java/com/zimbra/qa/unittest/TestDataSource.java @@ -16,6 +16,13 @@ */ package com.zimbra.qa.unittest; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + import java.util.ArrayList; import java.util.HashMap; import java.util.List; @@ -27,13 +34,6 @@ import org.junit.Test; import org.junit.rules.TestName; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.fail; - import com.google.common.collect.Lists; import com.zimbra.client.ZCalDataSource; import com.zimbra.client.ZDataSource; @@ -437,13 +437,34 @@ public void testPop3() throws Exception { assertEquals(id, hits.get(0).getId()); } + /** + * Tests {@link ZMailbox#testDataSource}. + */ + @Test + public void testBadDataSource() throws Exception { + ZMailbox mbox = TestUtil.getZMailbox(USER_NAME); + // Create data source + Provisioning prov = Provisioning.getInstance(); + Map attrs = new HashMap(); + attrs.put(Provisioning.A_zimbraDataSourceEnabled, LdapConstants.LDAP_FALSE); + attrs.put(Provisioning.A_zimbraDataSourceHost, "testhost"); + attrs.put(Provisioning.A_zimbraDataSourcePort, "0"); + attrs.put(Provisioning.A_zimbraDataSourceUsername, "testuser"); + attrs.put(Provisioning.A_zimbraDataSourcePassword, "testpass"); + attrs.put(Provisioning.A_zimbraDataSourceFolderId, "1"); + attrs.put(Provisioning.A_zimbraDataSourceConnectionType, ConnectionType.cleartext.toString()); + prov.createDataSource(account, DataSourceType.pop3, NAME_PREFIX + DS_NAME, attrs); + ZDataSource zds = TestUtil.getDataSource(mbox, NAME_PREFIX + DS_NAME); + String testVal = mbox.testDataSource(zds); + assertNotNull("ZMailbox::testDataSource should return an error", testVal); + } + /** * Creates a folder that syncs to another folder via RSS, and verifies that an * RSS data source was implicitly created. */ @Test - public void testRss() - throws Exception { + public void testRss() throws Exception { // Create source folder, make it publicly readable, and add a message to it. ZMailbox mbox = TestUtil.getZMailbox(USER_NAME); String parentId = Integer.toString(Mailbox.ID_FOLDER_USER_ROOT); diff --git a/store/src/java/com/zimbra/qa/unittest/TestImapImport.java b/store/src/java/com/zimbra/qa/unittest/TestImapImport.java index b752bd39ce4..3aba25604cb 100644 --- a/store/src/java/com/zimbra/qa/unittest/TestImapImport.java +++ b/store/src/java/com/zimbra/qa/unittest/TestImapImport.java @@ -103,7 +103,7 @@ public void setUp() throws Exception { String id = mLocalMbox.createDataSource(mDataSource); mDataSource = null; for (ZDataSource ds : mLocalMbox.getAllDataSources()) { - if (ds.getId().equals(id)) { + if (ds.getId() != null && ds.getId().equals(id)) { mDataSource = ds; } } diff --git a/store/src/java/com/zimbra/qa/unittest/TestImapOneWayImport.java b/store/src/java/com/zimbra/qa/unittest/TestImapOneWayImport.java index 78721e0b9bf..e1432d115d6 100644 --- a/store/src/java/com/zimbra/qa/unittest/TestImapOneWayImport.java +++ b/store/src/java/com/zimbra/qa/unittest/TestImapOneWayImport.java @@ -93,7 +93,7 @@ public void setUp() throws Exception { String id = mLocalMbox.createDataSource(mDataSource); mDataSource = null; for (ZDataSource ds : mLocalMbox.getAllDataSources()) { - if (ds.getId().equals(id)) { + if (ds.getId() != null && ds.getId().equals(id)) { mDataSource = ds; } } diff --git a/store/src/java/com/zimbra/qa/unittest/TestImapViaImapDaemon.java b/store/src/java/com/zimbra/qa/unittest/TestImapViaImapDaemon.java index f7dc2160a7d..3fc141a74fe 100644 --- a/store/src/java/com/zimbra/qa/unittest/TestImapViaImapDaemon.java +++ b/store/src/java/com/zimbra/qa/unittest/TestImapViaImapDaemon.java @@ -23,6 +23,7 @@ import com.zimbra.common.localconfig.LocalConfig; import com.zimbra.common.service.ServiceException; import com.zimbra.common.util.Log; +import com.zimbra.common.util.ZimbraLog; import com.zimbra.cs.account.Account; import com.zimbra.cs.account.Provisioning.CacheEntry; import com.zimbra.cs.imap.ImapHandler; @@ -43,6 +44,8 @@ * The actual tests that are run are in {@link SharedImapTests} */ public class TestImapViaImapDaemon extends SharedImapTests { + private static String IMAPD_CSV_FILE = "/opt/zimbra/zmstat/imapd.csv"; + private static String IMAPD_CSV_STATS_FILE = "/opt/zimbra/zmstat/imapd_stats.csv"; @Before public void setUp() throws Exception { @@ -60,8 +63,8 @@ public void setUp() throws Exception { @After public void tearDown() throws Exception { super.sharedTearDown(); + restoreImapConfigSettings(); if (imapHostname != null) { - restoreImapConfigSettings(); TestUtil.flushImapDaemonCache(imapServer); getAdminConnection().reloadLocalConfig(); } @@ -95,6 +98,36 @@ public void testClearDaemonCacheWrongAuthenticator() throws Exception { } } + @Test + public void testAddAccountLoggerWrongAuthenticator() throws Exception { + connection = connect(); + Account account = TestUtil.getAccount(USER); + try { + connection.addAccountLogger(account, "zimbra.imap", "trace"); + fail("should not be able to add an account logger without authenticating"); + } catch (CommandFailedException cfe) { + assertEquals("must be in AUTHENTICATED or SELECTED state", cfe.getError()); + } + connection.login(PASS); + try { + connection.addAccountLogger(account, "zimbra.imap", "trace"); + fail("should not be able to add an account logger without using X-ZIMBRA auth mechanism"); + } catch (CommandFailedException cfe) { + assertEquals("must be authenticated as admin with X-ZIMBRA auth mechanism", cfe.getError()); + } + } + + @Test + public void testAddAccountLogger() throws Exception { + connection = getAdminConnection(); + Account account = TestUtil.getAccount(USER); + try { + connection.addAccountLogger(account, "zimbra.imap", "trace"); + } catch (CommandFailedException cfe) { + fail("Couldn't add an account logger - " + cfe.getMessage()); + } + } + private void tryConnect(boolean shouldSucceed, String message) throws Exception { try { connection = connect(USER); @@ -237,11 +270,28 @@ public void testZimbraCommandsNonAdminUser() throws Exception { } @Test - public void testImapdStatFile() throws Exception { - File testFile = new File("/opt/zimbra/zmstat/imapd.csv"); - - assertTrue("imapd.csv file does not exists", testFile.exists()); - assertTrue("imapd.csv file is empty", testFile.length()>0); + public void imapdStatFiles() throws Exception { + connection = this.connectAndSelectInbox(USER); + connection.logout(); + connection = null; + int max_time = 70 * 1000; // Files are updated every minute, so max wait a little bit longer + int sofar = 0; + File testFile = new File(IMAPD_CSV_FILE); + while (!testFile.exists() && (sofar < max_time)) { + ZimbraLog.test.debug("%s file does not exist after %s seconds", IMAPD_CSV_FILE, sofar / 1000); + Thread.sleep(1000); + sofar += 1000; + } + assertTrue(String.format("%s file does not exist", testFile.getCanonicalPath()), testFile.exists()); + assertTrue(String.format("%s file is unexpectedly empty", testFile.getCanonicalPath()), + testFile.length()>0); + testFile = new File(IMAPD_CSV_STATS_FILE); + if (!testFile.exists()) { + Thread.sleep(500); + } + assertTrue(String.format("%s file does not exist", testFile.getCanonicalPath()), testFile.exists()); + assertTrue(String.format("%s file is unexpectedly empty", testFile.getCanonicalPath()), + testFile.length()>0); } diff --git a/store/src/java/com/zimbra/qa/unittest/TestPop3Import.java b/store/src/java/com/zimbra/qa/unittest/TestPop3Import.java index ccb70dab8da..9ed195df02f 100644 --- a/store/src/java/com/zimbra/qa/unittest/TestPop3Import.java +++ b/store/src/java/com/zimbra/qa/unittest/TestPop3Import.java @@ -200,7 +200,7 @@ private ZPop3DataSource getZDataSource() throws Exception { ZMailbox mbox = TestUtil.getZMailbox(USER_NAME); List dataSources = mbox.getAllDataSources(); for (ZDataSource ds : dataSources) { - if (ds.getName().equals(DATA_SOURCE_NAME)) { + if (ds.getName() != null && ds.getName().equals(DATA_SOURCE_NAME)) { return (ZPop3DataSource) ds; } } diff --git a/store/src/java/com/zimbra/qa/unittest/TestUtil.java b/store/src/java/com/zimbra/qa/unittest/TestUtil.java index 1b1a3237655..21f9f9fd980 100644 --- a/store/src/java/com/zimbra/qa/unittest/TestUtil.java +++ b/store/src/java/com/zimbra/qa/unittest/TestUtil.java @@ -783,7 +783,7 @@ public static void deleteTestData(String userName, String subjectSubstring) thro // Delete data sources List dataSources = mbox.getAllDataSources(); for (ZDataSource ds : dataSources) { - if (ds.getName().contains(subjectSubstring)) { + if (ds.getName() != null && ds.getName().contains(subjectSubstring)) { mbox.deleteDataSource(ds); } } @@ -1164,7 +1164,7 @@ public static ZMountpoint createMountpoint(ZMailbox remoteMbox, String remotePat */ public static ZDataSource getDataSource(ZMailbox mbox, String name) throws ServiceException { for (ZDataSource ds : mbox.getAllDataSources()) { - if (ds.getName().equals(name)) { + if (ds.getName() != null && ds.getName().equals(name)) { return ds; } } diff --git a/store/src/java/com/zimbra/qa/unittest/TestZClient.java b/store/src/java/com/zimbra/qa/unittest/TestZClient.java index 5b6b99dfb04..5160d141f8f 100644 --- a/store/src/java/com/zimbra/qa/unittest/TestZClient.java +++ b/store/src/java/com/zimbra/qa/unittest/TestZClient.java @@ -39,6 +39,8 @@ import java.util.Map; import java.util.Set; +import javax.mail.MessagingException; + import org.apache.commons.io.IOUtils; import org.junit.After; import org.junit.Before; @@ -60,11 +62,13 @@ import com.zimbra.client.ZMailbox.GalEntryType; import com.zimbra.client.ZMailbox.OpenIMAPFolderParams; import com.zimbra.client.ZMailbox.Options; +import com.zimbra.client.ZMailbox.ZActionResult; import com.zimbra.client.ZMailbox.ZAppointmentResult; import com.zimbra.client.ZMailbox.ZOutgoingMessage; import com.zimbra.client.ZMailbox.ZSearchGalResult; import com.zimbra.client.ZMessage; import com.zimbra.client.ZMessageHit; +import com.zimbra.client.ZMountpoint; import com.zimbra.client.ZPrefs; import com.zimbra.client.ZSearchFolder; import com.zimbra.client.ZSearchHit; @@ -119,14 +123,18 @@ public class TestZClient { @Rule public TestName testInfo = new TestName(); - private static String NAME_PREFIX = "TestZClient"; - private static String RECIPIENT_USER_NAME = NAME_PREFIX + "_user2"; - private static final String USER_NAME = NAME_PREFIX + "_user1"; - private static final String FOLDER_NAME = "testfolder"; + private static String NAME_PREFIX; + private static String RECIPIENT_USER_NAME; + private static String USER_NAME; + private static String FOLDER_NAME; @Before public void setUp() throws Exception { + NAME_PREFIX = String.format("%s-%d", testInfo.getMethodName(), (int)Math.abs(Math.random()*100)); + RECIPIENT_USER_NAME = NAME_PREFIX + "_user2"; + USER_NAME = NAME_PREFIX + "_user1"; + FOLDER_NAME = String.format("%s-Folder", this.getClass().getSimpleName()); if (!TestUtil.fromRunUnitTests) { TestUtil.cliSetup(); } @@ -1375,6 +1383,21 @@ public void createSearchFolder() throws ServiceException { "is:read", (String)null, SearchSortBy.nameAsc, ZFolder.Color.GREEN); } + @Test(timeout=100000) + public void copyMsgToMountpoint() throws ServiceException, IOException, MessagingException { + String sharedFolder = "shared"; + String mountpoint = String.format("shared-", testInfo.getMethodName()); + ZMailbox sharerZmbox = TestUtil.getZMailbox(RECIPIENT_USER_NAME); + ZMailbox shareeZmbox = TestUtil.getZMailbox(USER_NAME); + ZMountpoint mp = TestUtil.createMountpoint(sharerZmbox, "/" + sharedFolder, shareeZmbox, mountpoint); + String msgId = TestUtil.addMessage(shareeZmbox, String.format("test message for %s", + testInfo.getMethodName())); + ZActionResult result = shareeZmbox.moveMessage(msgId, mp.getFolderIdAsString()); + assertNotNull("ZActionResult for move message", result); + assertNotNull("ZActionResult Ids array for move message", result.getIdsAsArray()); + assertEquals("ZActionResult Ids array length for move message", 1, result.getIdsAsArray().length); + } + public static void main(String[] args) throws Exception { TestUtil.cliSetup(); TestUtil.runTest(TestZClient.class); diff --git a/store/src/java/com/zimbra/qa/unittest/ZimbraSuite.java b/store/src/java/com/zimbra/qa/unittest/ZimbraSuite.java index b2f2540e0b3..6c33093dc1c 100644 --- a/store/src/java/com/zimbra/qa/unittest/ZimbraSuite.java +++ b/store/src/java/com/zimbra/qa/unittest/ZimbraSuite.java @@ -109,6 +109,7 @@ public class ZimbraSuite { sClasses.add(TestJaxbProvisioning.class); sClasses.add(TestAclPush.class); sClasses.add(TestCalDav.class); + sClasses.add(TestCardDav.class); sClasses.add(TestCalDavImportServer.class); sClasses.add(TestContactCSV.class); sClasses.add(TestStoreManager.class); diff --git a/store/src/java/com/zimbra/qa/unittest/server/TestDataSourceServer.java b/store/src/java/com/zimbra/qa/unittest/server/TestDataSourceServer.java index 6927537a617..21097b5f4fc 100644 --- a/store/src/java/com/zimbra/qa/unittest/server/TestDataSourceServer.java +++ b/store/src/java/com/zimbra/qa/unittest/server/TestDataSourceServer.java @@ -16,13 +16,21 @@ */ package com.zimbra.qa.unittest.server; -import junit.framework.TestCase; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TestName; + +import com.zimbra.client.ZDataSource; import com.zimbra.client.ZFolder; import com.zimbra.client.ZImapDataSource; import com.zimbra.client.ZMailbox; -import com.zimbra.cs.account.Account; -import com.zimbra.cs.account.Cos; import com.zimbra.cs.account.Provisioning; import com.zimbra.cs.account.Server; import com.zimbra.cs.datasource.DataSourceManager; @@ -31,55 +39,61 @@ import com.zimbra.cs.mailbox.ScheduledTask; import com.zimbra.qa.unittest.TestUtil; import com.zimbra.soap.type.DataSource.ConnectionType; +public class TestDataSourceServer { -public class TestDataSourceServer extends TestCase { + @Rule + public TestName testInfo = new TestName(); - private static final String USER_NAME = "user1"; - private static final String TEST_USER_NAME = "testdatasource"; - private static final String NAME_PREFIX = TestDataSourceServer.class.getSimpleName(); - - private String mOriginalAccountPollingInterval; - private String mOriginalAccountPop3PollingInterval; - private String mOriginalAccountImapPollingInterval; - - private String mOriginalCosPollingInterval; - private String mOriginalCosPop3PollingInterval; - private String mOriginalCosImapPollingInterval; + protected static String USER_NAME = null; + protected String testId; - public void setUp() - throws Exception { + @Before + public void setUp() throws Exception { + testId = String.format("%s-%s-%d", this.getClass().getSimpleName(), testInfo.getMethodName(), (int)Math.abs(Math.random()*100)); + USER_NAME = String.format("%s-user", testId).toLowerCase(); cleanUp(); + TestUtil.createAccount(USER_NAME); + } - // Remember original polling intervals. - Account account = TestUtil.getAccount(USER_NAME); - Cos cos = account.getCOS(); - mOriginalAccountPollingInterval = account.getAttr(Provisioning.A_zimbraDataSourcePollingInterval, false); - if (mOriginalAccountPollingInterval == null) { - mOriginalAccountPollingInterval = ""; - } - mOriginalAccountPop3PollingInterval = account.getAttr(Provisioning.A_zimbraDataSourcePop3PollingInterval, false); - if (mOriginalAccountPop3PollingInterval == null) { - mOriginalAccountPop3PollingInterval = ""; - } - mOriginalAccountImapPollingInterval = account.getAttr(Provisioning.A_zimbraDataSourceImapPollingInterval, false); - if (mOriginalAccountImapPollingInterval == null) { - mOriginalAccountImapPollingInterval = ""; - } - - mOriginalCosPollingInterval = cos.getAttr(Provisioning.A_zimbraDataSourcePollingInterval, ""); - mOriginalCosPop3PollingInterval = cos.getAttr(Provisioning.A_zimbraDataSourcePop3PollingInterval, ""); - mOriginalCosImapPollingInterval = cos.getAttr(Provisioning.A_zimbraDataSourceImapPollingInterval, ""); + @Test + public void testOAuthDS() throws Exception { + String DSName = "testCustomDS"; + String importClassName = "some.data.source.ClassName"; + String refreshToken = "refresh-token"; + ZMailbox zmbox = TestUtil.getZMailbox(USER_NAME); + ZFolder folder = TestUtil.createFolder(zmbox, "/testCustomDS"); + //ZOAuthDataSource zds = new ZOAuthDataSource(DSName, true, refreshToken, folder.getId(), importClassName, ZOAuthDataSource.SOURCE_HOST_YAHOO); + ZDataSource zds = new ZDataSource(DSName, true); + zds.setImportClass(importClassName); + zds.setRefreshToken(refreshToken); + zds.setHost(ZDataSource.SOURCE_HOST_YAHOO); + zds.setFolderId(folder.getId()); + String dsId = zmbox.createDataSource(zds); + assertNotNull("DataSource should have an ID", dsId); + assertFalse("DataSource id should not be empty", dsId.isEmpty()); + ZDataSource ds = zmbox.getDataSourceByName(DSName); + assertNotNull("should retrieve a non-null DataSource", ds); + //assertTrue("expecting ZOAuthDataSource", ds instanceof ZOAuthDataSource); + //ZOAuthDataSource oads = (ZOAuthDataSource)ds; + assertNotNull("DataSource should have a name", ds.getName()); + assertEquals("Data source name should be " + DSName, ds.getName(), DSName); + assertNotNull("new DataSource should have an import class", ds.getImportClass()); + assertEquals("expecting import class: " + importClassName, importClassName, ds.getImportClass()); + assertNotNull("new DataSource should have a refresh token", ds.getRefreshToken()); + assertEquals("expecting refresh token: " + refreshToken, refreshToken, ds.getRefreshToken()); + assertNull("new DataSource should NOT have a refresh token URL", ds.getRefreshTokenUrl()); + assertNotNull("new DataSource should have a host", ds.getHost()); + assertEquals("expecting host: " + ZDataSource.SOURCE_HOST_YAHOO, ZDataSource.SOURCE_HOST_YAHOO, ds.getHost()); } - - public void testScheduling() - throws Exception { - // Create data source. + + @Test + public void testScheduling() throws Exception { ZMailbox zmbox = TestUtil.getZMailbox(USER_NAME); - ZFolder folder = TestUtil.createFolder(zmbox, "/" + NAME_PREFIX + "-testScheduling"); + ZFolder folder = TestUtil.createFolder(zmbox, "/testScheduling"); Provisioning prov = Provisioning.getInstance(); Server server = prov.getLocalServer(); int port = server.getImapBindPort(); - ZImapDataSource zds = new ZImapDataSource(NAME_PREFIX + " testScheduling", true, "localhost", port, + ZImapDataSource zds = new ZImapDataSource("testScheduling", true, "localhost", port, "user2", "test123", folder.getId(), ConnectionType.cleartext); String dsId = zmbox.createDataSource(zds); @@ -122,26 +136,14 @@ private void checkSchedule(Mailbox mbox, String dataSourceId, Integer intervalMi } } - public void tearDown() - throws Exception { - // Reset original polling intervals. - Account account = TestUtil.getAccount(USER_NAME); - Cos cos = account.getCOS(); - - account.setDataSourcePollingInterval(mOriginalAccountPollingInterval); - account.setDataSourcePop3PollingInterval(mOriginalAccountPop3PollingInterval); - account.setDataSourceImapPollingInterval(mOriginalAccountImapPollingInterval); - - cos.setDataSourcePollingInterval(mOriginalCosPollingInterval); - cos.setDataSourcePop3PollingInterval(mOriginalCosPop3PollingInterval); - cos.setDataSourceImapPollingInterval(mOriginalCosImapPollingInterval); - + @After + public void tearDown() throws Exception { cleanUp(); } - public void cleanUp() - throws Exception { - TestUtil.deleteAccount(TEST_USER_NAME); - TestUtil.deleteTestData(USER_NAME, NAME_PREFIX); + private void cleanUp() throws Exception { + if (USER_NAME != null) { + TestUtil.deleteAccountIfExists(USER_NAME); + } } } diff --git a/store/src/java/com/zimbra/qa/unittest/server/TestPop3ImportServer.java b/store/src/java/com/zimbra/qa/unittest/server/TestPop3ImportServer.java index d097cc439ae..60ffcc204aa 100644 --- a/store/src/java/com/zimbra/qa/unittest/server/TestPop3ImportServer.java +++ b/store/src/java/com/zimbra/qa/unittest/server/TestPop3ImportServer.java @@ -191,7 +191,7 @@ private ZPop3DataSource getZDataSource() throws Exception { ZMailbox mbox = TestUtil.getZMailbox(USER_NAME); List dataSources = mbox.getAllDataSources(); for (ZDataSource ds : dataSources) { - if (ds.getName().equals(DATA_SOURCE_NAME)) { + if (ds.getName() != null && ds.getName().equals(DATA_SOURCE_NAME)) { return (ZPop3DataSource) ds; } }