/* * Copyright 2014 * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at: * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or * implied. See the License for the specific language governing * permissions and limitations under the License. */ package org.openntf.domino.impl; import java.io.IOException; import java.io.InvalidClassException; import java.io.ObjectInput; import java.io.ObjectOutput; import java.util.Arrays; import java.util.Collection; import java.util.Comparator; import java.util.Locale; import java.util.logging.Logger; import lotus.domino.NotesException; import lotus.notes.addins.DominoServer; import org.openntf.domino.Session; import org.openntf.domino.WrapperFactory; import org.openntf.domino.utils.DominoUtils; import org.openntf.domino.utils.Factory; import org.openntf.domino.utils.Factory.SessionType; import org.openntf.domino.utils.Strings; import org.openntf.formula.impl.StringSplitSimple; /** * The class NameODA - alternative implementation for Name * * @author Praml, Steinsiek */ public class NameODA extends BaseNonThreadSafe<org.openntf.domino.Name, lotus.domino.Name, Session> implements org.openntf.domino.Name, Comparable<org.openntf.domino.Name>, Cloneable { @SuppressWarnings("unused") private static final Logger log_ = Logger.getLogger(Name.class.getName()); /*-------------------------------------------------------------------------------------*/ /* * Constructors */ @Deprecated // Needed for Externalization public NameODA() { super(null, Factory.getSession(SessionType.CURRENT), NOTES_NAME); } // Called from WrapperFactory.create protected NameODA(final Session sess, final String name, final String lang) { super(null, sess, NOTES_NAME); _language = null2Empty(lang); parse(name); } // Called from WrapperFactory.wrapLotusObject protected NameODA(final lotus.domino.Name delegate, final Session parent) { super(delegate, parent, NOTES_NAME); try { _language = delegate.getLanguage(); parse(delegate.getCanonical()); } catch (NotesException ne) { DominoUtils.handleException(ne); } finally { // WARNING: Wrapping recycles the caller's object. This may cause issues, if // the Lotus object is used outside openNTF Base.s_recycle(delegate); } } /*-------------------------------------------------------------------------------------*/ /* * Simple String methods */ private static final String _emptyString = ""; private static String null2Empty(final String s) { return (s == null) ? _emptyString : s; } private static boolean isEmpty(final String s) { return s == null || s.isEmpty(); } /*-------------------------------------------------------------------------------------*/ /* * Instance variables */ private String _language; private NameFormat _nameFormat = NameFormat.UNKNOWN; private NameError _nameError = NameError.NO_ERROR; private String _sourceString; private String _addr821; private String[] _addr822Comments = new String[3]; private String _addr822LocalPart; private String _addr822Phrase; private String _canonical; private String _abbreviated; private String _keyword; private static final int _iA = 0; // ADMD private static final int _iCN = _iA + 1; // Common private static final int _iC = _iCN + 1; // Country private static final int _iQ = _iC + 1; // Generation private static final int _iG = _iQ + 1; // Given private static final int _iI = _iG + 1; // Initials private static final int _iO = _iI + 1; // Organization private static final int _iOU1 = _iO + 1; // OrgUnit1 private static final int _iOU2 = _iOU1 + 1; // OrgUnit2 private static final int _iOU3 = _iOU2 + 1; // OrgUnit3 private static final int _iOU4 = _iOU3 + 1; // OrgUnit4 private static final int _iP = _iOU4 + 1; // PRMD private static final int _iS = _iP + 1; // Surname; private static final int _hpSize = _iS + 1; // # of parts private static final int _iOU = _hpSize; // Pseudo for OU= private String[] _hierParts = new String[_hpSize]; private static final String[] _hierPartPrefices = { "A=", "CN=", "C=", "Q=", "G=", "I=", "O=", // "OU1=", "OU2=", "OU3=", "OU4=", "P=", "S=", "OU=" }; private String _routingHint; // For addresses like CN=John Smith/OU=HR/O=Shell/C=US@SHELL@Esso // In this case, the hint will be "SHELL@Esso" private String _idPrefix; /*-------------------------------------------------------------------------------------*/ /* * ODA methods */ @Override protected lotus.domino.Name getDelegate() { try { lotus.domino.Session rawsession = toLotus(parent); return rawsession.createName(this.getCanonical()); } catch (NotesException ne) { DominoUtils.handleException(ne); } return null; } @Override protected WrapperFactory getFactory() { return parent.getFactory(); } @Override public final Session getAncestorSession() { return parent; } @Override public final Session getParent() { return parent; } /*-------------------------------------------------------------------------------------*/ /* * A few additional methods */ @Override // Since Name is immutable (at moment), this simply becomes: public NameODA clone() { return this; } @Override public boolean isHierarchical() { return _nameFormat.isHierarchical(); } @SuppressWarnings("unchecked") @Override public Collection<String> getGroups(final String serverName) { Collection<String> result = null; try { DominoServer server = new DominoServer(serverName); result = server.getNamesList(getCanonical()); } catch (NotesException e) { DominoUtils.handleException(e); } return result; } @Override public NameFormat getNameFormat() { return _nameFormat; } @Override public NameError getNameError() { return _nameError; } /*-------------------------------------------------------------------------------------*/ /* * toString, equals etc. */ @Override public String toString() { return getClass().getName() + " [Canonical='" + getCanonical() + "', Language='" + _language + "']"; } @Override public boolean equals(final Object obj) { if (this == obj) return true; if (!(obj instanceof NameODA)) // This comprises obj = null return false; NameODA other = (NameODA) obj; return getCanonical().equals(other.getCanonical()) && getAbbreviated().equals(other.getAbbreviated()) && _language.equals(other._language); } @Override public int compareTo(final org.openntf.domino.Name other) { if (other == null) return 1; return getCanonical().compareTo(other.getCanonical()); } /*-------------------------------------------------------------------------------------*/ /* * Externalization */ private static final int EXTERNALVERSIONUID = 20141219; // The current date (when it was implemented) @Override public void readExternal(final ObjectInput in) throws IOException, ClassNotFoundException { super.readExternal(in); int version = in.readInt(); if (version != EXTERNALVERSIONUID) throw new InvalidClassException("Cannot read data version " + version); String canonical = in.readUTF(); _language = in.readUTF(); this.parse(canonical); } @Override public void writeExternal(final ObjectOutput out) throws IOException { super.writeExternal(out); out.writeInt(EXTERNALVERSIONUID); out.writeUTF(getCanonical()); out.writeUTF(_language); } /*-------------------------------------------------------------------------------------*/ /* * Lotus-get-methods */ @Override public String getAbbreviated() { if (_abbreviated == null) _abbreviated = (_nameFormat == NameFormat.HIERARCHICAL) ? buildX400Path(false, false) : _sourceString; return _abbreviated; } @Override public String getAddr821() { return null2Empty(_addr821); } @Override public String getAddr822Comment1() { return null2Empty(_addr822Comments[0]); } @Override public String getAddr822Comment2() { return null2Empty(_addr822Comments[1]); } @Override public String getAddr822Comment3() { return null2Empty(_addr822Comments[2]); } @Override public String getAddr822LocalPart() { return null2Empty(_addr822LocalPart); } @Override public String getAddr822Phrase() { return null2Empty(_addr822Phrase); } @Override public String getADMD() { return null2Empty(_hierParts[_iA]); } @Override public String getCanonical() { if (_canonical == null) _canonical = _nameFormat.isHierarchical() && !_nameFormat.isError() ? buildX400Path(true, true) : null2Empty(_sourceString); return _canonical; } @Override public String getCommon() { return null2Empty(_hierParts[_iCN]); } @Override public String getCountry() { return null2Empty(_hierParts[_iC]); } @Override public String getGeneration() { return null2Empty(_hierParts[_iQ]); } @Override public String getGiven() { return null2Empty(_hierParts[_iG]); } @Override public String getInitials() { return null2Empty(_hierParts[_iI]); } @Override public String getKeyword() { if (_keyword == null) { if (_nameFormat != NameFormat.HIERARCHICAL) _keyword = _emptyString; else { StringBuilder sb = new StringBuilder(); if (!isEmpty(_hierParts[_iC])) { if (sb.length() != 0) sb.append('\\'); sb.append(_hierParts[_iC]); } if (!isEmpty(_hierParts[_iO])) { if (sb.length() != 0) sb.append('\\'); sb.append(_hierParts[_iO]); } if (!isEmpty(_hierParts[_iOU1])) { if (sb.length() != 0) sb.append('\\'); sb.append(_hierParts[_iOU1]); } if (!isEmpty(_hierParts[_iOU2])) { if (sb.length() != 0) sb.append('\\'); sb.append(_hierParts[_iOU2]); } if (!isEmpty(_hierParts[_iOU3])) { if (sb.length() != 0) sb.append('\\'); sb.append(_hierParts[_iOU3]); } if (!isEmpty(_hierParts[_iOU4])) { if (sb.length() != 0) sb.append('\\'); sb.append(_hierParts[_iOU4]); } _keyword = sb.toString(); } } return _keyword; } @Override public String getLanguage() { return _language; } @Override public String getOrganization() { return null2Empty(_hierParts[_iO]); } @Override public String getOrgUnit1() { return null2Empty(_hierParts[_iOU1]); } @Override public String getOrgUnit2() { return null2Empty(_hierParts[_iOU2]); } @Override public String getOrgUnit3() { return null2Empty(_hierParts[_iOU3]); } @Override public String getOrgUnit4() { return null2Empty(_hierParts[_iOU4]); } @Override public String getPRMD() { return null2Empty(_hierParts[_iP]); } @Override public String getSurname() { return null2Empty(_hierParts[_iS]); } /*-------------------------------------------------------------------------------------*/ /* * Auxiliary method for building X400 paths */ private String buildX400Path(final boolean withPref, final boolean includeOthers) { StringBuilder sb = new StringBuilder(); if (includeOthers) { if (!isEmpty(_hierParts[_iI])) { if (sb.length() != 0) sb.append('/'); if (withPref) sb.append(_hierPartPrefices[_iI]); sb.append(_hierParts[_iI]); } if (!isEmpty(_hierParts[_iG])) { if (sb.length() != 0) sb.append('/'); if (withPref) sb.append(_hierPartPrefices[_iG]); sb.append(_hierParts[_iG]); } if (!isEmpty(_hierParts[_iS])) { if (sb.length() != 0) sb.append('/'); if (withPref) sb.append(_hierPartPrefices[_iS]); sb.append(_hierParts[_iS]); } if (!isEmpty(_hierParts[_iQ])) { if (sb.length() != 0) sb.append('/'); if (withPref) sb.append(_hierPartPrefices[_iQ]); sb.append(_hierParts[_iQ]); } } if (!isEmpty(_hierParts[_iCN])) { if (sb.length() != 0) sb.append('/'); if (withPref && _hierParts[_iCN].charAt(0) != '*') sb.append(_hierPartPrefices[_iCN]); sb.append(_hierParts[_iCN]); } if (includeOthers) { if (!isEmpty(_hierParts[_iA])) { if (sb.length() != 0) sb.append('/'); if (withPref) sb.append(_hierPartPrefices[_iA]); sb.append(_hierParts[_iA]); } if (!isEmpty(_hierParts[_iP])) { if (sb.length() != 0) sb.append('/'); if (withPref) sb.append(_hierPartPrefices[_iP]); sb.append(_hierParts[_iP]); } } if (!isEmpty(_hierParts[_iOU4])) { if (sb.length() != 0) sb.append('/'); if (withPref) sb.append(_hierPartPrefices[_iOU]); sb.append(_hierParts[_iOU4]); } if (!isEmpty(_hierParts[_iOU3])) { if (sb.length() != 0) sb.append('/'); if (withPref) sb.append(_hierPartPrefices[_iOU]); sb.append(_hierParts[_iOU3]); } if (!isEmpty(_hierParts[_iOU2])) { if (sb.length() != 0) sb.append('/'); if (withPref) sb.append(_hierPartPrefices[_iOU]); sb.append(_hierParts[_iOU2]); } if (!isEmpty(_hierParts[_iOU1])) { if (sb.length() != 0) sb.append('/'); if (withPref) sb.append(_hierPartPrefices[_iOU]); sb.append(_hierParts[_iOU1]); } if (!isEmpty(_hierParts[_iO])) { if (sb.length() != 0) sb.append('/'); if (withPref) sb.append(_hierPartPrefices[_iO]); sb.append(_hierParts[_iO]); } if (!isEmpty(_hierParts[_iC])) { if (sb.length() != 0) sb.append('/'); if (withPref) sb.append(_hierPartPrefices[_iC]); sb.append(_hierParts[_iC]); } if (!isEmpty(_routingHint)) { sb.append('@'); sb.append(_routingHint); } return sb.toString(); } /*-------------------------------------------------------------------------------------*/ /* * Additional methods from ext interface */ @Override public String getRFC82xInternetAddress() { if (_nameFormat != NameFormat.RFC822) return _emptyString; // Works like javax.mail.internet.InternetAddress.toString (only difference: phrase is always quoted) if (isEmpty(_addr822Phrase) && isEmpty(_addr822Comments[0])) return _addr821; String phrase = isEmpty(_addr822Phrase) ? _addr822Comments[0] : _addr822Phrase; int lh = phrase.length(); StringBuilder sb = new StringBuilder(lh + _addr821.length() + 32); sb.append('"'); for (int i = 0; i < lh; i++) { char c = phrase.charAt(i); if (c == '"' || c == '\\') sb.append('\\'); sb.append(c); } sb.append('"'); sb.append(' '); sb.append('<'); sb.append(_addr821); sb.append('>'); return sb.toString(); } @Override public String getIDprefix() { if (isEmpty(_idPrefix)) { char[] idp = new char[4]; int count = 0; if (!isEmpty(_hierParts[_iCN])) { String aux = _hierParts[_iCN].toUpperCase().replaceAll("[^A-Z0-9 ]", ""); String[] parts = aux.split(" "); if (parts.length >= 1) { String firstName = parts[0]; String lastName = parts[parts.length - 1]; if (!firstName.isEmpty()) idp[count++] = firstName.charAt(0); int lh = lastName.length(); if (lh > 0) { idp[count++] = lastName.charAt(0); if (lh > 1) { idp[count++] = lastName.charAt(1); idp[count++] = lastName.charAt(lh - 1); } } } } while (count < 4) idp[count++] = 'X'; _idPrefix = new String(idp); } return _idPrefix; } @Override public String getNamePart(final NamePartKey key) { String ret; if (key == NamePartKey.Abbreviated) ret = getAbbreviated(); else if (key == NamePartKey.Addr821) ret = _addr821; else if (key == NamePartKey.Addr822Comment1) ret = _addr822Comments[0]; else if (key == NamePartKey.Addr822Comment2) ret = _addr822Comments[1]; else if (key == NamePartKey.Addr822Comment3) ret = _addr822Comments[2]; else if (key == NamePartKey.Addr822LocalPart) ret = _addr822LocalPart; else if (key == NamePartKey.Addr822Phrase) ret = _addr822Phrase; else if (key == NamePartKey.ADMD) ret = _hierParts[_iA]; else if (key == NamePartKey.Canonical) ret = getCanonical(); else if (key == NamePartKey.Common) ret = _hierParts[_iCN]; else if (key == NamePartKey.Country) ret = _hierParts[_iC]; else if (key == NamePartKey.Generation) ret = _hierParts[_iQ]; else if (key == NamePartKey.Given) ret = _hierParts[_iG]; else if (key == NamePartKey.Initials) ret = _hierParts[_iI]; else if (key == NamePartKey.Keyword) ret = getKeyword(); else if (key == NamePartKey.Language) ret = _language; else if (key == NamePartKey.Organization) ret = _hierParts[_iO]; else if (key == NamePartKey.OrgUnit1) ret = _hierParts[_iOU1]; else if (key == NamePartKey.OrgUnit2) ret = _hierParts[_iOU2]; else if (key == NamePartKey.OrgUnit3) ret = _hierParts[_iOU3]; else if (key == NamePartKey.OrgUnit4) ret = _hierParts[_iOU4]; else if (key == NamePartKey.PRMD) ret = _hierParts[_iP]; else if (key == NamePartKey.Surname) ret = _hierParts[_iS]; else if (key == NamePartKey.IDprefix) ret = getIDprefix(); else // if(key==NamePartKey.SourceString) ret = _sourceString; return null2Empty(ret); } /*-------------------------------------------------------------------------------------*/ /* * Parsing */ private void parse(final String name) { _sourceString = name.trim(); int indAt = _sourceString.indexOf('@'); if (indAt < 0) { // Then it's canonical or abbreviated parseHierarchical(_sourceString); return; } // There are several possibilities: An RFC822 Internet address, a hierarchical address of the form // [CN=]xy/[OU=]xx/[O=]zz@zz[@ww], or a Notes mail address sitting in an Internet address: // Phrase <John Smith/Dev/Company/EN%Company@Company.en>. // Since Notes doesn't seem to handle addresses of the latter form correctly, we won't consider // such addresses in the following. // // Moreover, the '@' may sit in an RFC822 phrase: a@b <a@b> is correct. (So is a@/b<a@b>, too.) // [In this latter case, the "real" address must be enclosed in <>.] // Unfortunately, the following address is likewise correct: "<a@b>" <a@b> // Finally, expressions like <a@b>(a<@@b) or a@b(a@@b) or even a@b(a@\(@>b) are also admissible. // Hence we first have a look at the tail in order to extract possible RFC822 comments. // char c = _sourceString.charAt(_sourceString.length() - 1); if (c == ')' || c == '>') { // It should be an Internet address parseRFC822(_sourceString); return; } if (c == '@') { // That is obviously not allowed _nameFormat = NameFormat.FLATERROR; _nameError = NameError.GENERAL_SYNTAX_ERROR; return; } // Remaining cases: Pure Internet address a@b or extended hierarchical address. // A 100% solution seems impossible: The following Internet address is syntactically correct // CN=John-Smith/OU=Dev/O=Comp/C=en@Comp // The only totally reliable criterion would be the presence of a blank left to the '@', e.g. // CN=John Smith, then it must be hierarchical. // So we make a compromise: If there isn't such a blank present, we consider the name as // hierarchical, if it contains a '/', or begins with one of the defined X400 prefixes (CN=, // OU=, ...). // String leftPart = _sourceString.substring(0, indAt); for (;;) { if (leftPart.indexOf(' ') > 0) break; if (leftPart.indexOf('/') >= 0) break; if (getHierPartIndex(leftPart) >= 0) break; // We are left in the case of a simple Internet address john.smith@x.y parseRFC822(_sourceString); return; } _routingHint = _sourceString.substring(indAt + 1); parseHierarchical(leftPart); } /*-------------------------------------------------------------------------------------*/ private void parseHierarchical(final String what) { if (what.isEmpty()) { _nameFormat = NameFormat.FLATERROR; _nameError = NameError.EMPTY_NAME; return; } StringSplitSimple sss = new StringSplitSimple(what, '/'); int numP1 = sss.split(true); String[] parts = new String[numP1]; int[] hpis = new int[numP1]; int numParts = 0; for (int i = 0; i < numP1; i++) { String part = sss.getSplitN(i, true); if (part.isEmpty()) { if (i == 0) { _nameFormat = NameFormat.HIERARCHICALERROR; _nameError = NameError.EMPTY_FIRST_PART; return; } continue; // Ignore empty non-first part } int hpi = getHierPartIndex(part); if (hpi < 0 && part.indexOf('=') >= 0) { _nameFormat = NameFormat.HIERARCHICALERROR; _nameError = NameError.UNKNOWN_PART; return; } if (hpi >= 0 && part.length() == _hierPartPrefices[hpi].length()) continue; // Ignore parts consisting only of "O=" or similar parts[numParts] = part; hpis[numParts++] = hpi; } if (numParts == 0) { // I.e. only CN=/O=/C= _nameFormat = NameFormat.HIERARCHICALERROR; _nameError = NameError.EMPTY_NAME; return; } boolean abbreviated; if (hpis[0] >= 0) abbreviated = false; else if (parts[0].charAt(0) == '*') abbreviated = (numParts == 1 || hpis[1] < 0); else abbreviated = true; for (int i = 1; i < numParts; i++) if (abbreviated != (hpis[i] < 0)) { _nameFormat = NameFormat.HIERARCHICALERROR; _nameError = NameError.MIXED_HIERARCHICAL; return; } _nameFormat = NameFormat.HIERARCHICAL; if (abbreviated) parseHierAbbrev(parts, numParts); else parseHierX400(parts, hpis, numParts); } /*-------------------------------------------------------------------------------------*/ private void parseHierAbbrev(final String[] parts, int numParts) { _hierParts[_iCN] = parts[0]; if (numParts == 1) { _nameFormat = NameFormat.FLAT; return; } String country = parts[numParts - 1]; if (lookupCountry(country)) { _hierParts[_iC] = country; if (--numParts == 1) // Only Country present return; } int numOUs = numParts - 2; if (numOUs > 4) { _nameFormat = NameFormat.HIERARCHICALERROR; _nameError = NameError.TWO_MANY_OUS; return; } _hierParts[_iO] = parts[numParts - 1]; for (int i = 0; i < numOUs; i++) _hierParts[_iOU1 + i] = parts[numParts - 2 - i]; } private static Comparator<String> _icComp = new Comparator<String>() { @Override public int compare(final String s1, final String s2) { return s1.compareToIgnoreCase(s2); } }; private boolean lookupCountry(final String country) { return (country.length() == 2 && Arrays.binarySearch(Locale.getISOCountries(), country, _icComp) >= 0); } /*-------------------------------------------------------------------------------------*/ private void parseHierX400(final String[] parts, final int[] hpis, final int numParts) { // // Treating the OUs is rather cumbersome, because OU= may be mixed with OUn=, ... // BTW: Notes seems to not always work entirely correct, when dealing with mixed OU=/OUn=. // For example, take the address cn=John Smith/O=Comp/ou2=xxx/ou=dev/ou4=dd/ou=hr // Then Notes-getCanonical gives: CN=John Smith/OU=dd/OU=dev/OU=hr/O=Comp // That is, ou2=xxx gets lost. int[] ouExpl = new int[4]; int[] ouImpl = new int[4]; int numOUExpl = 0, numOUImpl = 0; for (int i = 0; i < 4; i++) ouExpl[i] = ouImpl[i] = -1; for (int i = numParts - 1; i >= 0; i--) { int hpi = hpis[i]; if (hpi >= _iOU1 && hpi <= _iOU4) { if (ouExpl[hpi - _iOU1] != -1) { numOUExpl = -111; break; } ouExpl[hpi - _iOU1] = i; hpis[i] = -1; numOUExpl++; } else if (hpi == _iOU) { if (++numOUImpl > 4) break; ouImpl[numOUImpl - 1] = i; hpis[i] = -1; } } if (numOUExpl == -111) { _nameFormat = NameFormat.HIERARCHICALERROR; _nameError = NameError.DOUBLE_PART; return; } if (numOUImpl + numOUExpl > 4) { _nameFormat = NameFormat.HIERARCHICALERROR; _nameError = NameError.TWO_MANY_OUS; return; } for (int i = 0; i < numOUImpl; i++) for (int j = 0; j < 4; j++) if (ouExpl[j] == -1) { ouExpl[j] = ouImpl[i]; break; } numOUExpl += numOUImpl; // // The rest is trivial // for (int i = 0; i < numParts; i++) { int hpi = hpis[i]; if (hpi == -1) continue; if (!isEmpty(_hierParts[hpi])) { _nameFormat = NameFormat.HIERARCHICALERROR; _nameError = NameError.DOUBLE_PART; return; } String toSet = parts[i].substring(_hierPartPrefices[hpi].length()).trim(); _hierParts[hpi] = toSet; if (hpi == _iA || hpi == _iQ || hpi == _iG || hpi == _iI || hpi == _iP || hpi == _iS) _nameFormat = NameFormat.HIERARCHICALEX; } for (int i = 0; i < 4; i++) if (ouExpl[i] != -1) { String s = parts[ouExpl[i]]; String toSet = s.substring(s.indexOf('=') + 1).trim(); _hierParts[_iOU1 + i] = toSet; } // Finally: Special treatment in case when CN is given, but no O= and no OU= // Lotus only does this special treatment in case of an address CN=xxx, but never, when // "additional" X400 parts are present (G=, Q=, ...). We are not going to imitate that. // if (!isEmpty(_hierParts[_iCN]) && isEmpty(_hierParts[_iO]) && numOUExpl == 0 && this != _serverName) { if (_serverName._nameFormat == NameFormat.UNKNOWN) { synchronized (_serverName) { _serverName.parse(Factory.getLocalServerName()); } if (!_serverName._nameFormat.isError() && _serverName.isHierarchical()) { for (int i = _iO; i <= _iOU4; i++) _hierParts[i] = _serverName._hierParts[i]; if (isEmpty(_hierParts[_iC])) _hierParts[_iC] = _serverName._hierParts[_iC]; } } } } private static NameODA _serverName = new NameODA(); /*-------------------------------------------------------------------------------------*/ private int getHierPartIndex(final String part) { // // Question: Should also expressions like "CN = xxx" be supported (blank in front of '=')? // At the moment, we don't. (And neither does Notes.) // for (int i = 0; i <= _hpSize; i++) if (Strings.startsWithIgnoreCase(part, _hierPartPrefices[i])) return i; return -1; } /*-------------------------------------------------------------------------------------*/ private void parseRFC822(final String what) { // First we examine possible comments: int lh = what.length() - 1; char c = what.charAt(lh); if (c == ')') { if ((lh = parse822Comments(what, lh)) < 0) { _nameFormat = NameFormat.RFC822ERROR; _nameError = NameError.INVALID_RFC822; return; } c = what.charAt(lh); } String mailAddr; if (c != '>') mailAddr = (lh == what.length() - 1) ? what : what.substring(0, lh + 1); else if ((mailAddr = parse822PhrasePlusAddr(what, lh)) == null) { _nameFormat = NameFormat.RFC822ERROR; _nameError = NameError.INVALID_RFC822; return; } if (!checkMailAddress(mailAddr)) { _nameFormat = NameFormat.RFC822ERROR; _nameError = NameError.INVALID_MAILADDR; return; } _addr821 = mailAddr; _nameFormat = NameFormat.RFC822; // // Finally set up a "Common Name": Lotus sets CN to 822Phrase or, if phrase is empty, to 822LocalPart // In accordance with javax.mail, we set CN to 822Comment1, if phrase is empty, and finally to // 822LocalPart, if both phrase and comment1 are empty // if (isEmpty(_hierParts[_iCN])) _hierParts[_iCN] = !isEmpty(_addr822Phrase) ? _addr822Phrase : !isEmpty(_addr822Comments[0]) ? _addr822Comments[0] : _addr822LocalPart; } /*-------------------------------------------------------------------------------------*/ private String parse822PhrasePlusAddr(final String what, final int endPos) { int startPos; for (startPos = endPos - 1; startPos >= 0; startPos--) if (what.charAt(startPos) == '<') break; if (startPos < 0) return null; String ret = what.substring(startPos + 1, endPos).trim(); if (ret.isEmpty()) return null; for (startPos--; startPos >= 0; startPos--) if (!Character.isWhitespace(what.charAt(startPos))) break; if (startPos < 0) return ret; // One more difference to Lotus: Lotus doesn't respect the fact that a phrase may be enclosed in // quotes. And this is essential in case the phrase contains '<' or '>'. // boolean inQuotes = (what.charAt(0) == '"' && what.charAt(startPos) == '"'); if (!inQuotes) { for (int i = 0; i <= startPos; i++) { char c = what.charAt(i); if (c == '<' || c == '>') return null; } _addr822Phrase = what.substring(0, startPos + 1).trim(); } else { if (startPos == 0) return null; if (startPos == 1) return ret; if ((countBackslashesBefore(what, startPos) & 1) != 0) return null; for (int i = startPos - 1; i > 0; i--) { if (what.charAt(i) != '"') continue; if ((countBackslashesBefore(what, i) & 1) == 0) return null; } char[] sp = new char[startPos + 1]; int filled = 0; for (int i = 1; i < startPos; i++) { char c = what.charAt(i); if (c == '\\') c = what.charAt(++i); sp[filled++] = c; } _addr822Phrase = new String(sp, 0, filled); } return ret; } /*-------------------------------------------------------------------------------------*/ private int parse822Comments(final String what, int endPos) { do if ((endPos = parseOne822Comment(what, endPos)) < 0) return -1; while (what.charAt(endPos) == ')'); return endPos; } private int parseOne822Comment(final String what, int endPos) { // // Remark: Notes has here some deficiencies, too: Consider the following (correct) mail address: // "Phrase with <>" <JohnSmith@Company.en> (Should work\\) // Then Notes fails to interpret it and gives Addr821 = empty. - On the other hand, the // invalid mail address "Phrase with <>" <JohnSmith@Company.en> ((Works?) too?)) // is interpreted by Notes. if ((countBackslashesBefore(what, endPos) & 1) != 0) return -1; for (endPos--; endPos >= 0; endPos--) if (!Character.isWhitespace(what.charAt(endPos))) break; if (endPos < 0) return -1; int brackCount = 1; int startPos; for (startPos = endPos; startPos >= 0; startPos--) { char c = what.charAt(startPos); if (c != ')' && c != '(') continue; if ((countBackslashesBefore(what, startPos) & 1) != 0) continue; if (c == ')') brackCount++; else if (--brackCount == 0) break; } if (startPos < 0) return -1; char[] sp = new char[endPos - startPos + 1]; int filled = 0; for (int i = startPos + 1; i <= endPos; i++) { char c = what.charAt(i); if (filled == 0 && Character.isWhitespace(c)) continue; if (c == '\\') c = what.charAt(++i); sp[filled++] = c; } if (filled > 0) { _addr822Comments[2] = _addr822Comments[1]; _addr822Comments[1] = _addr822Comments[0]; _addr822Comments[0] = new String(sp, 0, filled); } for (startPos--; startPos >= 0; startPos--) if (!Character.isWhitespace(what.charAt(startPos))) break; return startPos; } private int countBackslashesBefore(final String what, final int where) { int ret = 0; for (int i = where - 1; i >= 0; i--, ret++) if (what.charAt(i) != '\\') break; return ret; } /*-------------------------------------------------------------------------------------*/ private boolean checkMailAddress(final String mailAddr) { // // At the moment, we restrict ourselves to "normal" mail addresses. That is, exotic (but valid) // mail addresses like "very.unusual.@.unusual.com"@example.com aren't supported. // int atPos = mailAddr.indexOf('@'); if (atPos <= 0) return false; String preAt = mailAddr.substring(0, atPos); String postAt = mailAddr.substring(atPos + 1); if (postAt.isEmpty() || postAt.charAt(0) == '.') return false; int lh = preAt.length(); for (int i = 0; i < lh; i++) { char c = preAt.charAt(i); if (c <= ' ' || c > 0x7f || c == '<' || c == '>' || c == '[' || c == ']') return false; } lh = postAt.length(); for (int i = 0; i < lh; i++) { char c = postAt.charAt(i); if (c <= ' ' || c > 0x7f) return false; if ((!Character.isLetterOrDigit(c)) && (c != '-') && (c != '.')) return false; if (c == '.' && (i == lh - 1 || postAt.charAt(i + 1) == '.')) return false; } _addr822LocalPart = preAt; return true; } }