Author: milinda
Date: Mon Dec 27 12:31:53 2010
New Revision: 1053054

URL: http://svn.apache.org/viewvc?rev=1053054&view=rev
Log:
Fix for AXIS2-3839 and AXIS2-4050.
This fix uses the same configuration mechanism used in old code.
But there are possible improvements to configuration mechanism and those will 
be added to the Axis2 later.


Added:
    
axis/axis2/java/core/trunk/modules/transport/http/src/org/apache/axis2/transport/http/util/HTTPProxyConfigurationUtil.java
Removed:
    
axis/axis2/java/core/trunk/modules/transport/http/src/org/apache/axis2/transport/http/ProxyConfiguration.java
Modified:
    
axis/axis2/java/core/trunk/modules/integration/test/org/apache/axis2/transport/http/NonProxyHostTest.java
    
axis/axis2/java/core/trunk/modules/transport/http/src/org/apache/axis2/transport/http/AbstractHTTPSender.java

Modified: 
axis/axis2/java/core/trunk/modules/integration/test/org/apache/axis2/transport/http/NonProxyHostTest.java
URL: 
http://svn.apache.org/viewvc/axis/axis2/java/core/trunk/modules/integration/test/org/apache/axis2/transport/http/NonProxyHostTest.java?rev=1053054&r1=1053053&r2=1053054&view=diff
==============================================================================
--- 
axis/axis2/java/core/trunk/modules/integration/test/org/apache/axis2/transport/http/NonProxyHostTest.java
 (original)
+++ 
axis/axis2/java/core/trunk/modules/integration/test/org/apache/axis2/transport/http/NonProxyHostTest.java
 Mon Dec 27 12:31:53 2010
@@ -20,13 +20,14 @@
 package org.apache.axis2.transport.http;
 
 import junit.framework.TestCase;
+import org.apache.axis2.transport.http.util.HTTPProxyConfigurationUtil;
 
 public class NonProxyHostTest extends TestCase {
     public void testForAxis2_3453() {
         String nonProxyHosts = 
"sealbook.ncl.ac.uk|*.sealbook.ncl.ac.uk|eskdale.ncl.ac.uk|*.eskdale.ncl.ac.uk|giga25.ncl.ac.uk|*.giga25.ncl.ac.uk";
-        
assertTrue(ProxyConfiguration.isHostInNonProxyList("sealbook.ncl.ac.uk", 
nonProxyHosts));
-        
assertFalse(ProxyConfiguration.isHostInNonProxyList("xsealbook.ncl.ac.uk", 
nonProxyHosts));
-        
assertTrue(ProxyConfiguration.isHostInNonProxyList("local","local|*.local|169.254/16|*.169.254/16"));
-        
assertFalse(ProxyConfiguration.isHostInNonProxyList("localhost","local|*.local|169.254/16|*.169.254/16"));
+        
assertTrue(HTTPProxyConfigurationUtil.isHostInNonProxyList("sealbook.ncl.ac.uk",
 nonProxyHosts));
+        
assertFalse(HTTPProxyConfigurationUtil.isHostInNonProxyList("xsealbook.ncl.ac.uk",
 nonProxyHosts));
+        
assertTrue(HTTPProxyConfigurationUtil.isHostInNonProxyList("local","local|*.local|169.254/16|*.169.254/16"));
+        
assertFalse(HTTPProxyConfigurationUtil.isHostInNonProxyList("localhost","local|*.local|169.254/16|*.169.254/16"));
     }
 }

Modified: 
axis/axis2/java/core/trunk/modules/transport/http/src/org/apache/axis2/transport/http/AbstractHTTPSender.java
URL: 
http://svn.apache.org/viewvc/axis/axis2/java/core/trunk/modules/transport/http/src/org/apache/axis2/transport/http/AbstractHTTPSender.java?rev=1053054&r1=1053053&r2=1053054&view=diff
==============================================================================
--- 
axis/axis2/java/core/trunk/modules/transport/http/src/org/apache/axis2/transport/http/AbstractHTTPSender.java
 (original)
