Java tutorial
/* * ***** BEGIN LICENSE BLOCK ***** * Zimbra Collaboration Suite Server * Copyright (C) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 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 <https://www.gnu.org/licenses/>. * ***** END LICENSE BLOCK ***** */ package com.zimbra.cs.zclient; import java.io.BufferedInputStream; import java.io.BufferedReader; import java.io.ByteArrayInputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStream; import java.io.OutputStreamWriter; import java.io.PrintWriter; import java.io.UnsupportedEncodingException; import java.net.URI; import java.net.URISyntaxException; import java.util.ArrayList; import java.util.Calendar; import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Properties; import java.util.TimeZone; import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.zip.GZIPInputStream; import javax.mail.MessagingException; import javax.mail.Session; import javax.mail.internet.MimeMessage; import javax.mail.util.SharedByteArrayInputStream; import org.apache.commons.cli.CommandLine; import org.apache.commons.cli.CommandLineParser; import org.apache.commons.cli.GnuParser; import org.apache.commons.cli.Option; import org.apache.commons.cli.Options; import org.apache.commons.cli.ParseException; import com.zimbra.client.ZAce; import com.zimbra.client.ZAppointmentHit; import com.zimbra.client.ZAutoCompleteMatch; import com.zimbra.client.ZContact; import com.zimbra.client.ZContactHit; import com.zimbra.client.ZConversation; import com.zimbra.client.ZConversation.ZMessageSummary; import com.zimbra.client.ZConversationHit; import com.zimbra.client.ZDocument; import com.zimbra.client.ZDocumentHit; import com.zimbra.client.ZEmailAddress; import com.zimbra.client.ZFilterRule; import com.zimbra.client.ZFilterRules; import com.zimbra.client.ZFolder; import com.zimbra.client.ZGetMessageParams; import com.zimbra.client.ZGrant; import com.zimbra.client.ZGrant.GranteeType; import com.zimbra.client.ZIdentity; import com.zimbra.client.ZJSONObject; import com.zimbra.client.ZMailbox; import com.zimbra.client.ZMailbox.Fetch; import com.zimbra.client.ZMailbox.GalEntryType; import com.zimbra.client.ZMailbox.OwnerBy; import com.zimbra.client.ZMailbox.SharedItemBy; import com.zimbra.client.ZMailbox.ZApptSummaryResult; import com.zimbra.client.ZMailbox.ZSearchGalResult; import com.zimbra.client.ZMessage; import com.zimbra.client.ZMessage.ZMimePart; import com.zimbra.client.ZMessageHit; import com.zimbra.client.ZMountpoint; import com.zimbra.client.ZSearchFolder; import com.zimbra.client.ZSearchHit; import com.zimbra.client.ZSearchPagerResult; import com.zimbra.client.ZSearchParams; import com.zimbra.client.ZSearchResult; import com.zimbra.client.ZSignature; import com.zimbra.client.ZTag; import com.zimbra.client.ZTag.Color; import com.zimbra.client.event.ZCreateEvent; import com.zimbra.client.event.ZDeleteEvent; import com.zimbra.client.event.ZEventHandler; import com.zimbra.client.event.ZModifyEvent; import com.zimbra.client.event.ZRefreshEvent; import com.zimbra.common.account.Key.AccountBy; import com.zimbra.common.auth.ZAuthToken; import com.zimbra.common.localconfig.LC; import com.zimbra.common.mailbox.ContactConstants; import com.zimbra.common.service.ServiceException; import com.zimbra.common.soap.Element; import com.zimbra.common.soap.SoapFaultException; import com.zimbra.common.soap.SoapTransport; import com.zimbra.common.soap.SoapTransport.DebugListener; import com.zimbra.common.util.ByteUtil; import com.zimbra.common.util.CliUtil; import com.zimbra.common.util.Constants; import com.zimbra.common.util.DateUtil; import com.zimbra.common.util.EmailUtil; import com.zimbra.common.util.HttpUtil; import com.zimbra.common.util.StringUtil; import com.zimbra.common.zclient.ZClientException; import com.zimbra.common.zmime.ZMimeMessage; import com.zimbra.cs.account.GuestAccount; import com.zimbra.cs.account.accesscontrol.Right; import com.zimbra.cs.account.accesscontrol.RightManager; import com.zimbra.cs.account.soap.SoapAccountInfo; import com.zimbra.cs.account.soap.SoapProvisioning; import com.zimbra.cs.account.soap.SoapProvisioning.DelegateAuthResponse; import com.zimbra.cs.ldap.LdapDateUtil; import com.zimbra.cs.util.BuildInfo; import com.zimbra.cs.util.SoapCLI; import com.zimbra.soap.type.SearchSortBy; /** * @author schemers */ public class ZMailboxUtil implements DebugListener { private boolean mInteractive = false; private boolean mGlobalVerbose = false; private boolean mDebug = false; private String mAdminAccountName = null; private String mAuthAccountName = null; private String mTargetAccountName = null; private String mPassword = null; private ZAuthToken mAdminAuthToken = null; private static final String DEFAULT_ADMIN_URL = "https://" + LC.zimbra_zmprov_default_soap_server.value() + ":" + LC.zimbra_admin_service_port.intValue() + "/"; private static final String DEFAULT_URL = "http://" + LC.zimbra_zmprov_default_soap_server.value() + (LC.zimbra_mail_service_port.intValue() == 80 ? "" : ":" + LC.zimbra_mail_service_port.intValue()) + "/"; private String mUrl = DEFAULT_URL; private Map<String, Command> mCommandIndex; private ZMailbox mMbox; private String mPrompt = "mbox> "; ZSearchParams mSearchParams; int mSearchPage; ZSearchParams mConvSearchParams; ZSearchResult mConvSearchResult; SoapProvisioning mProv; private int mTimeout = LC.httpclient_internal_connmgr_so_timeout.intValue(); private final Map<Integer, String> mIndexToId = new HashMap<Integer, String>(); /** current command */ private Command mCommand; /** current command line */ private CommandLine mCommandLine; /** parser for internal commands */ private final CommandLineParser mParser = new GnuParser(); public void setDebug(boolean debug) { mDebug = debug; } public void setVerbose(boolean verbose) { mGlobalVerbose = verbose; } public void setInteractive(boolean interactive) { mInteractive = interactive; } public void setAdminAccountName(String account) { mAdminAccountName = account; } public void setAuthAccountName(String account) { mAuthAccountName = account; } public void setTargetAccountName(String account) { mTargetAccountName = account; } public void setPassword(String password) { mPassword = password; } public void setAdminAuthToken(ZAuthToken authToken) { mAdminAuthToken = authToken; } public void setUrl(String url, boolean admin) throws ServiceException { mUrl = ZMailbox.resolveUrl(url, admin); } public void setTimeout(String timeout) throws ServiceException { int num; try { num = Integer.parseInt(timeout); } catch (NumberFormatException e) { throw ServiceException.INVALID_REQUEST("Invalid timeout value " + timeout, e); } if (num < 0) { throw ServiceException.INVALID_REQUEST("Timeout can't be negative", null); } if (num > 2147483) { throw ServiceException.INVALID_REQUEST("Timeout can't exceed 2147483 seconds", null); } mTimeout = num * 1000; } private void usage() { if (mCommand != null) { stdout.printf("usage:%n%n%s%n", mCommand.getFullUsage()); } if (mInteractive) return; stdout.println(""); stdout.println("zmmailbox [args] [cmd] [cmd-args ...]"); stdout.println(""); stdout.println(" -h/--help display usage"); stdout.println(" -f/--file use file as input stream"); stdout.println( " -u/--url http[s]://{host}[:{port}] server hostname and optional port. must use admin port with -z/-a"); stdout.println(" -a/--admin {name} admin account name to auth as"); stdout.println( " -z/--zadmin use zimbra admin name/password from localconfig for admin/password"); stdout.println(" -y/--authtoken {authtoken} " + SoapCLI.OPT_AUTHTOKEN.getDescription()); stdout.println(" -Y/--authtokenfile {authtoken file} " + SoapCLI.OPT_AUTHTOKENFILE.getDescription()); stdout.println(" -m/--mailbox {name} mailbox to open"); stdout.println( " --auth {name} account name to auth as; defaults to -m unless -A is used"); stdout.println(" -A/--admin-priv execute requests with admin privileges"); stdout.println(" requires -z or -a, and -m"); stdout.println(" -p/--password {pass} auth password"); stdout.println(" -P/--passfile {file} read password from file"); stdout.println(" -t/--timeout timeout (in seconds)"); stdout.println( " -v/--verbose verbose mode (dumps full exception stack trace)"); stdout.println(" -d/--debug debug mode (dumps SOAP messages)"); stdout.println(""); doHelp(null); System.exit(1); } public static enum Category { ADMIN("help on admin-related commands"), ACCOUNT("help on account-related commands"), APPOINTMENT( "help on appoint-related commands", " absolute date-specs:\n" + "\n" + " mm/dd/yyyy (i.e., 12/25/1998)\n" + " yyyy/dd/mm (i.e., 1989/12/25)\n" + " \\d+ (num milliseconds, i.e., 1132276598000)\n" + "\n" + " relative date-specs:\n" + "\n" + " [mp+-]?([0-9]+)([mhdwy][a-z]*)?g\n" + " \n" + " +/{not-specified} current time plus an offset\n" + " - current time minus an offset\n" + " \n" + " (0-9)+ value\n" + "\n" + " ([mhdwy][a-z]*) units, everything after the first character is ignored (except for \"mi\" case):\n" + " m(onths)\n" + " mi(nutes)\n" + " d(ays)\n" + " w(eeks)\n" + " h(ours)\n" + " y(ears)\n" + " \n" + " examples:\n" + " 1day 1 day from now\n" + " +1day 1 day from now \n" + " p1day 1 day from now\n" + " +60mi 60 minutes from now\n" + " +1week 1 week from now\n" + " +6mon 6 months from now \n" + " 1year 1 year from now\n" + "\n"), COMMANDS("help on all commands"), CONTACT( "help on contact-related commands"), CONVERSATION( "help on conversation-related commands"), FILTER( "help on filter-realted commnds", " {conditions}:\n" + " address \"comma-separated-header-names\" all|localpart|domain is|not_is|contains|not_contains|matches|not_matches [case_sensitive] \"value\"\n" + " addressbook in|not_in \"header-name\"\n" + " attachment exists|not_exists\n" + " body contains|not_contains [case_sensitive] \"text\"\n" + " bulk [not]\n" + " conversation where|not_where started|participated\n" + " current_day_of_week is|not_is \"comma-separated-days(0=Sun,6=Sat)\"\n" + " current_time before|not_before|after|not_after \"HHmm\"\n" + " date before|not_before|after|not_after \"YYYYMMDD\"\n" + " facebook [not]\n" + " flagged [not] \"flag\"\n" + " header \"comma-separated-names\" is|not_is|contains|not_contains|matches|not_matches [case_sensitive] \"value\"\n" + " header \"comma-separated-names\" exists|not_exists\n" + " importance is|not_is high|normal|low\n" + " invite exists|not_exists\n" + " linkedin [not]\n" + " list [not]\n" + " me in|not_in \"header-name\"\n" + " mime_header \"name\" is|not_is|contains|not_contains|matches|not_matches [case_sensitive] \"value\"\n" + " ranking in|not_in \"header-name\"\n" + " size under|not_under|over|not_over \"1|1K|1M\"\n" + " socialcast [not]\n" + " twitter [not]\n" + "\n" + " {actions}:\n" + " keep\n" + " discard\n" + " fileinto \"/path\"\n" + " tag \"tag\"\n" + " flag \"flag\"\n" + " mark read|flagged|priority\n" + " redirect \"address\"\n" + " reply \"body-template\"\n" + " notify \"address\" \"subject-template\" \"body-template\" [\"max-body-size(bytes)\"]\n" + " stop\n"), FOLDER( "help on folder-related commands"), ITEM( "help on item-related commands"), MESSAGE( "help on message-related commands"), MISC( "help on misc commands"), RIGHT( "help on right commands", "To grant/revoke rights on entries an account can inherit grants from(group, domain, global), use \"zmprov grr/rvr\" commands."), SEARCH( "help on search-related commands"), TAG( "help on tag-related commands"); String mDesc; String mCategoryHelp; public String getDescription() { return mDesc; } public String getCategoryHelp() { return mCategoryHelp; } Category(String desc) { mDesc = desc; } Category(String desc, String help) { mDesc = desc; mCategoryHelp = help; } } public static Option getOption(String shortName, String longName, boolean hasArgs, String help) { return new Option(shortName, longName, hasArgs, help); } static Option O_AUTH = new Option(null, "auth", true, "account to auth as"); static Option O_AS_ADMIN = new Option("A", "admin-priv", false, "execute requests with admin privileges"); static Option O_AFTER = new Option("a", "after", true, "add after filter-name"); static Option O_BEFORE = new Option("b", "before", true, "add before filter-name"); static Option O_COLOR = new Option("c", "color", true, "color"); static Option O_CONTENT_TYPE = new Option("c", "contentType", true, "content-type"); static Option O_CURRENT = new Option("c", "current", false, "current page of search results"); static Option O_DATE = new Option("d", "date", true, "received date (msecs since epoch)"); static Option O_FIRST = new Option("f", "first", false, "add as first filter rule"); static Option O_FLAGS = new Option("F", "flags", true, "flags"); static Option O_FOLDER = new Option("f", "folder", true, "folder-path-or-id"); static Option O_IGNORE = new Option("i", "ignore", false, "ignore unknown contact attrs"); static Option O_IGNORE_ERROR = new Option("i", "ignore", false, "ignore and continue on error during ics import"); static Option O_LAST = new Option("l", "last", false, "add as last filter rule"); static Option O_LIMIT = new Option("l", "limit", true, "max number of results to return (1-1000, default=25)"); static Option O_NEXT = new Option("n", "next", false, "next page of search results"); static Option O_OUTPUT_FILE = new Option("o", "output", true, "output filename"); static Option O_PRESERVE_ALARMS = new Option(null, "preserveAlarms", false, "preserve existing calendar alarms during ics import (default is to use alarms in ics file)"); static Option O_START_TIME = new Option(null, "startTime", true, "start time for ics export"); static Option O_END_TIME = new Option(null, "endTime", true, "end time for ics export"); static Option O_PREVIOUS = new Option("p", "previous", false, "previous page of search results"); static Option O_SORT = new Option("s", "sort", true, "sort order TODO"); static Option O_REPLACE = new Option("r", "replace", false, "replace contact (default is to merge)"); static Option O_TAGS = new Option("T", "tags", true, "list of tag ids/names"); static Option O_TYPES = new Option("t", "types", true, "list of types to search for (message,conversation,contact,appointment,document,task,wiki)"); static Option O_URL = new Option("u", "url", true, "url to connect to"); static Option O_VERBOSE = new Option("v", "verbose", false, "verbose output"); static Option O_VIEW = new Option("V", "view", true, "default type for folder (appointment,contact,conversation,document,message,task,wiki)"); static Option O_NO_VALIDATION = new Option(null, "noValidation", false, "don't validate file content"); static Option O_DUMPSTER = new Option(null, "dumpster", false, "search in dumpster"); enum Command { ADD_INCOMING_FILTER_RULE("addFilterRule", "afrl", "{name} [*active|inactive] [any|*all] {conditions}+ {actions}+", "add incoming filter rule", Category.FILTER, 2, Integer.MAX_VALUE, O_AFTER, O_BEFORE, O_FIRST, O_LAST), ADD_OUTGOING_FILTER_RULE("addOutgoingFilterRule", "aofrl", "{name} [*active|inactive] [any|*all] {conditions}+ {actions}+", "add outgoing filter rule", Category.FILTER, 2, Integer.MAX_VALUE, O_AFTER, O_BEFORE, O_FIRST, O_LAST), ADD_MESSAGE("addMessage", "am", "{dest-folder-path} {filename-or-dir} [{filename-or-dir} ...]", "add a message to a folder", Category.MESSAGE, 2, Integer.MAX_VALUE, O_TAGS, O_DATE, O_FLAGS, O_NO_VALIDATION), ADMIN_AUTHENTICATE("adminAuthenticate", "aa", "{admin-name} {admin-password}", "authenticate as an admin. can only be used by an admin", Category.ADMIN, 2, 2, O_URL), AUTHENTICATE("authenticate", "a", "{auth-account-name} {password} [target-account-name]", "authenticate as account and open target mailbox; target defaults to auth account if unspecified", Category.MISC, 2, 3, O_URL), AUTO_COMPLETE("autoComplete", "ac", "{query}", "contact auto autocomplete", Category.CONTACT, 1, 1, O_VERBOSE), AUTO_COMPLETE_GAL("autoCompleteGal", "acg", "{query}", "gal auto autocomplete", Category.CONTACT, 1, 1, O_VERBOSE), CHECK_RIGHT( "checkRight", "ckr", "{name} {right}", "check if the user has the specified right on target.", Category.RIGHT, 2, 2, O_VERBOSE), CREATE_CONTACT("createContact", "cct", "[attr1 value1 [attr2 value2...]]", "create contact", Category.CONTACT, 2, Integer.MAX_VALUE, O_FOLDER, O_IGNORE, O_TAGS), CREATE_FOLDER( "createFolder", "cf", "{folder-path}", "create folder", Category.FOLDER, 1, 1, O_VIEW, O_COLOR, O_FLAGS, O_URL), CREATE_IDENTITY( "createIdentity", "cid", "{identity-name} [attr1 value1 [attr2 value2...]]", "create identity", Category.ACCOUNT, 1, Integer.MAX_VALUE), CREATE_MOUNTPOINT( "createMountpoint", "cm", "{folder-path} {owner-id-or-name} {remote-item-id-or-path} [{reminder-enabled (0*|1)}]", "create mountpoint", Category.FOLDER, 3, 4, O_VIEW, O_COLOR, O_FLAGS), CREATE_SEARCH_FOLDER( "createSearchFolder", "csf", "{folder-path} {query}", "create search folder", Category.FOLDER, 2, 2, O_SORT, O_TYPES, O_COLOR), CREATE_SIGNATURE( "createSignature", "csig", "{signature-name} [signature-value}", "create signature", Category.ACCOUNT, 2, 2), CREATE_TAG( "createTag", "ct", "{tag-name}", "create tag", Category.TAG, 1, 1, O_COLOR), DELETE_CONTACT( "deleteContact", "dct", "{contact-ids}", "hard delete contact(s)", Category.CONTACT, 1, 1), DELETE_CONVERSATION( "deleteConversation", "dc", "{conv-ids}", "hard delete conversastion(s)", Category.CONVERSATION, 1, 1), DELETE_ITEM( "deleteItem", "di", "{item-ids}", "hard delete item(s)", Category.ITEM, 1, 1), DELETE_IDENTITY( "deleteIdentity", "did", "{identity-name}", "delete an identity", Category.ACCOUNT, 1, 1), DELETE_INCOMING_FILTER_RULE( "deleteFilterRule", "dfrl", "{name}", "delete incoming filter rule", Category.FILTER, 1, 1), DELETE_OUTGOING_FILTER_RULE( "deleteOutgoingFilterRule", "dofrl", "{name}", "delete outgoing filter rule", Category.FILTER, 1, 1), DELETE_FOLDER( "deleteFolder", "df", "{folder-path}", "hard delete a folder (and subfolders)", Category.FOLDER, 1, 1), DELETE_MESSAGE( "deleteMessage", "dm", "{msg-ids}", "hard delete message(s)", Category.MESSAGE, 1, 1), DELETE_SIGNATURE( "deleteSignature", "dsig", "{signature-name|signature-id}", "delete signature", Category.ACCOUNT, 1, 1), DELETE_TAG( "deleteTag", "dt", "{tag-name}", "delete a tag", Category.TAG, 1, 1), DUMPSTER_DELETE_ITEM( "dumpsterDeleteItem", "ddi", "{item-ids}", "permanently delete item(s) from the dumpster", Category.ITEM, 1, 1), EMPTY_DUMPSTER( "emptyDumpster", null, "", "empty the dumpster", Category.MISC, 0, 0), EMPTY_FOLDER( "emptyFolder", "ef", "{folder-path}", "empty all the items in a folder (including subfolders)", Category.FOLDER, 1, 1), EXIT( "exit", "quit", "", "exit program", Category.MISC, 0, 0), FLAG_CONTACT( "flagContact", "fct", "{contact-ids} [0|1*]", "flag/unflag contact(s)", Category.CONTACT, 1, 2), FLAG_CONVERSATION( "flagConversation", "fc", "{conv-ids} [0|1*]", "flag/unflag conversation(s)", Category.CONVERSATION, 1, 2), FLAG_ITEM( "flagItem", "fi", "{item-ids} [0|1*]", "flag/unflag item(s)", Category.ITEM, 1, 2), FLAG_MESSAGE( "flagMessage", "fm", "{msg-ids} [0|1*]", "flag/unflag message(s)", Category.MESSAGE, 1, 2), GET_ALL_CONTACTS( "getAllContacts", "gact", "[attr1 [attr2...]]", "get all contacts", Category.CONTACT, 0, Integer.MAX_VALUE, O_VERBOSE, O_FOLDER), GET_ALL_FOLDERS( "getAllFolders", "gaf", "", "get all folders", Category.FOLDER, 0, 0, O_VERBOSE), GET_ALL_TAGS( "getAllTags", "gat", "", "get all tags", Category.TAG, 0, 0, O_VERBOSE), GET_APPOINTMENT_SUMMARIES( "getAppointmentSummaries", "gaps", "{start-date-spec} {end-date-spec} {folder-path}", "get appointment summaries", Category.APPOINTMENT, 2, 3, O_VERBOSE), GET_CONTACTS( "getContacts", "gct", "{contact-ids} [attr1 [attr2...]]", "get contact(s)", Category.CONTACT, 1, Integer.MAX_VALUE, O_VERBOSE), GET_CONVERSATION( "getConversation", "gc", "{conv-id}", "get a converation", Category.CONVERSATION, 1, 1, O_VERBOSE), GET_IDENTITIES( "getIdentities", "gid", "", "get all identites", Category.ACCOUNT, 0, 0, O_VERBOSE), GET_INCOMING_FILTER_RULES( "getFilterRules", "gfrl", "", "get incoming filter rules", Category.FILTER, 0, 0), GET_OUTGOING_FILTER_RULES( "getOutgoingFilterRules", "gofrl", "", "get outgoing filter rules", Category.FILTER, 0, 0), GET_FOLDER( "getFolder", "gf", "{folder-path}", "get folder", Category.FOLDER, 1, 1, O_VERBOSE), GET_FOLDER_REQUEST( "getFolderRequest", "gfr", "{folder-id}", "get folder request (always issues a GetFolderRequest)", Category.FOLDER, 1, 1, O_VERBOSE), GET_FOLDER_GRANT( "getFolderGrant", "gfg", "{folder-path}", "get folder grants", Category.FOLDER, 1, 1, O_VERBOSE), GET_MESSAGE( "getMessage", "gm", "{msg-id}", "get a message", Category.MESSAGE, 1, 1, O_VERBOSE), GET_MAILBOX_SIZE( "getMailboxSize", "gms", "", "get mailbox size", Category.MISC, 0, 0, O_VERBOSE), GET_RIGHTS( "getRights", "gr", "[right1 [right2...]]", "get rights currently granted", Category.RIGHT, 0, Integer.MAX_VALUE, O_VERBOSE), GET_REST_URL( "getRestURL", "gru", "{relative-path}", "do a GET on a REST URL relative to the mailbox", Category.MISC, 1, 1, O_OUTPUT_FILE, O_START_TIME, O_END_TIME, O_URL), GET_SIGNATURES( "getSignatures", "gsig", "", "get all signatures", Category.ACCOUNT, 0, 0, O_VERBOSE), GRANT_RIGHT( "grantRight", "grr", "{account {name}|group {name}|domain {name}||all|public|guest {email} [{password}]|key {email} [{accesskey}] {[-]right}}", "allow or deny a right to a grantee or a group of grantee. to deny a right, put a '-' in front of the right", Category.RIGHT, 2, 4), HELP( "help", "?", "commands", "return help on a group of commands, or all commands. Use -v for detailed help.", Category.MISC, 0, 1, O_VERBOSE), IMPORT_URL_INTO_FOLDER( "importURLIntoFolder", "iuif", "{folder-path} {url}", "add the contents to the remote feed at {target-url} to the folder", Category.FOLDER, 2, 2), LIST_RIGHTS( "listRights", "lr", "", "list and describe all rights that can be granted", Category.RIGHT, 0, 0, O_VERBOSE), MARK_CONVERSATION_READ( "markConversationRead", "mcr", "{conv-ids} [0|1*]", "mark conversation(s) as read/unread", Category.CONVERSATION, 1, 2), MARK_CONVERSATION_SPAM( "markConversationSpam", "mcs", "{conv} [0|1*] [{dest-folder-path}]", "mark conversation as spam/not-spam, and optionally move", Category.CONVERSATION, 1, 3), MARK_ITEM_READ( "markItemRead", "mir", "{item-ids} [0|1*]", "mark item(s) as read/unread", Category.ITEM, 1, 2), MARK_FOLDER_READ( "markFolderRead", "mfr", "{folder-path}", "mark all items in a folder as read", Category.FOLDER, 1, 1), MARK_MESSAGE_READ( "markMessageRead", "mmr", "{msg-ids} [0|1*]", "mark message(s) as read/unread", Category.MESSAGE, 1, 2), MARK_MESSAGE_SPAM( "markMessageSpam", "mms", "{msg} [0|1*] [{dest-folder-path}]", "mark a message as spam/not-spam, and optionally move", Category.MESSAGE, 1, 3), MARK_TAG_READ( "markTagRead", "mtr", "{tag-name}", "mark all items with this tag as read", Category.TAG, 1, 1), MODIFY_CONTACT( "modifyContactAttrs", "mcta", "{contact-id} [attr1 value1 [attr2 value2...]]", "modify a contact", Category.CONTACT, 3, Integer.MAX_VALUE, O_REPLACE, O_IGNORE), MODIFY_INCOMING_FILTER_RULE( "modifyFilterRule", "mfrl", "{name} [*active|inactive] [any|*all] {conditions}+ {actions}+", "modify incoming filter rule", Category.FILTER, 2, Integer.MAX_VALUE), MODIFY_OUTGOING_FILTER_RULE( "modifyOutgoingFilterRule", "mofrl", "{name} [*active|inactive] [any|*all] {conditions}+ {actions}+", "modify outgoing filter rule", Category.FILTER, 2, Integer.MAX_VALUE), MODIFY_FOLDER_CHECKED( "modifyFolderChecked", "mfch", "{folder-path} [0|1*]", "modify whether a folder is checked in the UI", Category.FOLDER, 1, 2), MODIFY_FOLDER_COLOR( "modifyFolderColor", "mfc", "{folder-path} {new-color}", "modify a folder's color", Category.FOLDER, 2, 2), MODIFY_FOLDER_EXCLUDE_FREE_BUSY( "modifyFolderExcludeFreeBusy", "mfefb", "{folder-path} [0|1*]", "change whether folder is excluded from free-busy", Category.FOLDER, 1, 2), MODIFY_FOLDER_FLAGS( "modifyFolderFlags", "mff", "{folder-path} {folder-flags}", "replaces the flags on the folder (subscribed, checked, etc.)", Category.FOLDER, 2, 2), MODIFY_FOLDER_GRANT( "modifyFolderGrant", "mfg", "{folder-path} {account {name}|group {name}|cos {name}|domain {name}|all|public|guest {email}|key {email} [{accesskey}] {permissions|none}}", "add/remove a grant to a folder", Category.FOLDER, 3, 5), MODIFY_FOLDER_URL( "modifyFolderURL", "mfu", "{folder-path} {url}", "modify a folder's URL", Category.FOLDER, 2, 2), MODIFY_IDENTITY( "modifyIdentity", "mid", "{identity-name} [attr1 value1 [attr2 value2...]]", "modify an identity", Category.ACCOUNT, 1, Integer.MAX_VALUE), MODIFY_ITEM_FLAGS( "modifyItemFlags", "mif", "{item-ids} {item-flags}", "replaces the flags on the items (answered, unread, flagged, etc.)", Category.ITEM, 2, 2), MODIFY_MOUNTPOINT_ENABLE_SHARED_REMINDER( "modifyMountpointEnableSharedReminder", "mmesr", "{mountpoint-path} {0|1}", "enable/disable appointment/task reminder on shared calendar", Category.FOLDER, 2, 2), MODIFY_SIGNATURE( "modifySignature", "msig", "{signature-name|signature-id} {value}", "modify signature value", Category.ACCOUNT, 2, 2), MODIFY_TAG_COLOR( "modifyTagColor", "mtc", "{tag-name} {tag-color}", "modify a tag's color", Category.TAG, 2, 2), MOVE_CONTACT( "moveContact", "mct", "{contact-ids} {dest-folder-path}", "move contact(s) to a new folder", Category.CONTACT, 2, 2), MOVE_CONVERSATION( "moveConversation", "mc", "{conv-ids} {dest-folder-path}", "move conversation(s) to a new folder", Category.CONVERSATION, 2, 2), MOVE_ITEM( "moveItem", "mi", "{item-ids} {dest-folder-path}", "move item(s) to a new folder", Category.ITEM, 2, 2), MOVE_MESSAGE( "moveMessage", "mm", "{msg-ids} {dest-folder-path}", "move message(s) to a new folder", Category.MESSAGE, 2, 2), NOOP( "noOp", "no", "", "do a NoOp SOAP call to the server", Category.MISC, 0, 1), POST_REST_URL( "postRestURL", "pru", "{relative-path} {file-name}", "do a POST on a REST URL relative to the mailbox", Category.MISC, 2, 2, O_CONTENT_TYPE, O_IGNORE_ERROR, O_PRESERVE_ALARMS, O_URL), RECOVER_ITEM( "recoverItem", "ri", "{item-ids} {dest-folder-path}", "recover item(s) from the dumpster to a folder", Category.ITEM, 2, 2), RENAME_FOLDER( "renameFolder", "rf", "{folder-path} {new-folder-path}", "rename folder", Category.FOLDER, 2, 2), RENAME_SIGNATURE( "renameSignature", "rsig", "{signature-name|signature-id} {new-name}", "rename signature", Category.ACCOUNT, 2, 2), RENAME_TAG( "renameTag", "rt", "{tag-name} {new-tag-name}", "rename tag", Category.TAG, 2, 2), REVOKE_RIGHT( "revokeRight", "rvr", "{account {name}|group {name}|domain {name}||all|public|guest {email} [{password}]|key {email} [{accesskey}] {[-]right}}", "revoke a right previously granted to a grantee or a group of grantees. to revoke a denied right, put a '-' in front of the right", Category.RIGHT, 2, 4), SEARCH( "search", "s", "{query}", "perform search", Category.SEARCH, 0, 1, O_LIMIT, O_SORT, O_TYPES, O_VERBOSE, O_CURRENT, O_NEXT, O_PREVIOUS, O_DUMPSTER), SEARCH_CONVERSATION( "searchConv", "sc", "{conv-id} {query}", "perform search on conversation", Category.SEARCH, 0, 2, O_LIMIT, O_SORT, O_TYPES, O_VERBOSE, O_CURRENT, O_NEXT, O_PREVIOUS), SELECT_MAILBOX( "selectMailbox", "sm", "{name}", "select a different mailbox. can only be used by an admin", Category.ADMIN, 1, 1, O_AUTH, O_AS_ADMIN), SYNC_FOLDER( "syncFolder", "sf", "{folder-path}", "synchronize folder's contents to the remote feed specified by folder's {url}", Category.FOLDER, 1, 1), TAG_CONTACT( "tagContact", "tct", "{contact-ids} {tag-name} [0|1*]", "tag/untag contact(s)", Category.CONTACT, 2, 3), TAG_CONVERSATION( "tagConversation", "tc", "{conv-ids} {tag-name} [0|1*]", "tag/untag conversation(s)", Category.CONVERSATION, 2, 3), TAG_ITEM( "tagItem", "ti", "{item-ids} {tag-name} [0|1*]", "tag/untag item(s)", Category.ITEM, 2, 3), TAG_MESSAGE( "tagMessage", "tm", "{msg-ids} {tag-name} [0|1*]", "tag/untag message(s)", Category.MESSAGE, 2, 3), WHOAMI( "whoami", null, "", "show current auth'd/opened mailbox", Category.MISC, 0, 0); private String mName; private String mAlias; private String mSyntax; private String mHelp; private Option[] mOpt; private Category mCat; private int mMinArgLength = 0; private int mMaxArgLength = Integer.MAX_VALUE; public String getName() { return mName; } public String getAlias() { return mAlias; } public String getSyntax() { return mSyntax; } public String getHelp() { return mHelp; } public Category getCategory() { return mCat; } public boolean hasHelp() { return mSyntax != null; } public boolean checkArgsLength(String args[]) { int len = args == null ? 0 : args.length; return len >= mMinArgLength && len <= mMaxArgLength; } // it appears we have to create a new Option object everytime we call parse! // otherwise strange things were happening (i.e., looks like there is state // being stored in an Option, when one would assume they are immutable. public Options getOptions() { Options opts = new Options(); for (Option o : mOpt) { opts.addOption(o.getOpt(), o.getLongOpt(), o.hasArg(), o.getDescription()); } return opts; } public String getCommandHelp() { String commandName; String alias = getAlias(); if (alias != null) commandName = String.format("%s(%s)", getName(), alias); else commandName = getName(); StringBuilder sb = new StringBuilder(); sb.append(String.format(" %-38s %s%n", commandName, getHelp())); return sb.toString(); } public String getFullUsage() { String commandName; String alias = getAlias(); if (alias != null) { commandName = String.format("%s(%s)", getName(), alias); } else { commandName = getName(); } @SuppressWarnings("rawtypes") Collection opts = getOptions().getOptions(); StringBuilder sb = new StringBuilder(); sb.append(String.format(" %-28s %s%n", commandName, (opts.size() > 0 ? "[opts] " : "") + getSyntax())); //if (opts.size() > 0) // stdout.println(); for (Object o : opts) { Option opt = (Option) o; String arg = opt.hasArg() ? " <arg>" : ""; String shortOpt = opt.getOpt(); String longOpt = opt.getLongOpt(); String optStr; if (shortOpt != null) optStr = String.format(" -%s/--%s%s", shortOpt, longOpt, arg); else optStr = String.format(" --%s%s", longOpt, arg); sb.append(String.format(" %-30s %s%n", optStr, opt.getDescription())); } //sb.append(String.format("%n %s%n%n", getHelp())); return sb.toString(); } private Command(String name, String alias, String syntax, String help, Category cat, int minArgLength, int maxArgLength, Option... opts) { mName = name; mAlias = alias != null && alias.length() > 0 ? alias : null; mSyntax = syntax; mHelp = help; mCat = cat; mMinArgLength = minArgLength; mMaxArgLength = maxArgLength; mOpt = opts; } } private static final long KBYTES = 1024; private static final long MBYTES = 1024 * 1024; private static final long GBYTES = 1024 * 1024 * 1024; private String formatSize(long size) { if (size > GBYTES) { return String.format("%.2f GB", (((double) size) / GBYTES)); } else if (size > MBYTES) { return String.format("%.2f MB", (((double) size) / MBYTES)); } else if (size > KBYTES) { return String.format("%.2f KB", (((double) size) / KBYTES)); } else { return String.format("%d B", size); } } private void addCommand(Command command) { String name = command.getName().toLowerCase(); if (mCommandIndex.get(name) != null) { throw new RuntimeException("duplicate command: " + name); } mCommandIndex.put(name, command); String alias = command.getAlias(); if (alias != null) { alias = alias.toLowerCase(); if (mCommandIndex.get(alias) != null) { throw new RuntimeException("duplicate command: " + alias); } mCommandIndex.put(alias, command); } } private void initCommands() { mCommandIndex = new HashMap<String, Command>(); for (Command c : Command.values()) { addCommand(c); } } private Command lookupCommand(String command) { return mCommandIndex.get(command.toLowerCase()); } public ZMailboxUtil() { initCommands(); } private ZMailbox.Options getMailboxOptions(SoapProvisioning prov, String authAccount, String targetAccount, int lifetimeSeconds) throws ServiceException { ZMailbox.Options options; AccountBy targetBy = StringUtil.isUUID(targetAccount) ? AccountBy.id : AccountBy.name; if (!StringUtil.isNullOrEmpty(authAccount)) { AccountBy authBy = StringUtil.isUUID(authAccount) ? AccountBy.id : AccountBy.name; SoapAccountInfo sai = prov.getAccountInfo(authBy, authAccount); DelegateAuthResponse dar = prov.delegateAuth(authBy, authAccount, lifetimeSeconds > 0 ? lifetimeSeconds : Constants.SECONDS_PER_DAY); options = new ZMailbox.Options(dar.getAuthToken(), sai.getAdminSoapURL()); } else { SoapAccountInfo sai = prov.getAccountInfo(targetBy, targetAccount); options = new ZMailbox.Options(mAdminAuthToken, sai.getAdminSoapURL()); } options.setTargetAccount(targetAccount); options.setTargetAccountBy(targetBy); return options; } /* * called when sm command is embeded in zmprov */ public void selectMailbox(String authAccount, SoapProvisioning prov) throws ServiceException { selectMailbox(authAccount, authAccount, prov); } public void selectMailbox(String authAccount, String targetAccount, SoapProvisioning prov) throws ServiceException { boolean success = false; try { if (prov == null) { throw ZClientException.CLIENT_ERROR("can only select mailbox after adminAuthenticate", null); } else if (mProv == null) { mProv = prov; } mMbox = null; //make sure to null out current value so if select fails any further ops will fail setAuthAccountName(authAccount); setTargetAccountName(targetAccount); ZMailbox.Options options = getMailboxOptions(prov, authAccount, targetAccount, Constants.SECONDS_PER_DAY); options.setTimeout(mTimeout); if (prov.soapGetTransportDebugListener() != null) { options.setDebugListener(prov.soapGetTransportDebugListener()); } else { // use the same debug listener used by zmprov options.setHttpDebugListener(prov.soapGetHttpTransportDebugListener()); } mMbox = ZMailbox.getMailbox(options); dumpMailboxConnect(); mPrompt = String.format("mbox %s> ", mMbox.getName()); mSearchParams = null; mConvSearchParams = null; mConvSearchResult = null; mIndexToId.clear(); // TODO: clear all other mailbox-state success = true; } finally { if (!success) { setAuthAccountName(null); setTargetAccountName(null); } } } private void adminAuth(String name, String password, String uri) throws ServiceException { mAdminAccountName = name; mPassword = password; SoapTransport.DebugListener listener = mDebug ? this : null; mProv = new SoapProvisioning(); mProv.soapSetURI(ZMailbox.resolveUrl(uri, true)); if (listener != null) { mProv.soapSetTransportDebugListener(listener); } mProv.soapAdminAuthenticate(name, password); mAdminAuthToken = mProv.getAuthToken(); } private void adminAuth(ZAuthToken zat, String uri) throws ServiceException { SoapTransport.DebugListener listener = mDebug ? this : null; mProv = new SoapProvisioning(); mProv.soapSetURI(ZMailbox.resolveUrl(uri, true)); if (listener != null) { mProv.soapSetTransportDebugListener(listener); } mProv.soapAdminAuthenticate(zat); } private void auth(String authAccountName, String password, String targetAccountName, String uri) throws ServiceException { boolean success = false; try { mPassword = password; ZMailbox.Options options = new ZMailbox.Options(); options.setAccount(authAccountName); options.setAccountBy(StringUtil.isUUID(authAccountName) ? AccountBy.id : AccountBy.name); options.setPassword(mPassword); options.setTargetAccount(targetAccountName); options.setTargetAccountBy(StringUtil.isUUID(targetAccountName) ? AccountBy.id : AccountBy.name); options.setUri(ZMailbox.resolveUrl(uri, false)); options.setDebugListener(mDebug ? this : null); options.setTimeout(mTimeout); mMbox = ZMailbox.getMailbox(options); mPrompt = String.format("mbox %s> ", mMbox.getName()); setAuthAccountName(authAccountName); setTargetAccountName(targetAccountName); dumpMailboxConnect(); success = true; } finally { if (!success) { setAuthAccountName(null); setTargetAccountName(null); } } } static class Stats { int numMessages; int numUnread; } private void computeStats(ZFolder f, Stats s) { s.numMessages += f.getMessageCount(); s.numUnread += f.getUnreadCount(); for (ZFolder c : f.getSubFolders()) { computeStats(c, s); } } private void dumpMailboxConnect() throws ServiceException { if (!mInteractive) return; if (StringUtil.isNullOrEmpty(mTargetAccountName)) { stdout.format("no mailbox opened%n"); if (!StringUtil.isNullOrEmpty(mAdminAccountName)) { stdout.format("authenticated as %s (admin)%n", mAdminAccountName); } else { stdout.format("not authenticated%n"); } return; } Stats s = new Stats(); computeStats(mMbox.getUserRoot(), s); stdout.format("mailbox: %s, size: %s, messages: %d, unread: %d%n", mMbox.getName(), formatSize(mMbox.getSize()), s.numMessages, s.numUnread); if (StringUtil.equalIgnoreCase(mTargetAccountName, mAuthAccountName) || !StringUtil.isNullOrEmpty(mAuthAccountName)) { stdout.format("authenticated as %s%n", mAuthAccountName); } else { stdout.format("authenticated as %s (admin)%n", mAdminAccountName); } } public void initMailbox() throws ServiceException { if (mPassword == null && mAdminAuthToken == null) return; if (mAdminAccountName != null) { adminAuth(mAdminAccountName, mPassword, mUrl); } else if (mAdminAuthToken != null) { adminAuth(mAdminAuthToken, mUrl); } if (mTargetAccountName == null) { mAuthAccountName = null; return; } if (mAdminAccountName != null) { selectMailbox(mAuthAccountName, mTargetAccountName, mProv); } else { auth(mAuthAccountName, mPassword, mTargetAccountName, mUrl); } } private ZTag lookupTag(String idOrName) throws ServiceException { ZTag tag = mMbox.getTagByName(idOrName); if (tag == null) { tag = mMbox.getTagById(idOrName); } if (tag == null) { throw ZClientException.CLIENT_ERROR("unknown tag: " + idOrName, null); } return tag; } /** * takes a list of ids or names, and trys to resolve them all to valid tag ids * * @param idsOrNames * @return * @throws SoapFaultException */ private String lookupTagIds(String idsOrNames) throws ServiceException { StringBuilder ids = new StringBuilder(); for (String t : idsOrNames.split(",")) { ZTag tag = lookupTag(t); if (ids.length() > 0) { ids.append(","); } ids.append(tag.getId()); } return ids.toString(); } /** * takes a list of ids, and trys to resolve them all to tag names * */ private String lookupTagNames(String ids) throws ServiceException { StringBuilder names = new StringBuilder(); for (String tid : ids.split(",")) { ZTag tag = lookupTag(tid); if (names.length() > 0) { names.append(", "); } names.append(tag == null ? tid : tag.getName()); } return names.toString(); } private String lookupFolderId(String pathOrId) throws ServiceException { return lookupFolderId(pathOrId, false); } Pattern sTargetConstraint = Pattern.compile("\\{(.*)\\}$"); private String id(String indexOrId) throws ServiceException { Matcher m = sTargetConstraint.matcher(indexOrId); if (m.find()) { indexOrId = m.replaceAll(""); } StringBuilder ids = new StringBuilder(); for (String t : indexOrId.split(",")) { if (t.length() > 1 && t.charAt(0) == '#') { t = t.substring(1); //stdout.println(t); int i = t.indexOf('-'); if (i != -1) { int start = Integer.parseInt(t.substring(0, i)); int end = Integer.parseInt(t.substring(i + 1, t.length())); for (int j = start; j <= end; j++) { String id = mIndexToId.get(j); if (id == null) { throw ZClientException.CLIENT_ERROR("unknown index: " + t, null); } if (ids.length() > 0) { ids.append(","); } ids.append(id); } } else { String id = mIndexToId.get(Integer.parseInt(t)); if (id == null) { throw ZClientException.CLIENT_ERROR("unknown index: " + t, null); } if (ids.length() > 0) { ids.append(","); } ids.append(id); } } else { if (ids.length() > 0) { ids.append(","); } ids.append(t); } } return ids.toString(); } private String lookupFolderId(String pathOrId, boolean parent) throws ServiceException { if (parent && pathOrId != null) { pathOrId = ZMailbox.getParentPath(pathOrId); } if (pathOrId == null || pathOrId.length() == 0) { return null; } ZFolder folder = mMbox.getFolderById(pathOrId); if (folder == null) { folder = mMbox.getFolderByPath(pathOrId); } if (folder == null) { throw ZClientException.CLIENT_ERROR("unknown folder: " + pathOrId, null); } return folder.getId(); } private ZFolder lookupFolder(String pathOrId) throws ServiceException { if (pathOrId == null || pathOrId.length() == 0) { return null; } ZFolder folder = mMbox.getFolderById(pathOrId); if (folder == null) { folder = mMbox.getFolderByPath(pathOrId); } if (folder == null) { throw ZClientException.CLIENT_ERROR("unknown folder: " + pathOrId, null); } return folder; } private String param(String[] args, int index, String defaultValue) { return args.length > index ? args[index] : defaultValue; } private boolean paramb(String[] args, int index, boolean defaultValue) { return args.length > index ? args[index].equals("1") : defaultValue; } private String param(String[] args, int index) { return param(args, index, null); } private ZTag.Color tagColorOpt() throws ServiceException { String color = mCommandLine.getOptionValue(O_COLOR.getOpt()); return color == null ? null : ZTag.Color.fromString(color); } private String tagsOpt() throws ServiceException { String tags = mCommandLine.getOptionValue(O_TAGS.getOpt()); return tags == null ? null : lookupTagIds(tags); } private ZFolder.Color folderColorOpt() throws ServiceException { String color = mCommandLine.getOptionValue(O_COLOR.getOpt()); return color == null ? null : ZFolder.Color.fromString(color); } private ZFolder.View folderViewOpt() { String view = mCommandLine.getOptionValue(O_VIEW.getOpt()); return view == null ? null : ZFolder.View.fromString(view); } private String flagsOpt() { return mCommandLine.getOptionValue(O_FLAGS.getOpt()); } private String urlOpt(boolean admin) { String url = mCommandLine.getOptionValue(O_URL.getOpt()); return (url == null && admin) ? mUrl : url; } private String outputFileOpt() { return mCommandLine.getOptionValue(O_OUTPUT_FILE.getOpt()); } private String contentTypeOpt() { return mCommandLine.getOptionValue(O_CONTENT_TYPE.getOpt()); } private boolean ignoreAndContinueOnErrorOpt() { return mCommandLine.hasOption(O_IGNORE_ERROR.getOpt()); } private boolean preserveAlarmsOpt() { return mCommandLine.hasOption(O_PRESERVE_ALARMS.getLongOpt()); } private String startTimeOpt() { return mCommandLine.getOptionValue(O_START_TIME.getLongOpt()); } private String endTimeOpt() { return mCommandLine.getOptionValue(O_END_TIME.getLongOpt()); } private String typesOpt() throws ServiceException { String t = mCommandLine.getOptionValue(O_TYPES.getOpt()); return t == null ? null : ZSearchParams.getCanonicalTypes(t); } private long dateOpt(long def) { String ds = mCommandLine.getOptionValue(O_DATE.getOpt()); return ds == null ? def : Long.parseLong(ds); } private String folderOpt() { return mCommandLine.getOptionValue(O_FOLDER.getOpt()); } private boolean ignoreOpt() { return mCommandLine.hasOption(O_IGNORE.getOpt()); } private boolean verboseOpt() { return mCommandLine.hasOption(O_VERBOSE.getOpt()); } private boolean currrentOpt() { return mCommandLine.hasOption(O_CURRENT.getOpt()); } private boolean nextOpt() { return mCommandLine.hasOption(O_NEXT.getOpt()); } private boolean previousOpt() { return mCommandLine.hasOption(O_PREVIOUS.getOpt()); } private boolean firstOpt() { return mCommandLine.hasOption(O_FIRST.getOpt()); } private String beforeOpt() { return mCommandLine.getOptionValue(O_BEFORE.getOpt()); } private String afterOpt() { return mCommandLine.getOptionValue(O_AFTER.getOpt()); } private SearchSortBy searchSortByOpt() throws ServiceException { String sort = mCommandLine.getOptionValue(O_SORT.getOpt()); return sort == null ? null : SearchSortBy.fromString(sort); } private boolean validateOpt() { return !mCommandLine.hasOption(O_NO_VALIDATION.getLongOpt()); } enum ExecuteStatus { OK, EXIT }; public ExecuteStatus execute(String argsIn[]) throws ServiceException, IOException { mCommand = lookupCommand(argsIn[0]); // shift them over for parser String args[] = new String[argsIn.length - 1]; System.arraycopy(argsIn, 1, args, 0, args.length); if (mCommand == null) throw ZClientException .CLIENT_ERROR("Unknown command: (" + argsIn[0] + ") Type: 'help commands' for a list", null); try { mCommandLine = mParser.parse(mCommand.getOptions(), args, true); args = mCommandLine.getArgs(); } catch (ParseException e) { usage(); return ExecuteStatus.OK; } if (!mCommand.checkArgsLength(args)) { usage(); return ExecuteStatus.OK; } if (mCommand != Command.EXIT && mCommand != Command.HELP && mCommand != Command.AUTHENTICATE && mCommand != Command.ADMIN_AUTHENTICATE && mCommand != Command.SELECT_MAILBOX && mCommand != Command.WHOAMI) { if (mMbox == null) { throw ZClientException.CLIENT_ERROR( "no mailbox opened. select one with authenticate/adminAuthenticate/selectMailbox", null); } } switch (mCommand) { case AUTO_COMPLETE: doAutoComplete(args); break; case AUTO_COMPLETE_GAL: doAutoCompleteGal(args); break; case AUTHENTICATE: doAuth(args); break; case ADD_INCOMING_FILTER_RULE: doAddIncomingFilterRule(args); break; case ADD_OUTGOING_FILTER_RULE: doAddOutgoingFilterRule(args); break; case ADD_MESSAGE: doAddMessage(args); break; case ADMIN_AUTHENTICATE: doAdminAuth(args); break; case CHECK_RIGHT: doCheckRight(args); break; case CREATE_CONTACT: String ccId = mMbox .createContact(lookupFolderId(folderOpt()), tagsOpt(), getContactMap(args, 0, !ignoreOpt())) .getId(); stdout.println(ccId); break; case CREATE_IDENTITY: mMbox.createIdentity(new ZIdentity(args[0], getMultiMap(args, 1))); break; case CREATE_FOLDER: doCreateFolder(args); break; case CREATE_MOUNTPOINT: doCreateMountpoint(args); break; case CREATE_SEARCH_FOLDER: doCreateSearchFolder(args); break; case CREATE_SIGNATURE: doCreateSignature(args); break; case CREATE_TAG: ZTag ct = mMbox.createTag(args[0], tagColorOpt()); stdout.println(ct.getId()); break; case DELETE_CONTACT: mMbox.deleteContact(args[0]); break; case DELETE_CONVERSATION: mMbox.deleteConversation(id(args[0]), param(args, 1)); break; case DELETE_INCOMING_FILTER_RULE: doDeleteIncomingFilterRule(args); break; case DELETE_OUTGOING_FILTER_RULE: doDeleteOutgoingFilterRule(args); break; case DELETE_FOLDER: mMbox.deleteFolder(lookupFolderId(args[0])); break; case DELETE_IDENTITY: mMbox.deleteIdentity(args[0]); break; case DELETE_ITEM: mMbox.deleteItem(id(args[0]), param(args, 1)); break; case DELETE_MESSAGE: mMbox.deleteMessage(id(args[0])); break; case DELETE_SIGNATURE: mMbox.deleteSignature(lookupSignatureId(args[0])); break; case DELETE_TAG: mMbox.deleteTag(lookupTag(args[0]).getId()); break; case DUMPSTER_DELETE_ITEM: mMbox.dumpsterDeleteItem(id(args[0])); break; case EMPTY_DUMPSTER: mMbox.emptyDumpster(); break; case EMPTY_FOLDER: mMbox.emptyFolder(lookupFolderId(args[0])); break; case EXIT: return ExecuteStatus.EXIT; //break; case FLAG_CONTACT: mMbox.flagContact(id(args[0]), paramb(args, 1, true)); break; case FLAG_CONVERSATION: mMbox.flagConversation(id(args[0]), paramb(args, 1, true), param(args, 2)); break; case FLAG_ITEM: mMbox.flagItem(id(args[0]), paramb(args, 1, true), param(args, 2)); break; case FLAG_MESSAGE: mMbox.flagMessage(id(args[0]), paramb(args, 1, true)); break; case GET_ALL_CONTACTS: doGetAllContacts(args); break; case GET_CONTACTS: doGetContacts(args); break; case GET_IDENTITIES: doGetIdentities(); break; case GET_SIGNATURES: doGetSignatures(); break; case GET_ALL_FOLDERS: doGetAllFolders(); break; case GET_ALL_TAGS: doGetAllTags(); break; case GET_APPOINTMENT_SUMMARIES: doGetAppointmentSummaries(args); break; case GET_CONVERSATION: doGetConversation(args); break; case GET_INCOMING_FILTER_RULES: doGetIncomingFilterRules(); break; case GET_OUTGOING_FILTER_RULES: doGetOutgoingFilterRules(); break; case GET_FOLDER: doGetFolder(args); break; case GET_FOLDER_REQUEST: doGetFolderRequest(args); break; case GET_FOLDER_GRANT: doGetFolderGrant(args); break; case GET_MAILBOX_SIZE: if (verboseOpt()) stdout.format("%d%n", mMbox.getSize()); else stdout.format("%s%n", formatSize(mMbox.getSize())); break; case GET_MESSAGE: doGetMessage(args); break; case GET_RIGHTS: doGetRights(args); break; case GET_REST_URL: doGetRestURL(args); break; case GRANT_RIGHT: doGrantRight(args); break; case HELP: doHelp(args); break; case IMPORT_URL_INTO_FOLDER: mMbox.importURLIntoFolder(lookupFolderId(args[0]), args[1]); break; case LIST_RIGHTS: doListRights(); break; case MARK_CONVERSATION_READ: mMbox.markConversationRead(id(args[0]), paramb(args, 1, true), param(args, 2)); break; case MARK_ITEM_READ: mMbox.markItemRead(id(args[0]), paramb(args, 1, true), param(args, 2)); break; case MARK_FOLDER_READ: mMbox.markFolderRead(lookupFolderId(args[0])); break; case MARK_MESSAGE_READ: mMbox.markMessageRead(id(args[0]), paramb(args, 1, true)); break; case MARK_CONVERSATION_SPAM: mMbox.markConversationSpam(id(args[0]), paramb(args, 1, true), lookupFolderId(param(args, 2)), param(args, 3)); break; case MARK_MESSAGE_SPAM: mMbox.markMessageSpam(id(args[0]), paramb(args, 1, true), lookupFolderId(param(args, 2))); break; case MARK_TAG_READ: mMbox.markTagRead(lookupTag(args[0]).getId()); break; case MODIFY_CONTACT: doModifyContact(args); break; case MODIFY_INCOMING_FILTER_RULE: doModifyIncomingFilterRule(args); break; case MODIFY_OUTGOING_FILTER_RULE: doModifyOutgoingFilterRule(args); break; case MODIFY_FOLDER_CHECKED: mMbox.modifyFolderChecked(lookupFolderId(args[0]), paramb(args, 1, true)); break; case MODIFY_FOLDER_COLOR: mMbox.modifyFolderColor(lookupFolderId(args[0]), ZFolder.Color.fromString(args[1])); break; case MODIFY_FOLDER_EXCLUDE_FREE_BUSY: mMbox.modifyFolderExcludeFreeBusy(lookupFolderId(args[0]), paramb(args, 1, true)); break; case MODIFY_FOLDER_GRANT: doModifyFolderGrant(args); break; case MODIFY_FOLDER_FLAGS: mMbox.updateFolder(lookupFolderId(args[0]), null, null, null, null, args[1], null); break; case MODIFY_FOLDER_URL: mMbox.modifyFolderURL(lookupFolderId(args[0]), args[1]); break; case MODIFY_IDENTITY: mMbox.modifyIdentity(new ZIdentity(args[0], getMultiMap(args, 1))); break; case MODIFY_ITEM_FLAGS: mMbox.updateItem(id(args[0]), null, null, args[1], null); break; case MODIFY_MOUNTPOINT_ENABLE_SHARED_REMINDER: mMbox.enableSharedReminder(lookupFolderId(args[0]), paramb(args, 1, false)); break; case MODIFY_SIGNATURE: doModifySignature(args); break; case MODIFY_TAG_COLOR: mMbox.modifyTagColor(lookupTag(args[0]).getId(), Color.fromString(args[1])); break; case MOVE_CONVERSATION: mMbox.moveConversation(id(args[0]), lookupFolderId(param(args, 1)), param(args, 2)); break; case MOVE_ITEM: mMbox.moveItem(id(args[0]), lookupFolderId(param(args, 1)), param(args, 2)); break; case MOVE_MESSAGE: mMbox.moveMessage(id(args[0]), lookupFolderId(param(args, 1))); break; case MOVE_CONTACT: mMbox.moveContact(id(args[0]), lookupFolderId(param(args, 1))); break; case NOOP: doNoop(args); break; case POST_REST_URL: doPostRestURL(args); break; case RECOVER_ITEM: mMbox.recoverItem(id(args[0]), lookupFolderId(param(args, 1))); break; case RENAME_FOLDER: mMbox.renameFolder(lookupFolderId(args[0]), args[1]); break; case RENAME_SIGNATURE: doRenameSignature(args); break; case RENAME_TAG: mMbox.renameTag(lookupTag(args[0]).getId(), args[1]); break; case REVOKE_RIGHT: doRevokeRight(args); break; case SEARCH: doSearch(args); break; case SEARCH_CONVERSATION: doSearchConv(args); break; case SELECT_MAILBOX: doSelectMailbox(args); break; case SYNC_FOLDER: mMbox.syncFolder(lookupFolderId(args[0])); break; case TAG_CONTACT: mMbox.tagContact(id(args[0]), lookupTag(args[1]).getId(), paramb(args, 2, true)); break; case TAG_CONVERSATION: mMbox.tagConversation(id(args[0]), lookupTag(args[1]).getId(), paramb(args, 2, true), param(args, 3)); break; case TAG_ITEM: mMbox.tagItem(id(args[0]), lookupTag(args[1]).getId(), paramb(args, 2, true), param(args, 3)); break; case TAG_MESSAGE: mMbox.tagMessage(id(args[0]), lookupTag(args[1]).getId(), paramb(args, 2, true)); break; case WHOAMI: dumpMailboxConnect(); break; default: throw ZClientException.CLIENT_ERROR("Unhandled command: (" + mCommand.name() + ")", null); } return ExecuteStatus.OK; } private final ZEventHandler mTraceHandler = new TraceHandler(); private static class TraceHandler extends ZEventHandler { @Override public void handleRefresh(ZRefreshEvent refreshEvent, ZMailbox mailbox) { stdout.println("ZRefreshEvent: " + refreshEvent); } @Override public void handleModify(ZModifyEvent event, ZMailbox mailbox) { stdout.println(event.getClass().getSimpleName() + ": " + event); } @Override public void handleCreate(ZCreateEvent event, ZMailbox mailbox) { stdout.println(event.getClass().getSimpleName() + ": " + event); } @Override public void handleDelete(ZDeleteEvent event, ZMailbox mailbox) { stdout.println("ZDeleteEvent: " + event); } } private void doNoop(String[] args) throws ServiceException { if (args.length == 0 || !args[0].equals("-t")) mMbox.noOp(); else { mMbox.addEventHandler(mTraceHandler); while (true) { stdout.println("NoOp: " + LdapDateUtil.toGeneralizedTime(new Date())); mMbox.noOp(); try { Thread.sleep(5000); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } //testCreateAppt(); } /* <CreateAppointmentRequest xmlns="urn:zimbraMail"> <m d="1173202005230" l="10"> <inv> <comp status="CONF" fb="B" transp="O" allDay="0" name="test yearly"> <s tz="(GMT-08.00) Pacific Time (US & Canada)" d="20070308T130000"/> <e tz="(GMT-08.00) Pacific Time (US & Canada)" d="20070308T150000"/> <or a="user1@slapshot.liquidsys.com"/> <recur> <add> <rule freq="YEA"> <interval ival="1"/> </rule> </add> </recur> </comp></inv><su>test yearly</su><mp ct="multipart/alternative"><mp ct="text/plain"><content></content></mp><mp ct="text/html"><content><html><body></body></html></content></mp></mp></m></CreateAppointmentRequest> */ /* private void testCreateAppt() throws ServiceException { ZMailbox.ZOutgoingMessage message = new ZOutgoingMessage(); message.setSubject("test zclient API"); message.setMessagePart(new MessagePart("text/plain", "this is da body")); ZInvite invite = new ZInvite(); ZComponent comp = new ZComponent(); comp.setStart(new ZDateTime("20070309T170000", mMbox.getPrefs().getTimeZoneWindowsId())); comp.setEnd(new ZDateTime("20070309T210000", mMbox.getPrefs().getTimeZoneWindowsId())); comp.setOrganizer(new ZOrganizer(mMbox.getName())); comp.setName("test zclient API"); comp.setLocation("Zimbra"); invite.getComponents().add(comp); ZAppointmentResult response = mMbox.createAppointment(ZFolder.ID_CALENDAR, null, message, invite, null); stdout.printf("calItemId(%s) inviteId(%s)%n", response.getCalItemId(), response.getInviteId()); } */ private void doGetAppointmentSummaries(String args[]) throws ServiceException { long startTime = DateUtil.parseDateSpecifier(args[0], new Date().getTime()); long endTime = DateUtil.parseDateSpecifier(args[1], (new Date().getTime()) + Constants.MILLIS_PER_WEEK); String folderId = args.length == 3 ? lookupFolderId(args[2]) : null; List<ZApptSummaryResult> results = mMbox.getApptSummaries(null, startTime, endTime, new String[] { folderId }, TimeZone.getDefault(), ZSearchParams.TYPE_APPOINTMENT); if (results.size() != 1) return; ZApptSummaryResult result = results.get(0); stdout.print("["); boolean first = true; for (ZAppointmentHit appt : result.getAppointments()) { if (!first) stdout.println(","); stdout.print(ZJSONObject.toString(appt)); if (first) first = false; } stdout.println("]"); } /* addFilterRule(afrl) --before {existing-rule-name} --after {existing-rule-name} --first --last {name} [*active|inactive] [any|*all] {conditions}+ {actions}+ */ private void doAddIncomingFilterRule(String[] args) throws ServiceException { ZFilterRules rules = mMbox.getIncomingFilterRules(); doAddFilterRule(args, rules); mMbox.saveIncomingFilterRules(rules); } private void doAddOutgoingFilterRule(String[] args) throws ServiceException { ZFilterRules rules = mMbox.getOutgoingFilterRules(); doAddFilterRule(args, rules); mMbox.saveOutgoingFilterRules(rules); } private void doAddFilterRule(String[] args, ZFilterRules rules) throws ServiceException { ZFilterRule newRule = ZFilterRule.parseFilterRule(args); List<ZFilterRule> list = rules.getRules(); if (firstOpt()) { list.add(0, newRule); } else if (afterOpt() != null) { boolean found = false; String name = afterOpt(); for (int i = 0; i < list.size(); i++) { found = list.get(i).getName().equalsIgnoreCase(name); if (found) { if (i + 1 >= list.size()) list.add(newRule); else list.add(i + 1, newRule); break; } } if (!found) { throw ZClientException.CLIENT_ERROR("can't find rule: " + name, null); } } else if (beforeOpt() != null) { String name = beforeOpt(); boolean found = false; for (int i = 0; i < list.size(); i++) { found = list.get(i).getName().equalsIgnoreCase(name); if (found) { list.add(i, newRule); break; } } if (!found) { throw ZClientException.CLIENT_ERROR("can't find rule: " + name, null); } } else { // add to end list.add(newRule); } } private void doModifyIncomingFilterRule(String[] args) throws ServiceException { ZFilterRules rules = mMbox.getIncomingFilterRules(true); doModifyFilterRule(args, rules); mMbox.saveIncomingFilterRules(rules); } private void doModifyOutgoingFilterRule(String[] args) throws ServiceException { ZFilterRules rules = mMbox.getOutgoingFilterRules(true); doModifyFilterRule(args, rules); mMbox.saveOutgoingFilterRules(rules); } private static void doModifyFilterRule(String[] args, ZFilterRules rules) throws ServiceException { ZFilterRule modifiedRule = ZFilterRule.parseFilterRule(args); List<ZFilterRule> list = rules.getRules(); for (int i = 0; i < list.size(); i++) { if (list.get(i).getName().equalsIgnoreCase(modifiedRule.getName())) { list.set(i, modifiedRule); return; } } throw ZClientException.CLIENT_ERROR("can't find rule: " + args[0], null); } private void doDeleteIncomingFilterRule(String[] args) throws ServiceException { ZFilterRules rules = mMbox.getIncomingFilterRules(true); doDeleteFilterRule(args, rules); mMbox.saveIncomingFilterRules(rules); } private void doDeleteOutgoingFilterRule(String[] args) throws ServiceException { ZFilterRules rules = mMbox.getOutgoingFilterRules(true); doDeleteFilterRule(args, rules); mMbox.saveOutgoingFilterRules(rules); } private static void doDeleteFilterRule(String[] args, ZFilterRules rules) throws ServiceException { String name = args[0]; List<ZFilterRule> list = rules.getRules(); for (int i = 0; i < list.size(); i++) { if (list.get(i).getName().equalsIgnoreCase(name)) { list.remove(i); return; } } throw ZClientException.CLIENT_ERROR("can't find rule: " + args, null); } private void doGetIncomingFilterRules() throws ServiceException { ZFilterRules rules = mMbox.getIncomingFilterRules(true); printFilterRules(rules); } private void doGetOutgoingFilterRules() throws ServiceException { ZFilterRules rules = mMbox.getOutgoingFilterRules(true); printFilterRules(rules); } private static void printFilterRules(ZFilterRules rules) { for (ZFilterRule r : rules.getRules()) { stdout.println(r.generateFilterRule()); } } private String getGranteeDisplay(GranteeType type) { switch (type) { case usr: return "account"; case grp: return "group"; case cos: return "cos"; case pub: return "public"; case all: return "all"; case dom: return "domain"; case guest: return "guest"; case key: return "key"; default: return "unknown"; } } /* * after bug 40178, grantee names are not returned in the refresh block, * they are only returned in GetFolderResponse. * lookupFolder returns the cached folder (inited from the refresh block), after that, * do a GetFolderRequest with the folder id to get the full grant info that includes grantee name. */ private ZFolder getFolderWithFullGrantInfo(String pathOrId) throws ServiceException { ZFolder f = lookupFolder(pathOrId); return mMbox.getFolderRequestById(f.getId()); } private void doGetFolderGrant(String[] args) throws ServiceException { ZFolder f = getFolderWithFullGrantInfo(args[0]); if (verboseOpt()) { StringBuilder sb = new StringBuilder(); for (ZGrant g : f.getGrants()) { if (sb.length() > 0) sb.append(",\n"); sb.append(g.dump()); } stdout.format("[%n%s%n]%n", sb.toString()); } else { String format = "%11.11s %8.8s %s%n"; stdout.format(format, "Permissions", "Type", "Display"); stdout.format(format, "-----------", "--------", "-------"); for (ZGrant g : f.getGrants()) { GranteeType gt = g.getGranteeType(); String dn = (gt == GranteeType.all || gt == GranteeType.pub) ? "" : ((gt == GranteeType.guest || gt == GranteeType.key) ? g.getGranteeId() : (g.getGranteeName() != null ? g.getGranteeName() : g.getGranteeId())); stdout.format(format, g.getPermissions(), getGranteeDisplay(g.getGranteeType()), dn); } } } private GranteeType getGranteeType(String name) throws ServiceException { if (name.equalsIgnoreCase("account")) return GranteeType.usr; else if (name.equalsIgnoreCase("group")) return GranteeType.grp; else if (name.equalsIgnoreCase("cos")) return GranteeType.cos; else if (name.equalsIgnoreCase("public")) return GranteeType.pub; else if (name.equalsIgnoreCase("all")) return GranteeType.all; else if (name.equalsIgnoreCase("domain")) return GranteeType.dom; else if (name.equalsIgnoreCase("guest")) return GranteeType.guest; else if (name.equalsIgnoreCase("key")) return GranteeType.key; else throw ZClientException.CLIENT_ERROR("unknown grantee type: " + name, null); } private void doModifyFolderGrant(String[] args) throws ServiceException { String folderId = lookupFolderId(args[0], false); GranteeType type = getGranteeType(args[1]); String grantee = null; String perms = null; String password = null; switch (type) { case usr: case grp: case cos: case dom: if (args.length != 4) { throw ZClientException.CLIENT_ERROR("not enough args", null); } grantee = args[2]; perms = args[3]; break; case pub: grantee = GuestAccount.GUID_PUBLIC; perms = args[2]; break; case all: grantee = GuestAccount.GUID_AUTHUSER; perms = args[2]; break; case guest: if (args.length != 4 && args.length != 5) throw ZClientException.CLIENT_ERROR("not enough args", null); grantee = args[2]; if (args.length == 5) { password = args[3]; perms = args[4]; } else { password = null; perms = args[3]; } break; case key: if (args.length != 4 && args.length != 5) throw ZClientException.CLIENT_ERROR("not enough args", null); grantee = args[2]; if (args.length == 5) { password = args[3]; perms = args[4]; } else { perms = args[3]; } break; } boolean revoke = (perms != null && (perms.equalsIgnoreCase("none") || perms.length() == 0)); if (revoke) { // convert grantee to grantee id if it is a name ZFolder f = getFolderWithFullGrantInfo(folderId); String zid = null; for (ZGrant g : f.getGrants()) { if (grantee.equalsIgnoreCase(g.getGranteeName()) || grantee.equalsIgnoreCase(g.getGranteeId())) { zid = g.getGranteeId(); break; } } if (zid != null || (type == GranteeType.all || type == GranteeType.pub)) { if (zid != null) grantee = zid; mMbox.modifyFolderRevokeGrant(folderId, grantee); } else { // zid is null /* * It could be we are trying to revoke a grant on a sub folder. * e.g. /top/sub * mfg /top account user2 r * mfg /top/sub account user2 none * or * mfg /top account all r * mfg /top/sub account user3 none * * or simply just want to grant "no right" to a user * e.g. * mfg /top account user2 none * * If this is the case zid wil be null because there is no such * grant on the specified folder. Just let it go through by issuing * a grant action, instead of revoke. */ mMbox.modifyFolderGrant(folderId, type, grantee, "", password); } } else { mMbox.modifyFolderGrant(folderId, type, grantee, perms, password); } } private void doListRights() throws ServiceException { for (Right r : RightManager.getInstance().getAllUserRights().values()) { stdout.println(" " + r.getName() + ": " + r.getDesc()); stdout.println(); } } private void doGetRights(String[] args) throws ServiceException { if (verboseOpt()) { StringBuilder sb = new StringBuilder(); for (ZAce g : mMbox.getRights(args)) { if (sb.length() > 0) sb.append(",\n"); sb.append(g.dump()); } stdout.format("[%n%s%n]%n", sb.toString()); } else { String format = "%16.16s %8.8s %s%n"; stdout.format(format, "Right", "Type", "Display"); stdout.format(format, "----------------", "--------", "-------"); List<ZAce> result = mMbox.getRights(args); Comparator<ZAce> comparator = new Comparator<ZAce>() { @Override public int compare(ZAce a, ZAce b) { // sort by right -> grantee type -> grantee name String aKey = a.getRight() + a.getGranteeTypeSortOrder() + (a.getGranteeName() == null ? "" : a.getGranteeName()); String bKey = b.getRight() + b.getGranteeTypeSortOrder() + (b.getGranteeName() == null ? "" : b.getGranteeName()); int order = aKey.compareTo(bKey); if (order == 0) // a grantee is denied and allowed, not likely, but put the deny before allow if such entry does exist order = a.getDeny() ? -1 : 1; return order; } }; Collections.sort(result, comparator); for (ZAce ace : result) { stdout.format(format, ace.getRightDisplay(), ace.getGranteeTypeDisplay(), ace.getGranteeName()); } } stdout.println(); } private ZAce getAceFromArgs(String[] args) throws ServiceException { ZAce.GranteeType type = ZAce.getGranteeTypeFromDisplay(args[0]); String granteeName = null; String granteeId = null; String right = null; boolean deny = false; String secret = null; switch (type) { case usr: case grp: case dom: if (args.length != 3) throw ZClientException.CLIENT_ERROR("wrong number of args", null); granteeName = args[1]; right = args[2]; break; case all: if (args.length != 2) throw ZClientException.CLIENT_ERROR("wrong number of args", null); granteeId = GuestAccount.GUID_AUTHUSER; right = args[1]; break; case pub: if (args.length != 2) throw ZClientException.CLIENT_ERROR("wrong number of args", null); granteeId = GuestAccount.GUID_PUBLIC; right = args[1]; break; case gst: if (args.length != 4) throw ZClientException.CLIENT_ERROR("wrong number of args", null); granteeName = args[1]; secret = args[2]; right = args[3]; break; case key: if (args.length != 3 && args.length != 4) throw ZClientException.CLIENT_ERROR("wrong number of args", null); granteeName = args[1]; if (args.length == 3) { right = args[2]; } else { secret = args[2]; right = args[3]; } break; } if (right.charAt(0) == '-') { deny = true; right = right.substring(1); } return new ZAce(type, granteeId, granteeName, right, deny, secret); } private void doGrantRight(String[] args) throws ServiceException { ZAce ace = getAceFromArgs(args); List<ZAce> granted = mMbox.grantRight(ace); if (granted.size() == 0) stdout.println(" granted no right"); else { stdout.println(" granted: "); for (ZAce g : granted) stdout.println( " " + g.getGranteeTypeDisplay() + " " + g.getGranteeName() + " " + g.getRightDisplay()); } } private void doRevokeRight(String[] args) throws ServiceException { ZAce ace = getAceFromArgs(args); ZAce.GranteeType granteeType = ace.getGranteeType(); if (granteeType == ZAce.GranteeType.usr || granteeType == ZAce.GranteeType.grp || granteeType == ZAce.GranteeType.dom) { // convert grantee to grantee id if it is a name String zid = null; String granteeName = ace.getGranteeName(); String[] rights = new String[] { ace.getRight() }; for (ZAce g : mMbox.getRights(rights)) { if (granteeName.equalsIgnoreCase(g.getGranteeName()) || granteeName.equalsIgnoreCase(g.getGranteeId())) { zid = g.getGranteeId(); break; } } if (zid == null) throw ZClientException.CLIENT_ERROR("no such grant", null); ace.setGranteeId(zid); } List<ZAce> revoked = mMbox.revokeRight(ace); if (revoked.size() == 0) stdout.println(" revoked no right"); else { stdout.println(" revoked: "); for (ZAce r : revoked) stdout.println( " " + r.getGranteeTypeDisplay() + " " + r.getGranteeName() + " " + r.getRightDisplay()); } } private void doCheckRight(String[] args) throws ServiceException { String user = args[0]; List<String> rights = new ArrayList<String>(); rights.add(args[1]); // support only one right in CLI boolean allow = mMbox.checkRights(user, rights); stdout.println((allow ? "allowed" : "not allowed")); } private void doAdminAuth(String[] args) throws ServiceException { adminAuth(args[0], args[1], urlOpt(true)); } private void doAuth(String[] args) throws ServiceException { auth(args[0], args[1], param(args, 2, args[0]), urlOpt(true)); } private void doSelectMailbox(String[] args) throws ServiceException { String targetAccount = args[0]; String authAccount; if (mCommandLine.hasOption(O_AS_ADMIN.getLongOpt())) { authAccount = null; } else { authAccount = mCommandLine.getOptionValue(O_AUTH.getLongOpt()); if (StringUtil.isNullOrEmpty(authAccount)) { authAccount = targetAccount; } } selectMailbox(authAccount, targetAccount, mProv); } private static PrintWriter stdout; private static PrintWriter stderr; private static Session mSession; static { try { stdout = new PrintWriter(new OutputStreamWriter(System.out, "UTF-8"), true); stderr = new PrintWriter(new OutputStreamWriter(System.err, "UTF-8"), true); } catch (UnsupportedEncodingException e) { e.printStackTrace(); System.exit(1); } Properties props = new Properties(); props.setProperty("mail.mime.address.strict", "false"); mSession = Session.getInstance(props); // Assume that most malformed base64 errors occur due to incorrect delimiters, // as opposed to errors in the data itself. See bug 11213 for more details. System.setProperty("mail.mime.base64.ignoreerrors", "true"); } private void addMessage(String folderId, String flags, String tags, long date, File file, boolean validate) throws ServiceException, IOException { //String aid = mMbox.uploadAttachments(new File[] {file}, 5000); InputStream in = new BufferedInputStream(new FileInputStream(file)); // Buffering required for gzip check long sizeHint = -1; if (ByteUtil.isGzipped(in)) { in = new GZIPInputStream(in); } else { sizeHint = file.length(); } byte[] data = ByteUtil.getContent(in, (int) sizeHint); if (validate && !EmailUtil.isRfc822Message(new ByteArrayInputStream(data))) { throw ZClientException.CLIENT_ERROR(file.getPath() + " does not contain a valid RFC 822 message", null); } try { if (date == -1) { MimeMessage mm = new ZMimeMessage(mSession, new SharedByteArrayInputStream(data)); Date d = mm.getSentDate(); if (d != null) date = d.getTime(); else date = 0; } } catch (MessagingException e) { date = 0; } String id = mMbox.addMessage(folderId, flags, tags, date, data, false); stdout.format("%s (%s)%n", id, file.getPath()); } private void doAddMessage(String[] args) throws ServiceException, IOException { String folderId = lookupFolderId(args[0], false); String tags = tagsOpt(); String flags = flagsOpt(); long date = dateOpt(-1); boolean validate = validateOpt(); for (int i = 1; i < args.length; i++) { File file = new File(args[i]); if (file.isDirectory()) { // TODO: should we recurse? for (File child : file.listFiles()) { if (child.isFile()) addMessage(folderId, flags, tags, date, child, validate); } } else { addMessage(folderId, flags, tags, date, file, validate); } } } private String emailAddrs(List<ZEmailAddress> addrs) { StringBuilder sb = new StringBuilder(); for (ZEmailAddress e : addrs) { if (sb.length() > 0) sb.append(", "); sb.append(e.getDisplay()); } return sb.toString(); } private void doCreateFolder(String args[]) throws ServiceException { ZFolder cf = mMbox.createFolder(lookupFolderId(args[0], true), ZMailbox.getBasePath(args[0]), folderViewOpt(), folderColorOpt(), flagsOpt(), urlOpt(false)); stdout.println(cf.getId()); } private void doCreateSignature(String args[]) throws ServiceException { ZSignature sig = new ZSignature(args[0], args[1]); stdout.println(mMbox.createSignature(sig)); } private void doModifySignature(String args[]) throws ServiceException { ZSignature sig = lookupSignature(args[0]); ZSignature modSig = new ZSignature(sig.getId(), sig.getName(), args[1]); mMbox.modifySignature(modSig); } private void doRenameSignature(String args[]) throws ServiceException { ZSignature sig = lookupSignature(args[0]); ZSignature modSig = new ZSignature(sig.getId(), args[1], sig.getValue()); mMbox.modifySignature(modSig); } private ZSignature lookupSignature(String idOrName) throws ServiceException { for (ZSignature sig : mMbox.getSignatures()) { if (sig.getName().equalsIgnoreCase(idOrName) || sig.getId().equalsIgnoreCase(idOrName)) return sig; } throw ZClientException.CLIENT_ERROR("unknown signature: " + idOrName, null); } private String lookupSignatureId(String idOrName) throws ServiceException { if (StringUtil.isUUID(idOrName)) { return idOrName; } else { for (ZSignature sig : mMbox.getSignatures()) { if (sig.getName().equalsIgnoreCase(idOrName)) { return sig.getId(); } } throw ZClientException.CLIENT_ERROR("unknown signature: " + idOrName, null); } } private void doCreateSearchFolder(String args[]) throws ServiceException { ZSearchFolder csf = mMbox.createSearchFolder(lookupFolderId(args[0], true), ZMailbox.getBasePath(args[0]), args[1], typesOpt(), searchSortByOpt(), folderColorOpt()); stdout.println(csf.getId()); } private void doCreateMountpoint(String args[]) throws ServiceException { String cmPath = args[0]; String cmOwner = args[1]; String cmItem = args[2]; boolean reminderEnabled = paramb(args, 3, false); OwnerBy ownerBy = OwnerBy.BY_NAME; if (StringUtil.isUUID(cmOwner)) { ownerBy = OwnerBy.BY_ID; } SharedItemBy sharedItemBy = SharedItemBy.BY_PATH; String sharedItem = cmItem; int colonAt = cmItem.indexOf(':'); if (colonAt != -1 && colonAt != 0 && colonAt != cmItem.length() - 1) { String itemOwnerId = cmItem.substring(0, colonAt); String itemId = cmItem.substring(colonAt + 1); if (StringUtil.isUUID(itemOwnerId)) { sharedItemBy = SharedItemBy.BY_ID; sharedItem = itemId; } } ZMountpoint cm = mMbox.createMountpoint(lookupFolderId(cmPath, true), ZMailbox.getBasePath(cmPath), folderViewOpt(), folderColorOpt(), flagsOpt(), ownerBy, cmOwner, sharedItemBy, sharedItem, reminderEnabled); stdout.println(cm.getId()); } private void doEnableSharedReminder(String args[]) throws ServiceException { } private void doSearch(String[] args) throws ServiceException { if (currrentOpt()) { doSearchRedisplay(); return; } else if (previousOpt()) { doSearchPrevious(); return; } else if (nextOpt()) { doSearchNext(); return; } else if (args.length == 0) { usage(); return; } mSearchParams = new ZSearchParams(args[0]); // [limit {limit}] [sortby {sortBy}] [types {types}] mSearchParams.setLimit(getOptLimit()); SearchSortBy sortBy = searchSortByOpt(); mSearchParams.setSortBy(sortBy != null ? sortBy : SearchSortBy.dateDesc); String types = typesOpt(); mSearchParams.setTypes(types != null ? types : ZSearchParams.TYPE_CONVERSATION); mSearchParams.setInDumpster(mCommandLine.hasOption(O_DUMPSTER.getLongOpt())); mIndexToId.clear(); mSearchPage = 0; ZSearchPagerResult pager = mMbox.search(mSearchParams, mSearchPage, false, false); //stdout.println(result); dumpSearch(pager.getResult(), verboseOpt()); } private int getOptLimit() throws ServiceException { int limit; try { limit = Integer.parseInt(mCommandLine.getOptionValue(O_LIMIT.getOpt(), "25")); } catch (NumberFormatException e) { throw ZClientException.CLIENT_ERROR("limit must be a number", e); } if (limit <= 0 || limit > 1000) { throw ZClientException.CLIENT_ERROR("limit must be 1-1000", null); } return limit; } private void doSearchRedisplay() throws ServiceException { if (mSearchParams == null) return; ZSearchPagerResult pager = mMbox.search(mSearchParams, mSearchPage, true, false); mSearchPage = pager.getActualPage(); if (pager.getResult().getHits().size() == 0) return; dumpSearch(pager.getResult(), verboseOpt()); } private void doSearchNext() throws ServiceException { if (mSearchParams == null) return; ZSearchPagerResult pager = mMbox.search(mSearchParams, ++mSearchPage, true, false); mSearchPage = pager.getActualPage(); if (pager.getResult().getHits().size() == 0) return; dumpSearch(pager.getResult(), verboseOpt()); } private void doSearchPrevious() throws ServiceException { if (mSearchParams == null || mSearchPage == 0) return; ZSearchPagerResult pager = mMbox.search(mSearchParams, --mSearchPage, true, false); mSearchPage = pager.getActualPage(); if (pager.getResult().getHits().size() == 0) return; dumpSearch(pager.getResult(), verboseOpt()); } String mConvSearchConvId; private void doSearchConv(String[] args) throws ServiceException { if (currrentOpt()) { doSearchConvRedisplay(); return; } else if (previousOpt()) { doSearchConvPrevious(); return; } else if (nextOpt()) { doSearchConvNext(); return; } else if (args.length != 2) { usage(); return; } mConvSearchConvId = id(args[0]); mConvSearchParams = new ZSearchParams(args[1]); // [limit {limit}] [sortby {sortBy}] [types {types}] mConvSearchParams.setLimit(getOptLimit()); SearchSortBy sortBy = searchSortByOpt(); mConvSearchParams.setSortBy(sortBy != null ? sortBy : SearchSortBy.dateDesc); String types = typesOpt(); mConvSearchParams.setTypes(types != null ? types : ZSearchParams.TYPE_CONVERSATION); mIndexToId.clear(); //stdout.println(result); dumpConvSearch(mMbox.searchConversation(mConvSearchConvId, mConvSearchParams), verboseOpt()); } private void doSearchConvRedisplay() { ZSearchResult sr = mConvSearchResult; if (sr == null) return; dumpConvSearch(mConvSearchResult, verboseOpt()); } private void doSearchConvNext() throws ServiceException { ZSearchParams sp = mConvSearchParams; ZSearchResult sr = mConvSearchResult; if (sp == null || sr == null || !sr.hasMore()) return; List<ZSearchHit> hits = sr.getHits(); if (hits.size() == 0) return; sp.setOffset(sp.getOffset() + hits.size()); dumpConvSearch(mMbox.searchConversation(mConvSearchConvId, sp), verboseOpt()); } private void doSearchConvPrevious() throws ServiceException { ZSearchParams sp = mConvSearchParams; ZSearchResult sr = mConvSearchResult; if (sp == null || sr == null || sp.getOffset() == 0) return; sp.setOffset(sp.getOffset() - sr.getHits().size()); dumpConvSearch(mMbox.searchConversation(mConvSearchConvId, sp), verboseOpt()); } private int colWidth(int num) { int i = 1; while (num >= 10) { i++; num /= 10; } return i; } private void dumpSearch(ZSearchResult sr, boolean verbose) { if (verbose) { stdout.println(sr.dump()); return; } int offset = mSearchPage * mSearchParams.getLimit(); int first = offset + 1; int last = offset + sr.getHits().size(); stdout.printf("num: %d, more: %s%n%n", sr.getHits().size(), sr.hasMore()); int width = colWidth(last); if (sr.getHits().size() == 0) { return; } final int FROM_LEN = 20; int id_len = 4; for (ZSearchHit hit : sr.getHits()) { id_len = Math.max(id_len, hit.getId().length()); } Calendar cal = Calendar.getInstance(); String headerFormat = String.format("%%%d.%ds %%%d.%ds %%4s %%-20.20s %%-50.50s %%s%%n", width, width, id_len, id_len); String itemFormat = String.format("%%%d.%ds. %%%d.%ds %%4s %%-20.20s %%-50.50s %%tD %%<tR%%n", width, width, id_len, id_len); stdout.format(headerFormat, "", "Id", "Type", "From", "Subject", "Date"); stdout.format(headerFormat, "", "----------------------------------------------------------------------------------------------------", "----", "--------------------", "--------------------------------------------------", "--------------"); int i = first; for (ZSearchHit hit : sr.getHits()) { if (hit instanceof ZConversationHit) { ZConversationHit ch = (ZConversationHit) hit; cal.setTimeInMillis(ch.getDate()); String sub = ch.getSubject(); String from = emailAddrs(ch.getRecipients()); if (ch.getMessageCount() > 1) { String numMsg = " (" + ch.getMessageCount() + ")"; int space = FROM_LEN - numMsg.length(); from = ((from.length() < space) ? from : from.substring(0, space)) + numMsg; } //if (ch.getFragment() != null || ch.getFragment().length() > 0) // sub += " (" + ch.getFragment()+")"; mIndexToId.put(i, ch.getId()); stdout.format(itemFormat, i++, ch.getId(), "conv", from, sub, cal); } else if (hit instanceof ZContactHit) { ZContactHit ch = (ZContactHit) hit; cal.setTimeInMillis(ch.getDate()); String from = getFirstEmail(ch); String sub = ch.getFileAsStr(); mIndexToId.put(i, ch.getId()); stdout.format(itemFormat, i++, ch.getId(), "cont", from, sub, cal); } else if (hit instanceof ZMessageHit) { ZMessageHit mh = (ZMessageHit) hit; cal.setTimeInMillis(mh.getDate()); String sub = mh.getSubject(); String from = mh.getSender() == null ? "<none>" : mh.getSender().getDisplay(); mIndexToId.put(i, mh.getId()); stdout.format(itemFormat, i++, mh.getId(), "mess", from, sub, cal); } else if (hit instanceof ZAppointmentHit) { ZAppointmentHit ah = (ZAppointmentHit) hit; if (ah.getInstanceExpanded()) { cal.setTimeInMillis(ah.getStartTime()); } else { cal.setTimeInMillis(ah.getHitDate()); } String sub = ah.getName(); String from = "<na>"; mIndexToId.put(i, ah.getId()); stdout.format(itemFormat, i++, ah.getId(), ah.getIsTask() ? "task" : "appo", from, sub, cal); } else if (hit instanceof ZDocumentHit) { ZDocumentHit dh = (ZDocumentHit) hit; ZDocument doc = dh.getDocument(); cal.setTimeInMillis(doc.getModifiedDate()); String name = doc.getName(); String editor = doc.getEditor(); mIndexToId.put(i, dh.getId()); stdout.format(itemFormat, i++, dh.getId(), doc.isWiki() ? "wiki" : "doc", editor, name, cal); } } stdout.println(); } private String getFirstEmail(ZContactHit ch) { if (ch.getEmail() != null) return ch.getEmail(); else if (ch.getEmail2() != null) return ch.getEmail2(); else if (ch.getEmail3() != null) return ch.getEmail3(); else return "<none>"; } private void dumpConvSearch(ZSearchResult sr, boolean verbose) { mConvSearchResult = sr; if (verbose) { stdout.println(sr.dump()); return; } int offset = sr.getOffset(); int first = offset + 1; int last = offset + sr.getHits().size(); stdout.printf("num: %d, more: %s%n%n", sr.getHits().size(), sr.hasMore()); int width = colWidth(last); if (sr.getHits().size() == 0) { return; } int id_len = 4; for (ZSearchHit hit : sr.getHits()) { id_len = Math.max(id_len, hit.getId().length()); } Calendar c = Calendar.getInstance(); String headerFormat = String.format("%%%d.%ds %%%d.%ds %%-20.20s %%-50.50s %%s%%n", width, width, id_len, id_len); //String headerFormat = String.format("%10.10s %-20.20s %-50.50s %-6.6s %s%n"); String itemFormat = String.format("%%%d.%ds. %%%d.%ds %%-20.20s %%-50.50s %%tD %%<tR%%n", width, width, id_len, id_len); //String itemFormat = "%10.10s %-20.20s %-50.50s %-6.6s %tD %5$tR%n"; stdout.format(headerFormat, "", "Id", "From", "Subject", "Date"); stdout.format(headerFormat, "", "----------------------------------------------------------------------------------------------------", "--------------------", "--------------------------------------------------", "--------------"); int i = first; for (ZSearchHit hit : sr.getHits()) { if (hit instanceof ZMessageHit) { ZMessageHit mh = (ZMessageHit) hit; c.setTimeInMillis(mh.getDate()); String sub = mh.getSubject(); String from = mh.getSender().getDisplay(); mIndexToId.put(i, mh.getId()); stdout.format(itemFormat, i++, mh.getId(), from, sub, c); } } stdout.println(); } private void doGetAllTags() throws ServiceException { if (verboseOpt()) { StringBuilder sb = new StringBuilder(); for (String tagName : mMbox.getAllTagNames()) { ZTag tag = mMbox.getTagByName(tagName); if (sb.length() > 0) sb.append(",\n"); sb.append(tag.dump()); } stdout.format("[%n%s%n]%n", sb.toString()); } else { if (mMbox.getAllTagNames().size() == 0) return; String hdrFormat = "%10.10s %10.10s %10.10s %s%n"; stdout.format(hdrFormat, "Id", "Unread", "Color", "Name"); stdout.format(hdrFormat, "----------", "----------", "----------", "----------"); for (String tagName : mMbox.getAllTagNames()) { ZTag tag = mMbox.getTagByName(tagName); stdout.format("%10.10s %10d %10.10s %s%n", tag.getId(), tag.getUnreadCount(), tag.getColor().name(), tag.getName()); } } } private void doDumpFolder(ZFolder folder, boolean recurse) { String path; if (folder instanceof ZSearchFolder) { path = String.format("%s (%s)", folder.getPath(), ((ZSearchFolder) folder).getQuery()); } else if (folder instanceof ZMountpoint) { ZMountpoint mp = (ZMountpoint) folder; path = String.format("%s (%s:%s)", folder.getPath(), mp.getOwnerDisplayName(), mp.getRemoteId()); } else if (folder.getRemoteURL() != null) { path = String.format("%s (%s)", folder.getPath(), folder.getRemoteURL()); } else { path = folder.getPath(); } stdout.format("%10.10s %4.4s %10d %10d %s%n", folder.getId(), folder.getDefaultView().name(), folder.getUnreadCount(), folder.getMessageCount(), path); if (recurse) { for (ZFolder child : folder.getSubFolders()) { doDumpFolder(child, recurse); } } } private void doGetAllFolders() throws ServiceException { if (verboseOpt()) { stdout.println(mMbox.getUserRoot().dump()); } else { String hdrFormat = "%10.10s %4.4s %10.10s %10.10s %s%n"; stdout.format(hdrFormat, "Id", "View", "Unread", "Msg Count", "Path"); stdout.format(hdrFormat, "----------", "----", "----------", "----------", "----------"); doDumpFolder(mMbox.getUserRoot(), true); } } private void doGetFolder(String[] args) throws ServiceException { ZFolder f = lookupFolder(args[0]); stdout.println(f.dump()); /* if (verboseOpt()) { } else { stdout } */ } private void doGetFolderRequest(String[] args) throws ServiceException { ZFolder f = mMbox.getFolderRequestById(args[0]); stdout.println(f); } private void dumpIdentities(List<ZIdentity> identities) { if (verboseOpt()) { stdout.println("["); boolean first = true; for (ZIdentity identity : identities) { if (first) first = false; else stdout.println(","); stdout.println(identity.dump()); } stdout.println("]"); } else { if (identities.size() == 0) return; for (ZIdentity identity : identities) { stdout.println(identity.getName()); } } } private void doGetIdentities() throws ServiceException { dumpIdentities(mMbox.getIdentities()); } private void dumpSignatures(List<ZSignature> signatures) { if (verboseOpt()) { stdout.println("["); boolean first = true; for (ZSignature sig : signatures) { if (first) first = false; else stdout.println(","); stdout.println(sig.dump()); } stdout.println("]"); } else { if (signatures.size() == 0) return; for (ZSignature sig : signatures) { stdout.println(sig.getName()); } } } private void doGetSignatures() throws ServiceException { dumpSignatures(mMbox.getSignatures()); } private void dumpContacts(List<ZContact> contacts) throws ServiceException { if (verboseOpt()) { stdout.println("["); boolean first = true; for (ZContact cn : contacts) { if (first) first = false; else stdout.println(","); stdout.println(cn.dump()); } stdout.println("]"); } else { if (contacts.size() == 0) return; for (ZContact cn : contacts) { dumpContact(cn); } } } private void dumpAutoCompleteMatches(List<ZAutoCompleteMatch> matches) { if (matches.isEmpty()) { stdout.println("no matches"); return; } int idLen = 2; int fidLen = 3; int displayLen = 7; for (ZAutoCompleteMatch match : matches) { String id = match.getId(); if (id != null && id.length() > idLen) idLen = id.length(); String fid = match.getFolderId(); if (fid != null && fid.length() > fidLen) fidLen = fid.length(); String display = match.getDisplayName(); if (display != null && display.length() > displayLen) displayLen = display.length(); } String format = "%7s %7s"; if (verboseOpt()) { format += String.format(" %%%d.%ds", idLen, idLen); format += String.format(" %%%d.%ds", fidLen, fidLen); format += String.format(" %%%d.%ds", displayLen, displayLen); } format += " %s %n"; if (verboseOpt()) { stdout.format(format, "Ranking", "type", "id", "fid", "display", "email"); stdout.println("-------------------------------------------------------------------"); } else { stdout.format(format, "Ranking", "type", "email"); stdout.println("-------------------------------------------------------------------"); } for (ZAutoCompleteMatch match : matches) { if (verboseOpt()) { String folderId = match.getFolderId(); String id = match.getId(); String display = match.getDisplayName(); if (folderId == null) folderId = ""; if (id == null) id = ""; if (display == null) display = ""; stdout.format(format, match.getRanking(), match.getType(), id, folderId, display, match.getEmail()); } else { stdout.format(format, match.getRanking(), match.getType(), match.getEmail()); } } } private void doGetAllContacts(String[] args) throws ServiceException { dumpContacts(mMbox.getAllContacts(lookupFolderId(folderOpt()), null, true, getList(args, 0))); } private void doGetContacts(String[] args) throws ServiceException { dumpContacts(mMbox.getContacts(id(args[0]), null, true, getList(args, 1))); } private void doAutoComplete(String[] args) throws ServiceException { dumpAutoCompleteMatches(mMbox.autoComplete(args[0], 20)); } private void doAutoCompleteGal(String[] args) throws ServiceException { ZSearchGalResult result = mMbox.autoCompleteGal(args[0], GalEntryType.account, 20); dumpContacts(result.getContacts()); } private void dumpConversation(ZConversation conv) throws ServiceException { int first = 1; int last = first + conv.getMessageCount(); int width = colWidth(last); mIndexToId.clear(); stdout.format("%nSubject: %s%n", conv.getSubject()); stdout.format("Id: %s%n", conv.getId()); if (conv.hasTags()) stdout.format("Tags: %s%n", lookupTagNames(conv.getTagIds())); if (conv.hasFlags()) stdout.format("Flags: %s%n", ZConversation.Flag.toNameList(conv.getFlags())); stdout.format("Num-Messages: %d%n%n", conv.getMessageCount()); if (conv.getMessageCount() == 0) return; int id_len = 4; for (ZMessageSummary ms : conv.getMessageSummaries()) { id_len = Math.max(id_len, ms.getId().length()); } String headerFormat = String.format("%%%d.%ds %%%d.%ds %%-15.15s %%-50.50s %%s%%n", width, width, id_len, id_len); String itemFormat = String.format("%%%d.%ds. %%%d.%ds %%-15.15s %%-50.50s %%tD %%<tR%%n", width, width, id_len, id_len); stdout.format(headerFormat, "", "Id", "Sender", "Fragment", "Date"); stdout.format(headerFormat, "", "----------------------------------------------------------------------------------------------------", "---------------", "--------------------------------------------------", "--------------"); int i = first; for (ZMessageSummary ms : conv.getMessageSummaries()) { stdout.format(itemFormat, i, ms.getId(), ms.getSender().getDisplay(), ms.getFragment(), ms.getDate()); mIndexToId.put(i++, ms.getId()); } stdout.println(); } private void doGetConversation(String[] args) throws ServiceException { ZConversation conv = mMbox.getConversation(id(args[0]), Fetch.none); if (verboseOpt()) { stdout.println(conv.dump()); } else { dumpConversation(conv); } } private static int addEmail(StringBuilder sb, String email, int line) { if (sb.length() > 0) { sb.append(','); line++; } if (line > 76) { sb.append("\n"); line = 1; } if (sb.length() > 0) { sb.append(' '); line++; } if (line > 20 && (line + email.length() > 76)) { sb.append("\n "); line = 1; } sb.append(email); line += email.length(); return line; } public static String formatEmail(ZEmailAddress e) { String p = e.getPersonal(); String a = e.getAddress(); if (a == null) a = ""; if (p == null) return String.format("<%s>", a); else return String.format("%s <%s>", p, a); } public static String formatEmail(List<ZEmailAddress> list, String type, int used) { if (list == null || list.size() == 0) return ""; StringBuilder sb = new StringBuilder(); for (ZEmailAddress e : list) { if (e.getType().equalsIgnoreCase(type)) { String fe = formatEmail(e); used = addEmail(sb, fe, used); } } return sb.toString(); } private void doHeader(List<ZEmailAddress> list, String hdrName, String addrType) { String val = formatEmail(list, addrType, hdrName.length() + 2); if (val == null || val.length() == 0) return; stdout.format("%s: %s%n", hdrName, val); } private void dumpMessage(ZMessage msg) throws ServiceException { stdout.format("Id: %s%n", msg.getId()); stdout.format("Conversation-Id: %s%n", msg.getConversationId()); ZFolder f = mMbox.getFolderById(msg.getFolderId()); stdout.format("Folder: %s%n", f == null ? msg.getFolderId() : f.getPath()); stdout.format("Subject: %s%n", msg.getSubject()); doHeader(msg.getEmailAddresses(), "From", ZEmailAddress.EMAIL_TYPE_FROM); doHeader(msg.getEmailAddresses(), "To", ZEmailAddress.EMAIL_TYPE_TO); doHeader(msg.getEmailAddresses(), "Cc", ZEmailAddress.EMAIL_TYPE_CC); stdout.format("Date: %s\n", DateUtil.toRFC822Date(new Date(msg.getReceivedDate()))); if (msg.hasTags()) stdout.format("Tags: %s%n", lookupTagNames(msg.getTagIds())); if (msg.hasFlags()) stdout.format("Flags: %s%n", ZMessage.Flag.toNameList(msg.getFlags())); stdout.format("Size: %s%n", formatSize(msg.getSize())); stdout.println(); if (dumpBody(msg.getMimeStructure())) stdout.println(); } private void doGetMessage(String[] args) throws ServiceException { ZGetMessageParams params = new ZGetMessageParams(); params.setMarkRead(true); params.setId(id(args[0])); ZMessage msg = mMbox.getMessage(params); if (verboseOpt()) { stdout.println(msg.dump()); } else { dumpMessage(msg); } } private boolean dumpBody(ZMimePart mp) { if (mp == null) return false; if (mp.isBody()) { stdout.println(mp.getContent()); return true; } else { for (ZMimePart child : mp.getChildren()) { if (dumpBody(child)) return true; } } return false; } private void doModifyContact(String[] args) throws ServiceException { String id = mMbox .modifyContact(id(args[0]), mCommandLine.hasOption('r'), getContactMap(args, 1, !ignoreOpt())) .getId(); stdout.println(id); } private void dumpContact(ZContact contact) throws ServiceException { stdout.format("Id: %s%n", contact.getId()); ZFolder f = mMbox.getFolderById(contact.getFolderId()); stdout.format("Folder: %s%n", f == null ? contact.getFolderId() : f.getPath()); stdout.format("Date: %tD %<tR%n", contact.getMetaDataChangedDate()); if (contact.hasTags()) stdout.format("Tags: %s%n", lookupTagNames(contact.getTagIds())); if (contact.hasFlags()) stdout.format("Flags: %s%n", ZContact.Flag.toNameList(contact.getFlags())); stdout.format("Revision: %s%n", contact.getRevision()); stdout.format("Attrs:%n"); Map<String, String> attrs = contact.getAttrs(); for (Map.Entry<String, String> entry : attrs.entrySet()) { stdout.format(" %s: %s%n", entry.getKey(), entry.getValue()); } if (contact.isGroup()) { stdout.format("GroupMembers: %s%n", contact.getMembers().keySet()); } stdout.println(); } private Map<String, String> getContactMap(String[] args, int offset, boolean validate) throws ServiceException { Map<String, String> result = getMap(args, offset); if (validate) { for (String name : result.keySet()) { ContactConstants.Attr.fromString(name); } } return result; } private Map<String, Object> getMultiMap(String[] args, int offset) throws ServiceException { try { return StringUtil.keyValueArrayToMultiMap(args, offset); } catch (IllegalArgumentException iae) { throw ZClientException.CLIENT_ERROR("not enough arguments", null); } } private Map<String, String> getMap(String[] args, int offset) throws ServiceException { Map<String, String> attrs = new HashMap<String, String>(); for (int i = offset; i < args.length; i += 2) { String n = args[i]; if (i + 1 >= args.length) throw ZClientException.CLIENT_ERROR("not enough arguments", null); String v = args[i + 1]; attrs.put(n, v); } return attrs; } private List<String> getList(String[] args, int offset) { List<String> attrs = new ArrayList<String>(); for (int i = offset; i < args.length; i++) { attrs.add(args[i]); } return attrs; } public void interactive(BufferedReader in) throws IOException { while (true) { stdout.print(mPrompt); stdout.flush(); String line = StringUtil.readLine(in); if (line == null || line.length() == -1) break; if (mGlobalVerbose) { stdout.println(line); } String args[] = StringUtil.parseLine(line); if (args.length == 0) continue; try { switch (execute(args)) { case EXIT: return; //break; } } catch (ServiceException e) { Throwable cause = e.getCause(); stderr.println("ERROR: " + e.getCode() + " (" + e.getMessage() + ")" + (cause == null ? "" : " (cause: " + cause.getClass().getName() + " " + cause.getMessage() + ")")); if (mGlobalVerbose) e.printStackTrace(stderr); } } } public static void main(String args[]) throws IOException, ServiceException { CliUtil.toolSetup(); SoapTransport.setDefaultUserAgent("zmmailbox", BuildInfo.VERSION); ZMailboxUtil pu = new ZMailboxUtil(); CommandLineParser parser = new GnuParser(); Options options = new Options(); options.addOption("a", "admin", true, "admin account name to auth as"); options.addOption("z", "zadmin", false, "use zimbra admin name/password from localconfig for admin/password"); options.addOption("h", "help", false, "display usage"); options.addOption("f", "file", true, "use file as input stream"); options.addOption("u", "url", true, "http[s]://host[:port] of server to connect to"); options.addOption("r", "protocol", true, "protocol to use for request/response [soap11, soap12, json]"); options.addOption("m", "mailbox", true, "mailbox to open"); options.addOption(null, "auth", true, "account name to auth as; defaults to -m unless -A is used"); options.addOption("A", "admin-priv", false, "execute requests with admin privileges"); options.addOption("p", "password", true, "auth password"); options.addOption("P", "passfile", true, "filename with password in it"); options.addOption("t", "timeout", true, "timeout (in seconds)"); options.addOption("v", "verbose", false, "verbose mode"); options.addOption("d", "debug", false, "debug mode"); options.addOption(SoapCLI.OPT_AUTHTOKEN); options.addOption(SoapCLI.OPT_AUTHTOKENFILE); CommandLine cl = null; boolean err = false; try { cl = parser.parse(options, args, true); } catch (ParseException pe) { stderr.println("error: " + pe.getMessage()); err = true; } if (err || cl.hasOption('h')) { pu.usage(); } try { boolean isAdmin = false; pu.setVerbose(cl.hasOption('v')); if (cl.hasOption('a')) { pu.setAdminAccountName(cl.getOptionValue('a')); pu.setUrl(DEFAULT_ADMIN_URL, true); isAdmin = true; } if (cl.hasOption('z')) { pu.setAdminAccountName(LC.zimbra_ldap_user.value()); pu.setPassword(LC.zimbra_ldap_password.value()); pu.setUrl(DEFAULT_ADMIN_URL, true); isAdmin = true; } if (cl.hasOption(SoapCLI.O_AUTHTOKEN) && cl.hasOption(SoapCLI.O_AUTHTOKENFILE)) pu.usage(); if (cl.hasOption(SoapCLI.O_AUTHTOKEN)) { pu.setAdminAuthToken(ZAuthToken.fromJSONString(cl.getOptionValue(SoapCLI.O_AUTHTOKEN))); pu.setUrl(DEFAULT_ADMIN_URL, true); isAdmin = true; } if (cl.hasOption(SoapCLI.O_AUTHTOKENFILE)) { String authToken = StringUtil.readSingleLineFromFile(cl.getOptionValue(SoapCLI.O_AUTHTOKENFILE)); pu.setAdminAuthToken(ZAuthToken.fromJSONString(authToken)); pu.setUrl(DEFAULT_ADMIN_URL, true); isAdmin = true; } String authAccount, targetAccount; if (cl.hasOption('m')) { if (!cl.hasOption('p') && !cl.hasOption('P') && !cl.hasOption('y') && !cl.hasOption('Y') && !cl.hasOption('z')) { throw ZClientException.CLIENT_ERROR("-m requires one of the -p/-P/-y/-Y/-z options", null); } targetAccount = cl.getOptionValue('m'); } else { targetAccount = null; } if ((cl.hasOption("A") || cl.hasOption("auth")) && !cl.hasOption('m')) { throw ZClientException.CLIENT_ERROR("-A/--auth requires -m", null); } if (cl.hasOption("A")) { if (!isAdmin) { throw ZClientException.CLIENT_ERROR("-A requires admin auth", null); } if (cl.hasOption("auth")) { throw ZClientException.CLIENT_ERROR("-A cannot be combined with --auth", null); } authAccount = null; } else if (cl.hasOption("auth")) { authAccount = cl.getOptionValue("auth"); } else { // default case authAccount = targetAccount; } if (!StringUtil.isNullOrEmpty(authAccount)) pu.setAuthAccountName(authAccount); if (!StringUtil.isNullOrEmpty(targetAccount)) pu.setTargetAccountName(targetAccount); if (cl.hasOption('u')) pu.setUrl(cl.getOptionValue('u'), isAdmin); if (cl.hasOption('p')) pu.setPassword(cl.getOptionValue('p')); if (cl.hasOption('P')) { pu.setPassword(StringUtil.readSingleLineFromFile(cl.getOptionValue('P'))); } if (cl.hasOption('d')) pu.setDebug(true); if (cl.hasOption('t')) pu.setTimeout(cl.getOptionValue('t')); args = cl.getArgs(); pu.setInteractive(args.length < 1); pu.initMailbox(); if (args.length < 1) { InputStream is = null; if (cl.hasOption('f')) { is = new FileInputStream(cl.getOptionValue('f')); } else { if (LC.command_line_editing_enabled.booleanValue()) { try { CliUtil.enableCommandLineEditing(LC.zimbra_home.value() + "/.zmmailbox_history"); } catch (IOException e) { System.err.println("Command line editing will be disabled: " + e); if (pu.mGlobalVerbose) { e.printStackTrace(System.err); } } } is = System.in; // This has to happen last because JLine modifies System.in. } pu.interactive(new BufferedReader(new InputStreamReader(is, "UTF-8"))); } else { pu.execute(args); } } catch (ServiceException e) { Throwable cause = e.getCause(); stderr.println("ERROR: " + e.getCode() + " (" + e.getMessage() + ")" + (cause == null ? "" : " (cause: " + cause.getClass().getName() + " " + cause.getMessage() + ")")); if (pu.mGlobalVerbose) e.printStackTrace(stderr); System.exit(2); } } private void doHelp(String[] args) { Category cat = null; if (args != null && args.length >= 1) { String s = args[0].toUpperCase(); try { cat = Category.valueOf(s); } catch (IllegalArgumentException e) { for (Category c : Category.values()) { if (c.name().startsWith(s)) { cat = c; break; } } } } if (args == null || args.length == 0 || cat == null) { stdout.println(" zmmailbox is used for mailbox management. Try:"); stdout.println(""); for (Category c : Category.values()) { stdout.printf(" zmmailbox help %-15s %s\n", c.name().toLowerCase(), c.getDescription()); } } if (cat != null) { stdout.println(""); for (Command c : Command.values()) { if (!c.hasHelp()) continue; if (cat == Category.COMMANDS || cat == c.getCategory()) { stdout.print(c.getFullUsage()); stdout.println(); } } if (cat.getCategoryHelp() != null) stdout.println(cat.getCategoryHelp()); } stdout.println(); } private long mSendStart; @Override public void receiveSoapMessage(Element envelope) { long end = System.currentTimeMillis(); stdout.printf("======== SOAP RECEIVE =========\n"); stdout.println(envelope.prettyPrint()); stdout.printf("=============================== (%d msecs)\n", end - mSendStart); } @Override public void sendSoapMessage(Element envelope) { mSendStart = System.currentTimeMillis(); stdout.println("========== SOAP SEND =========="); stdout.println(envelope.prettyPrint()); stdout.println("==============================="); } public static String encodeURL(String unencoded) { // Look for a query string. It's supposed to be URL encoded already, so encode only what comes before. String queryString = null; int queryStringStart = unencoded.indexOf('?'); if (queryStringStart != -1) { queryString = unencoded.substring(queryStringStart); unencoded = unencoded.substring(0, queryStringStart); } StringBuilder encoded = new StringBuilder(); String parts[] = unencoded.split("/"); if (parts != null) { for (int i = 0; i < parts.length; i++) { parts[i] = HttpUtil.encodePath(parts[i]); } encoded.append(StringUtil.join("/", parts)); } else { encoded.append(unencoded); } if (queryString != null) { String encodedQuery; try { URI uri = new URI(null, null, null, queryString.substring(1), null); encodedQuery = uri.toString(); } catch (URISyntaxException e) { encodedQuery = queryString; } encoded.append(encodedQuery); } return encoded.toString(); } private void doGetRestURL(String args[]) throws ServiceException { OutputStream os = null; String outputFile = outputFileOpt(); boolean hasOutputFile = outputFile != null; try { os = hasOutputFile ? new FileOutputStream(outputFile) : System.out; mMbox.getRESTResource(encodeURL(args[0]), os, hasOutputFile, startTimeOpt(), endTimeOpt(), mTimeout, urlOpt(false)); } catch (IOException e) { throw ZClientException.IO_ERROR(e.getMessage(), e); } finally { if (hasOutputFile && os != null) try { os.close(); } catch (IOException e) { } } } private void doPostRestURL(String args[]) throws ServiceException { try { File file = new File(args[1]); mMbox.postRESTResource(encodeURL(args[0]), new FileInputStream(file), true, file.length(), contentTypeOpt(), ignoreAndContinueOnErrorOpt(), preserveAlarmsOpt(), mTimeout, urlOpt(false)); } catch (FileNotFoundException e) { throw ZClientException.CLIENT_ERROR("file not found: " + args[1], e); } } }