+++ 
axis/axis2/java/core/trunk/modules/transport/http/src/org/apache/axis2/transport/http/AbstractHTTPSender.java
 Mon Dec 27 12:31:53 2010
@@ -31,6 +31,7 @@ import org.apache.axis2.description.Tran
 import org.apache.axis2.i18n.Messages;
 import org.apache.axis2.transport.MessageFormatter;
 import org.apache.axis2.transport.TransportUtils;
+import org.apache.axis2.transport.http.util.HTTPProxyConfigurationUtil;
 import org.apache.axis2.util.JavaUtils;
 import org.apache.axis2.wsdl.WSDLConstants;
 import org.apache.commons.httpclient.Credentials;
@@ -286,10 +287,11 @@ public abstract class AbstractHTTPSender
         }
         // proxy configuration
 
-        if (ProxyConfiguration.isProxyEnabled(msgCtx,targetURL)) {
-            log.debug("ProxyConfiguration");
-            ProxyConfiguration proxyConfiguration = new ProxyConfiguration();
-            proxyConfiguration.configure(msgCtx,client,config);
+        if (HTTPProxyConfigurationUtil.isProxyEnabled(msgCtx, targetURL)) {
+            if(log.isDebugEnabled()){
+                log.debug("Configuring HTTP proxy.");
+            }
+            HTTPProxyConfigurationUtil.configure(msgCtx, client, config);
         }
 
         return config;

Added: 
axis/axis2/java/core/trunk/modules/transport/http/src/org/apache/axis2/transport/http/util/HTTPProxyConfigurationUtil.java
URL: 
http://svn.apache.org/viewvc/axis/axis2/java/core/trunk/modules/transport/http/src/org/apache/axis2/transport/http/util/HTTPProxyConfigurationUtil.java?rev=1053054&view=auto
==============================================================================
--- 
axis/axis2/java/core/trunk/modules/transport/http/src/org/apache/axis2/transport/http/util/HTTPProxyConfigurationUtil.java
 (added)
+++ 
axis/axis2/java/core/trunk/modules/transport/http/src/org/apache/axis2/transport/http/util/HTTPProxyConfigurationUtil.java
 Mon Dec 27 12:31:53 2010
@@ -0,0 +1,472 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.apache.axis2.transport.http.util;
+
+import org.apache.axiom.om.OMElement;
+import org.apache.axis2.AxisFault;
+import org.apache.axis2.context.MessageContext;
+import org.apache.axis2.description.Parameter;
+import org.apache.axis2.transport.http.HTTPConstants;
+import org.apache.axis2.transport.http.HttpTransportProperties;
+import org.apache.commons.httpclient.*;
+import org.apache.commons.httpclient.auth.AuthScope;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import javax.xml.namespace.QName;
+import java.net.URL;
+import java.util.StringTokenizer;
+
+/**
+ * Contains utility functions used when configuring HTTP Proxy for HTTP Sender.
+ */
+public class HTTPProxyConfigurationUtil {
+    private static Log log = 
LogFactory.getLog(HTTPProxyConfigurationUtil.class);
+
+    protected static final String HTTP_PROXY_HOST = "http.proxyHost";
+    protected static final String HTTP_PROXY_PORT = "http.proxyPort";
+    protected static final String HTTP_NON_PROXY_HOSTS = "http.nonProxyHosts";
+
+    protected static final String ATTR_PROXY = "Proxy";
+    protected static final String PROXY_HOST_ELEMENT = "ProxyHost";
+    protected static final String PROXY_PORT_ELEMENT = "ProxyPort";
+    protected static final String PROXY_USER_ELEMENT = "ProxyUser";
+    protected static final String PROXY_PASSWORD_ELEMENT = "ProxyPassword";
+
+
+    protected static final String PROXY_CONFIGURATION_NOT_FOUND =
+            "HTTP Proxy is enabled, but proxy configuration element is missing 
in axis2.xml";
+    protected static final String PROXY_HOST_ELEMENT_NOT_FOUND =
+            "HTTP Proxy is enabled, but proxy host element is missing in 
axis2.xml";
+    protected static final String PROXY_PORT_ELEMENT_NOT_FOUND =
+            "HTTP Proxy is enabled, but proxy port element is missing in 
axis2.xml";
+    protected static final String PROXY_HOST_ELEMENT_WITH_EMPTY_VALUE =
+            "HTTP Proxy is enabled, but proxy host value is empty.";
+    protected static final String PROXY_PORT_ELEMENT_WITH_EMPTY_VALUE =
+            "HTTP Proxy is enabled, but proxy port value is empty.";
+
+    /**
+     * Configure HTTP Proxy settings of commons-httpclient HostConfiguration. 
Proxy settings can be get from
+     * axis2.xml, Java proxy settings or can be override through property in 
message context.
+     * <p/>
+     * HTTP Proxy setting element format:
+     * <parameter name="Proxy">
+     * <Configuration>
+     * <ProxyHost>example.org</ProxyHost>
+     * <ProxyPort>3128</ProxyPort>
+     * <ProxyUser>EXAMPLE/John</ProxyUser>
+     * <ProxyPassword>password</ProxyPassword>
+     * <Configuration>
+     * <parameter>
+     *
+     * @param messageContext in message context for
+     * @param httpClient     commons-httpclient instance
+     * @param config         commons-httpclient HostConfiguration
+     * @throws AxisFault if Proxy settings are invalid
+     */
+    public static void configure(MessageContext messageContext,
+                                 HttpClient httpClient,
+                                 HostConfiguration config) throws AxisFault {
+
+        Credentials proxyCredentials = null;
+        String proxyHost = null;
+        String nonProxyHosts = null;
+        Integer proxyPort = -1;
+        String proxyUser = null;
+        String proxyPassword = null;
+
+        //Getting configuration values from Axis2.xml
+        Parameter proxySettingsFromAxisConfig = 
messageContext.getConfigurationContext().getAxisConfiguration()
+                .getParameter(ATTR_PROXY);
+        if (proxySettingsFromAxisConfig != null) {
+            OMElement proxyConfiguration = 
getProxyConfigurationElement(proxySettingsFromAxisConfig);
+            proxyHost = getProxyHost(proxyConfiguration);
+            proxyPort = getProxyPort(proxyConfiguration);
+            proxyUser = getProxyUser(proxyConfiguration);
+            proxyPassword = getProxyPassword(proxyConfiguration);
+            if(proxyUser != null){
+                if(proxyPassword == null){
+                    proxyPassword = "";
+                }
+                int proxyUserDomainIndex = proxyUser.indexOf("\\");
+                if( proxyUserDomainIndex > 0){
+                    String domain = proxyUser.substring(0, 
proxyUserDomainIndex);
+                    if(proxyUser.length() > proxyUserDomainIndex + 1) {
+                        String user = proxyUser.substring(proxyUserDomainIndex 
+ 1);
+                        proxyCredentials = new NTCredentials(user, 
proxyPassword, proxyHost, domain);
+                    }
+                }
+                proxyCredentials = new UsernamePasswordCredentials(proxyUser, 
proxyPassword);
+            }
+
+        }
+
+        // If there is runtime proxy settings, these settings will override 
settings from axis2.xml
+        HttpTransportProperties.ProxyProperties proxyProperties =
+                (HttpTransportProperties.ProxyProperties) 
messageContext.getProperty(HTTPConstants.PROXY);
+        if(proxyProperties != null) {
+            String proxyHostProp = proxyProperties.getProxyHostName();
+            if(proxyHostProp == null || proxyHostProp.length() <= 0) {
+                throw new AxisFault("HTTP Proxy host is not available. Host is 
a MUST parameter");
+            } else {
+                proxyHost = proxyHostProp;
+            }
+            proxyPort = proxyProperties.getProxyPort();
+
+            // Overriding credentials
+            String userName = proxyProperties.getUserName();
+            String password = proxyProperties.getPassWord();
+            String domain = proxyProperties.getDomain();
+
+            if(userName != null && password != null && domain != null){
+                proxyCredentials = new NTCredentials(userName, password, 
proxyHost, domain);
+            } else if(userName != null && domain == null){
+                proxyCredentials = new UsernamePasswordCredentials(userName, 
password);
+            }
+
+        }
+
+        // Overriding proxy settings if proxy is available from JVM settings
+        String host = System.getProperty(HTTP_PROXY_HOST);
+        if(host != null) {
+            proxyHost = host;
+        }
+
+        String port = System.getProperty(HTTP_PROXY_PORT);
+        if(port != null) {
+            proxyPort = Integer.parseInt(port);
+        }
+
+        if(proxyCredentials != null) {
+            httpClient.getParams().setAuthenticationPreemptive(true);
+            HttpState cachedHttpState = 
(HttpState)messageContext.getProperty(HTTPConstants.CACHED_HTTP_STATE);
+            if(cachedHttpState != null){
+                httpClient.setState(cachedHttpState);
+            }
+            httpClient.getState().setProxyCredentials(AuthScope.ANY, 
proxyCredentials);
+        }
+        config.setProxy(proxyHost, proxyPort);
+    }
+
+    private static OMElement getProxyConfigurationElement(Parameter 
proxySettingsFromAxisConfig) throws AxisFault {
+        OMElement proxyConfigurationElement = 
proxySettingsFromAxisConfig.getParameterElement().getFirstElement();
+        if (proxyConfigurationElement == null) {
+            log.error(PROXY_CONFIGURATION_NOT_FOUND);
+            throw new AxisFault(PROXY_CONFIGURATION_NOT_FOUND);
+        }
+        return proxyConfigurationElement;
+    }
+
+    private static String getProxyHost(OMElement proxyConfiguration) throws 
AxisFault {
+        OMElement proxyHostElement = 
proxyConfiguration.getFirstChildWithName(new QName(PROXY_HOST_ELEMENT));
+        if (proxyHostElement == null) {
+            log.error(PROXY_HOST_ELEMENT_NOT_FOUND);
+            throw new AxisFault(PROXY_HOST_ELEMENT_NOT_FOUND);
+        }
+        String proxyHost = proxyHostElement.getText();
+        if (proxyHost == null) {
+            log.error(PROXY_HOST_ELEMENT_WITH_EMPTY_VALUE);
+            throw new AxisFault(PROXY_HOST_ELEMENT_WITH_EMPTY_VALUE);
+        }
+        return proxyHost;
+    }
+
+    private static Integer getProxyPort(OMElement proxyConfiguration) throws 
AxisFault {
+        OMElement proxyPortElement = 
proxyConfiguration.getFirstChildWithName(new QName(PROXY_PORT_ELEMENT));
+        if (proxyPortElement == null) {
+            log.error(PROXY_PORT_ELEMENT_NOT_FOUND);
+            throw new AxisFault(PROXY_PORT_ELEMENT_NOT_FOUND);
+        }
+        String proxyPort = proxyPortElement.getText();
+        if (proxyPort == null) {
+            log.error(PROXY_PORT_ELEMENT_WITH_EMPTY_VALUE);
+            throw new AxisFault(PROXY_PORT_ELEMENT_WITH_EMPTY_VALUE);
+        }
+        return Integer.parseInt(proxyPort);
+    }
+
+    private static String getProxyUser(OMElement proxyConfiguration) {
+        OMElement proxyUserElement = 
proxyConfiguration.getFirstChildWithName(new QName(PROXY_USER_ELEMENT));
+        if (proxyUserElement == null) {
+            return null;
+        }
+        String proxyUser = proxyUserElement.getText();
+        if (proxyUser == null) {
+            log.warn("Empty user name element in HTTP Proxy settings.");
+            return null;
+        }
+
+        return proxyUser;
+    }
+
+    private static String getProxyPassword(OMElement proxyConfiguration) {
+        OMElement proxyPasswordElement = 
proxyConfiguration.getFirstChildWithName(new QName(PROXY_PASSWORD_ELEMENT));
+        if (proxyPasswordElement == null) {
+            return null;
+        }
+        String proxyUser = proxyPasswordElement.getText();
+        if (proxyUser == null) {
+            log.warn("Empty user name element in HTTP Proxy settings.");
+            return null;
+        }
+
+        return proxyUser;
+    }
+
+    /**
+     * Check whether http proxy is configured or active.
+     * This is not a deep check.
+     *
+     * @param messageContext in message context
+     * @param targetURL      URL of the edpoint which we are sending the 
request
+     * @return true if proxy is enabled, false otherwise
+     */
+    public static boolean isProxyEnabled(MessageContext messageContext, URL 
targetURL) {
+        boolean proxyEnabled = false;
+
+        Parameter param = 
messageContext.getConfigurationContext().getAxisConfiguration()
+                .getParameter(ATTR_PROXY);
+
+        //If configuration is over ridden
+        Object obj = messageContext.getProperty(HTTPConstants.PROXY);
+
+        //From Java Networking Properties
+        String sp = System.getProperty(HTTP_PROXY_HOST);
+
+        if (param != null || obj != null || sp != null) {
+            proxyEnabled = true;
+        }
+
+        boolean isNonProxyHost = validateNonProxyHosts(targetURL.getHost());
+
+        return proxyEnabled && !isNonProxyHost;
+    }
+
+    /**
+     * Validates for names that shouldn't be listered as proxies.
+     * The http.nonProxyHosts can be set to specify the hosts which should be
+     * connected to directly (not through the proxy server).
+     * The value of the http.nonProxyHosts property can be a list of hosts,
+     * each separated by a |; it can also take a regular expression for 
matches;
+     * for example: *.sfbay.sun.com would match any fully qualified hostname 
in the sfbay domain.
+     * <p/>
+     * For more information refer to : 
http://java.sun.com/features/2002/11/hilevel_network.html
+     * <p/>
+     * false : validation fail : User can use the proxy
+     * true : validation pass ; User can't use the proxy
+     *
+     * @return boolean
+     */
+    private static boolean validateNonProxyHosts(String host) {
+        //From system property http.nonProxyHosts
+        String nonProxyHosts = System.getProperty(HTTP_NON_PROXY_HOSTS);
+        return isHostInNonProxyList(host, nonProxyHosts);
+    }
+
+    /**
+     * Check if the specified host is in the list of non proxy hosts.
+     *
+     * @param host          host name
+     * @param nonProxyHosts string containing the list of non proxy hosts
+     * @return true/false
+     */
+    public static boolean isHostInNonProxyList(String host, String 
nonProxyHosts) {
+        if ((nonProxyHosts == null) || (host == null)) {
+            return false;
+        }
+
+        /*
+         * The http.nonProxyHosts system property is a list enclosed in
+         * double quotes with items separated by a vertical bar.
+         */
+        StringTokenizer tokenizer = new StringTokenizer(nonProxyHosts, "|\"");
+
+        while (tokenizer.hasMoreTokens()) {
+            String pattern = tokenizer.nextToken();
+            if (match(pattern, host, false)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Matches a string against a pattern. The pattern contains two special
+     * characters:
+     * '*' which means zero or more characters,
+     *
+     * @param pattern         the (non-null) pattern to match against
+     * @param str             the (non-null) string that must be matched 
against the
+     *                        pattern
+     * @param isCaseSensitive
+     * @return <code>true</code> when the string matches against the pattern,
+     *         <code>false</code> otherwise.
+     */
+    private static boolean match(String pattern, String str,
+                                 boolean isCaseSensitive) {
+
+        char[] patArr = pattern.toCharArray();
+        char[] strArr = str.toCharArray();
+        int patIdxStart = 0;
+        int patIdxEnd = patArr.length - 1;
+        int strIdxStart = 0;
+        int strIdxEnd = strArr.length - 1;
+        char ch;
+        boolean containsStar = false;
+
+        for (int i = 0; i < patArr.length; i++) {
+            if (patArr[i] == '*') {
+                containsStar = true;
+                break;
+            }
+        }
+        if (!containsStar) {
+
+            // No '*'s, so we make a shortcut
+            if (patIdxEnd != strIdxEnd) {
+                return false;        // Pattern and string do not have the 
same size
+            }
+            for (int i = 0; i <= patIdxEnd; i++) {
+                ch = patArr[i];
+                if (isCaseSensitive && (ch != strArr[i])) {
+                    return false;    // Character mismatch
+                }
+                if (!isCaseSensitive
+                        && (Character.toUpperCase(ch)
+                        != Character.toUpperCase(strArr[i]))) {
+                    return false;    // Character mismatch
+                }
+            }
+            return true;             // String matches against pattern
+        }
+        if (patIdxEnd == 0) {
+            return true;    // Pattern contains only '*', which matches 
anything
+        }
+
+        // Process characters before first star
+        while ((ch = patArr[patIdxStart]) != '*'
+                && (strIdxStart <= strIdxEnd)) {
+            if (isCaseSensitive && (ch != strArr[strIdxStart])) {
+                return false;    // Character mismatch
+            }
+            if (!isCaseSensitive
+                    && (Character.toUpperCase(ch)
+                    != Character.toUpperCase(strArr[strIdxStart]))) {
+                return false;    // Character mismatch
+            }
+            patIdxStart++;
+            strIdxStart++;
+        }
+        if (strIdxStart > strIdxEnd) {
+
+            // All characters in the string are used. Check if only '*'s are
+            // left in the pattern. If so, we succeeded. Otherwise failure.
+            for (int i = patIdxStart; i <= patIdxEnd; i++) {
+                if (patArr[i] != '*') {
+                    return false;
+                }
+            }
+            return true;
+        }
+
+        // Process characters after last star
+        while ((ch = patArr[patIdxEnd]) != '*' && (strIdxStart <= strIdxEnd)) {
+            if (isCaseSensitive && (ch != strArr[strIdxEnd])) {
+                return false;    // Character mismatch
+            }
+            if (!isCaseSensitive
+                    && (Character.toUpperCase(ch)
+                    != Character.toUpperCase(strArr[strIdxEnd]))) {
+                return false;    // Character mismatch
+            }
+            patIdxEnd--;
+            strIdxEnd--;
+        }
+        if (strIdxStart > strIdxEnd) {
+
+            // All characters in the string are used. Check if only '*'s are
+            // left in the pattern. If so, we succeeded. Otherwise failure.
+            for (int i = patIdxStart; i <= patIdxEnd; i++) {
+                if (patArr[i] != '*') {
+                    return false;
+                }
+            }
+            return true;
+        }
+
+        // process pattern between stars. padIdxStart and patIdxEnd point
+        // always to a '*'.
+        while ((patIdxStart != patIdxEnd) && (strIdxStart <= strIdxEnd)) {
+            int patIdxTmp = -1;
+
+            for (int i = patIdxStart + 1; i <= patIdxEnd; i++) {
+                if (patArr[i] == '*') {
+                    patIdxTmp = i;
+                    break;
+                }
+            }
+            if (patIdxTmp == patIdxStart + 1) {
+
+                // Two stars next to each other, skip the first one.
+                patIdxStart++;
+                continue;
+            }
+
+            // Find the pattern between padIdxStart & padIdxTmp in str between
+            // strIdxStart & strIdxEnd
+            int patLength = (patIdxTmp - patIdxStart - 1);
+            int strLength = (strIdxEnd - strIdxStart + 1);
+            int foundIdx = -1;
+
+            strLoop:
+            for (int i = 0; i <= strLength - patLength; i++) {
+                for (int j = 0; j < patLength; j++) {
+                    ch = patArr[patIdxStart + j + 1];
+                    if (isCaseSensitive
+                            && (ch != strArr[strIdxStart + i + j])) {
+                        continue strLoop;
+                    }
+                    if (!isCaseSensitive && (Character
+                            .toUpperCase(ch) != Character
+                            .toUpperCase(strArr[strIdxStart + i + j]))) {
+                        continue strLoop;
+                    }
+                }
+                foundIdx = strIdxStart + i;
+                break;
+            }
+            if (foundIdx == -1) {
+                return false;
+            }
+            patIdxStart = patIdxTmp;
+            strIdxStart = foundIdx + patLength;
+        }
+
+        // All characters in the string are used. Check if only '*'s are left
+        // in the pattern. If so, we succeeded. Otherwise failure.
+        for (int i = patIdxStart; i <= patIdxEnd; i++) {
+            if (patArr[i] != '*') {
+                return false;
+            }
+        }
+        return true;
+    }
+
+}


Reply via email to