63 changes: 31 additions & 32 deletions jodd-http/src/main/java/jodd/http/net/HTTPProxySocketFactory.java
Original file line number Diff line number Diff line change
Expand Up @@ -76,50 +76,49 @@ private Socket createHttpProxySocket(final String host, final int port) {
int proxyPort = proxy.getProxyPort();

try {

socket = new Socket(proxyAddress, proxyPort);
String hostport = "CONNECT " + host + ":" + port;
String proxyLine;
String hostport = host + ":" + port;
String proxyLine = "";
String username = proxy.getProxyUsername();

if (username == null) {
proxyLine = "";
} else {
if (username != null) {
String password = proxy.getProxyPassword();
proxyLine =
"\r\nProxy-Authorization: Basic "
+ Base64.encodeToString((username + ":" + password));
"Proxy-Authorization: Basic " +
Base64.encodeToString((username + ":" + password)) + "\r\n";
}

socket.getOutputStream().write(
(hostport + " HTTP/1.1\r\nHost: "
+ hostport + proxyLine + "\r\n\r\n").getBytes("UTF-8"));
("CONNECT " + hostport + " HTTP/1.1\r\n" +
"Host: " + hostport + "\r\n" +
proxyLine +
"\r\n"
).getBytes("UTF-8")
);

InputStream in = socket.getInputStream();
StringBuilder recv = new StringBuilder(100);
int nlchars = 0;

while (true) {
int i = in.read();
if (i == -1) {
throw new HttpException(ProxyInfo.ProxyType.HTTP, "Invalid response");
}

char c = (char) i;
recv.append(c);
if (recv.length() > 1024) {
throw new HttpException(ProxyInfo.ProxyType.HTTP, "Received header longer then 1024 chars");
}
if ((nlchars == 0 || nlchars == 2) && c == '\r') {
nlchars++;
} else if ((nlchars == 1 || nlchars == 3) && c == '\n') {
nlchars++;
} else {
nlchars = 0;
}
if (nlchars == 4) {
break;
}
}
do {
int i = in.read();
if (i == -1) {
throw new HttpException(ProxyInfo.ProxyType.HTTP, "Invalid response");
}

char c = (char) i;
recv.append(c);
if (recv.length() > 1024) {
throw new HttpException(ProxyInfo.ProxyType.HTTP, "Received header longer then 1024 chars");
}
if ((nlchars == 0 || nlchars == 2) && c == '\r') {
nlchars++;
} else if ((nlchars == 1 || nlchars == 3) && c == '\n') {
nlchars++;
} else {
nlchars = 0;
}
} while (nlchars != 4);

String recvStr = recv.toString();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ public class SocketHttpConnectionProvider implements HttpConnectionProvider {

protected ProxyInfo proxy = ProxyInfo.directProxy();
protected String secureEnabledProtocols = System.getProperty("https.protocols");
protected String sslProtocol = "TLSv1.1";

/**
* Defines proxy to use for created sockets.
Expand All @@ -67,14 +68,29 @@ public void setSecuredProtocols(final String secureEnabledProtocols) {
this.secureEnabledProtocols = secureEnabledProtocols;
}

/**
* Returns current SSL protocol used.
*/
public String getSslProtocol() {
return sslProtocol;
}

/**
* Sets default SSL protocol to use. One of "SSL", "TLSv1.2", "TLSv1.1", "TLSv1".
*/
public SocketHttpConnectionProvider setSslProtocol(final String sslProtocol) {
this.sslProtocol = sslProtocol;
return this;
}

/**
* Creates new connection from current {@link jodd.http.HttpRequest request}.
*
* @see #createSocket(String, int, int)
*/
@Override
public HttpConnection createHttpConnection(final HttpRequest httpRequest) throws IOException {
SocketHttpConnection httpConnection;
final SocketHttpConnection httpConnection;

final boolean https = httpRequest.protocol().equalsIgnoreCase("https");

Expand Down Expand Up @@ -215,7 +231,7 @@ protected SSLSocket createSSLSocket(
protected SSLSocketFactory getDefaultSSLSocketFactory(final boolean trustAllCertificates) throws IOException {
if (trustAllCertificates) {
try {
SSLContext sc = SSLContext.getInstance("SSL");
SSLContext sc = SSLContext.getInstance(sslProtocol);
sc.init(null, TrustManagers.TRUST_ALL_CERTS, new java.security.SecureRandom());
return sc.getSocketFactory();
}
Expand Down
21 changes: 19 additions & 2 deletions jodd-joy/src/main/java/jodd/joy/JoyScanner.java
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,11 @@ public class JoyScanner extends JoyBase implements JoyScannerConfig {
*/
private List<String> includedJars = new ArrayList<>();

/**
* Excluded jars.
*/
private List<String> excludedJars = new ArrayList<>();

/**
* List of APP classes.
*/
Expand All @@ -76,6 +81,13 @@ public JoyScanner setIncludedJars(final String... includedJars) {
return this;
}

@Override
public JoyScanner setExcludedJars(final String... excludedJars) {
requireNotStarted(classScanner);
Collections.addAll(this.excludedJars, excludedJars);
return this;
}

@Override
public JoyScanner setIgnoreExceptions(final boolean ignoreExceptions) {
requireNotStarted(classScanner);
Expand Down Expand Up @@ -146,15 +158,20 @@ protected void scanClassPath(final File root) {
if (log.isDebugEnabled()) {
log.debug("Scan entries: " + Converter.get().toString(includedEntries));
log.debug("Scan jars: " + Converter.get().toString(includedJars));
log.debug("Scan exclude jars: " + Converter.get().toString(excludedJars));
log.debug("Scan ignore exception: " + ignoreExceptions);
}

classScanner.excludeCommonEntries();
classScanner.excludeCommonJars();
classScanner.excludeJars(excludedJars.toArray(new String[0]));

if (includedEntries.isEmpty() && includedJars.isEmpty()) {
// nothing was explicitly included
classScanner.excludeAllEntries(false);
classScanner.excludeCommonEntries();
classScanner.excludeCommonJars();
}
else {
// something was included by user
classScanner.excludeAllEntries(true);
includedEntries.add("jodd.*");
}
Expand Down
2 changes: 2 additions & 0 deletions jodd-joy/src/main/java/jodd/joy/JoyScannerConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ public interface JoyScannerConfig {

JoyScannerConfig setIncludedJars(final String... includedJars);

JoyScannerConfig setExcludedJars(final String... includedJars);

JoyScannerConfig setIgnoreExceptions(final boolean ignoreExceptions);

JoyScannerConfig scanClasspathOf(final Class applicationClass);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,17 +34,19 @@
*/
public class EmailConstraint implements ValidationConstraint<Email> {

@Override
public void configure(final Email annotation) {
}

@Override
public boolean isValid(final ValidationConstraintContext vcc, final Object value) {
if (value == null) {
return true;
}

RFC2822AddressParser.ParsedAddress address = RFC2822AddressParser.LOOSE.parse(value.toString());

if (address == null) {
if (!address.isValid()) {
return false;
}

Expand Down
48 changes: 33 additions & 15 deletions jodd-json/src/main/java/jodd/json/JsonContext.java
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@

import java.util.ArrayList;
import java.util.List;
import java.util.function.Function;

import static jodd.util.StringPool.NULL;

Expand All @@ -47,13 +48,17 @@ public class JsonContext extends JsonWriter {
protected int bagSize = 0;
protected final Path path;
protected final boolean excludeNulls;
protected final boolean excludeEmpty;
protected final Function<Object, TypeJsonSerializer> serializerResolver;

public JsonContext(final JsonSerializer jsonSerializer, final Appendable appendable, final boolean excludeNulls, final boolean strictStringEncoding) {
super(appendable, strictStringEncoding);
public JsonContext(final JsonSerializer jsonSerializer, final Appendable appendable) {
super(appendable, jsonSerializer.strictStringEncoding);
this.jsonSerializer = jsonSerializer;
this.bag = new ArrayList<>();
this.path = new Path();
this.excludeNulls = excludeNulls;
this.excludeNulls = jsonSerializer.excludeNulls;
this.excludeEmpty = jsonSerializer.excludeEmpty;
this.serializerResolver = jsonSerializer.serializerResolver;
}

/**
Expand All @@ -64,12 +69,16 @@ public JsonSerializer getJsonSerializer() {
}

/**
* Returns <code>true</code> if null values have to be excluded.
* Returns <code>true</code> if <code>null</code> values have to be excluded.
*/
public boolean isExcludeNulls() {
return excludeNulls;
}

public boolean isExcludeEmpty() {
return excludeEmpty;
}

// ---------------------------------------------------------------- path and value context

protected JsonValueContext lastValueContext = null;
Expand Down Expand Up @@ -172,24 +181,33 @@ public boolean serialize(final Object object) {

TypeJsonSerializer typeJsonSerializer = null;

// + read paths map
// callback

if (jsonSerializer.pathSerializersMap != null) {
typeJsonSerializer = jsonSerializer.pathSerializersMap.get(path);
if (serializerResolver != null) {
typeJsonSerializer = serializerResolver.apply(object);
}

Class type = object.getClass();
if (typeJsonSerializer == null) {

// + read types map
// + read paths map

if (jsonSerializer.typeSerializersMap != null) {
typeJsonSerializer = jsonSerializer.typeSerializersMap.lookup(type);
}
if (jsonSerializer.pathSerializersMap != null) {
typeJsonSerializer = jsonSerializer.pathSerializersMap.get(path);
}

// + globals
final Class type = object.getClass();

if (typeJsonSerializer == null) {
typeJsonSerializer = TypeJsonSerializerMap.get().lookup(type);
// + read local types map

if (jsonSerializer.typeSerializersMap != null) {
typeJsonSerializer = jsonSerializer.typeSerializersMap.lookup(type);
}

// + globals

if (typeJsonSerializer == null) {
typeJsonSerializer = TypeJsonSerializerMap.get().lookup(type);
}
}

return typeJsonSerializer.serialize(this, object);
Expand Down
34 changes: 34 additions & 0 deletions jodd-json/src/main/java/jodd/json/JsonParser.java
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
import jodd.util.UnsafeUtil;

import java.math.BigInteger;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
Expand Down Expand Up @@ -278,12 +279,25 @@ protected ValueConverter lookupValueConverter() {

/**
* Sets local class meta-data name.
* <p>
* Note that by using the class meta-data name you may expose a security hole in case untrusted source
* manages to specify a class that is accessible through class loader and exposes set of methods and/or fields,
* access of which opens an actual security hole. Such classes are known as “deserialization gadget”s.
*
* Because of this, use of "default typing" is not encouraged in general, and in particular is recommended against
* if the source of content is not trusted. Conversely, default typing may be used for processing content in
* cases where both ends (sender and receiver) are controlled by same entity.
*/
public JsonParser setClassMetadataName(final String name) {
classMetadataName = name;
return this;
}

/**
* Sets usage of default class meta-data name.
* Using it may introduce a security hole, see {@link #setClassMetadataName(String)} for more details.
* @see #setClassMetadataName(String)
*/
public JsonParser withClassMetadata(final boolean useMetadata) {
if (useMetadata) {
classMetadataName = Defaults.DEFAULT_CLASS_METADATA_NAME;
Expand All @@ -294,6 +308,26 @@ public JsonParser withClassMetadata(final boolean useMetadata) {
return this;
}

/**
* Adds a {@link jodd.util.Wildcard wildcard} pattern for white-listing classes.
* @see #setClassMetadataName(String)
*/
public JsonParser allowClass(final String classPattern) {
if (super.classnameWhitelist == null) {
super.classnameWhitelist = new ArrayList<>();
}
classnameWhitelist.add(classPattern);
return this;
}

/**
* Removes the whitelist of allowed classes.
* @see #setClassMetadataName(String)
*/
public JsonParser allowAllClasses() {
classnameWhitelist = null;
return this;
}

// ---------------------------------------------------------------- parse

Expand Down
1 change: 1 addition & 0 deletions jodd-json/src/main/java/jodd/json/JsonParserBase.java
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ public abstract class JsonParserBase {

protected Supplier<Map> mapSupplier = HASMAP_SUPPLIER;
protected Supplier<List> listSupplier = ARRAYLIST_SUPPLIER;
protected List<String> classnameWhitelist;

/**
* Creates new instance of {@link jodd.json.MapToBean}.
Expand Down
24 changes: 22 additions & 2 deletions jodd-json/src/main/java/jodd/json/JsonSerializer.java
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,13 @@

package jodd.json;

import jodd.buffer.FastCharBuffer;
import jodd.inex.InExRules;
import jodd.util.ArraysUtil;
import jodd.buffer.FastCharBuffer;

import java.util.HashMap;
import java.util.Map;
import java.util.function.Function;

/**
* JSON serializer.
Expand Down Expand Up @@ -111,6 +112,8 @@ public boolean accept(final Path value, final PathQuery rule, final boolean incl
protected Class[] excludedTypes = Defaults.excludedTypes;
protected String[] excludedTypeNames = Defaults.excludedTypeNames;
protected boolean excludeNulls = false;
protected boolean excludeEmpty = false;
protected Function<Object, TypeJsonSerializer> serializerResolver = null;

/**
* Defines custom {@link jodd.json.TypeJsonSerializer} for given path.
Expand Down Expand Up @@ -265,6 +268,14 @@ public JsonSerializer excludeNulls(final boolean excludeNulls) {
return this;
}

/**
* Excludes empty maps and collections.
*/
public JsonSerializer excludeEmpty(final boolean excludeEmpty) {
this.excludeEmpty = excludeEmpty;
return this;
}

/**
* Specifies strict string encoding.
*/
Expand All @@ -273,6 +284,15 @@ public JsonSerializer strictStringEncoding(final boolean strictStringEncoding) {
return this;
}

/**
* Defines callback for value serialization. It defines the instance of {@link TypeJsonSerializer}
* to be used with the value. If {@code null} is returned, default serializer will be resolved.
*/
public JsonSerializer onValue(final Function<Object, TypeJsonSerializer> function) {
this.serializerResolver = function;
return this;
}

// ---------------------------------------------------------------- serialize

/**
Expand Down Expand Up @@ -312,6 +332,6 @@ public CharSequence serializeToCharSequence(final Object source) {
* Creates new JSON context.
*/
public JsonContext createJsonContext(final Appendable appendable) {
return new JsonContext(this, appendable, excludeNulls, strictStringEncoding);
return new JsonContext(this, appendable);
}
}
14 changes: 14 additions & 0 deletions jodd-json/src/main/java/jodd/json/MapToBean.java
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
import jodd.typeconverter.TypeConverterManager;
import jodd.util.ClassLoaderUtil;
import jodd.util.ClassUtil;
import jodd.util.Wildcard;

import java.lang.reflect.InvocationTargetException;
import java.util.HashMap;
Expand Down Expand Up @@ -69,6 +70,8 @@ public Object map2bean(final Map map, Class targetType) {
}
}
else {
checkClassName(jsonParser.classnameWhitelist, className);

try {
targetType = ClassLoaderUtil.loadClass(className);
} catch (ClassNotFoundException cnfex) {
Expand Down Expand Up @@ -145,6 +148,17 @@ else if (value instanceof Map) {
return target;
}

private void checkClassName(final List<String> classnameWhitelist, final String className) {
if (classnameWhitelist == null) {
return;
}
classnameWhitelist.forEach(pattern -> {
if (!Wildcard.equalsOrMatch(className, pattern)) {
throw new JsonException("Class can't be loaded as it is not whitelisted: " + className);
}
});
}

/**
* Converts type of all list elements to match the component type.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ private void ident(final JsonContext jsonContext) {

@Override
public JsonContext createJsonContext(final Appendable appendable) {
return new JsonContext(this, appendable, excludeNulls, strictStringEncoding) {
return new JsonContext(this, appendable) {
@Override
public void writeOpenArray() {
deep++;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,9 +50,14 @@ protected K get(final K[] array, final int index) {

@Override
public void serializeValue(final JsonContext jsonContext, final Object array) {
final int length = getLength((K[]) array);

if (length == 0 && jsonContext.isExcludeEmpty()) {
return;
}

jsonContext.writeOpenArray();

int length = getLength((K[]) array);
int count = 0;

for (int i = 0; i < length; i++) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,10 @@ public class BooleanArrayJsonSerializer implements TypeJsonSerializer<boolean[]>

@Override
public boolean serialize(final JsonContext jsonContext, final boolean[] array) {
if (array.length == 0 && jsonContext.isExcludeEmpty()) {
return true;
}

jsonContext.writeOpenArray();

for (int i = 0; i < array.length; i++) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,10 @@ public class ByteArrayJsonSerializer implements TypeJsonSerializer<byte[]> {

@Override
public boolean serialize(final JsonContext jsonContext, final byte[] array) {
if (array.length == 0 && jsonContext.isExcludeEmpty()) {
return true;
}

jsonContext.writeOpenArray();

for (int i = 0; i < array.length; i++) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,10 @@ public class DoubleArrayJsonSerializer implements TypeJsonSerializer<double[]> {

@Override
public boolean serialize(final JsonContext jsonContext, final double[] array) {
if (array.length == 0 && jsonContext.isExcludeEmpty()) {
return true;
}

jsonContext.writeOpenArray();

for (int i = 0; i < array.length; i++) {
Expand Down
38 changes: 38 additions & 0 deletions jodd-json/src/main/java/jodd/json/impl/EmptyJsonSerializer.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
// Copyright (c) 2003-present, Jodd Team (http://jodd.org)
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright
// notice, this list of conditions and the following disclaimer in the
// documentation and/or other materials provided with the distribution.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.

package jodd.json.impl;

import jodd.json.JsonContext;

/**
* Not a serializer - simply ignores the value and outputs nothing.
*/
public class EmptyJsonSerializer extends ValueJsonSerializer {

@Override
public void serializeValue(final JsonContext jsonContext, final Object value) {
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,10 @@ public class FloatArrayJsonSerializer implements TypeJsonSerializer<float[]> {

@Override
public boolean serialize(final JsonContext jsonContext, final float[] array) {
if (array.length == 0 && jsonContext.isExcludeEmpty()) {
return true;
}

jsonContext.writeOpenArray();

for (int i = 0; i < array.length; i++) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,10 @@ public class IntArrayJsonSerializer implements TypeJsonSerializer<int[]> {

@Override
public boolean serialize(final JsonContext jsonContext, final int[] array) {
if (array.length == 0 && jsonContext.isExcludeEmpty()) {
return true;
}

jsonContext.writeOpenArray();

for (int i = 0; i < array.length; i++) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,14 @@ public class JsonArraySerializer implements TypeJsonSerializer<JsonArray> {

@Override
public boolean serialize(final JsonContext jsonContext, final JsonArray jsonArray) {
final int length = jsonArray.size();

if (length == 0 && jsonContext.isExcludeEmpty()) {
return true;
}

jsonContext.writeOpenArray();

int length = jsonArray.size();
int count = 0;

for (int i = 0; i < length; i++) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,10 @@ public class LongArrayJsonSerializer implements TypeJsonSerializer<long[]> {

@Override
public boolean serialize(final JsonContext jsonContext, final long[] array) {
if (array.length == 0 && jsonContext.isExcludeEmpty()) {
return true;
}

jsonContext.writeOpenArray();

for (int i = 0; i < array.length; i++) {
Expand Down
3 changes: 3 additions & 0 deletions jodd-json/src/main/java/jodd/json/impl/MapJsonSerializer.java
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,9 @@ public class MapJsonSerializer extends KeyValueJsonSerializer<Map<?, ?>> {

@Override
public void serializeValue(final JsonContext jsonContext, final Map<?, ?> map) {
if (map.isEmpty() && jsonContext.isExcludeEmpty()) {
return;
}
jsonContext.writeOpenObject();

int count = 0;
Expand Down
32 changes: 32 additions & 0 deletions jodd-json/src/test/java/jodd/json/JSONDeserializerTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.fail;

Expand Down Expand Up @@ -709,6 +710,37 @@ void testPoint() {
});
}

@Test
void testPointWithException() {
JsonParser.Defaults.classMetadataName = "__class";
JsonSerializer.Defaults.classMetadataName = "__class";

JsonParsers.forEachParser(jsonParser -> {
jsonParser.allowClass("notAllowed");
final String json = new JsonSerializer().serialize(new Point2D.Float(1.0f, 2.0f));
assertThrows(JsonException.class, () -> {
jsonParser.parse(json);
});
jsonParser.allowAllClasses();
});
}

@Test
void testPointWithoutExceptionWhitelisted() {
JsonParser.Defaults.classMetadataName = "__class";
JsonSerializer.Defaults.classMetadataName = "__class";

JsonParsers.forEachParser(jsonParser -> {
jsonParser.allowClass("*.Point?D*");
String json = new JsonSerializer().serialize(new Point2D.Float(1.0f, 2.0f));
Point2D.Float point = jsonParser.parse(json);
assertEquals(1.0f, point.x, DELTA);
assertEquals(2.0f, point.y, DELTA);
jsonParser.allowAllClasses();
});
}


@Test
void testUnixEpoch() {
JsonParsers.forEachParser(jsonParser -> {
Expand Down
58 changes: 58 additions & 0 deletions jodd-json/src/test/java/jodd/json/JsonSerializerTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
import jodd.json.fixtures.model.FileMan;
import jodd.json.fixtures.model.HitList;
import jodd.json.fixtures.model.State;
import jodd.json.impl.EmptyJsonSerializer;
import jodd.json.meta.JSON;
import jodd.json.meta.JsonAnnotationManager;
import jodd.json.meta.TypeData;
Expand Down Expand Up @@ -555,6 +556,63 @@ void testExcludingNulls() {
assertEquals("{\"one\":{\"id\":1}}", json);
}


@Test
void testExcludeNullCollections() {
Map<String, Object> map = new HashMap<>();
map.put("a", null);

String json = new JsonSerializer().serialize(map);
assertEquals("{\"a\":null}", json);

json = new JsonSerializer().excludeNulls(true).serialize(map);
assertEquals("{}", json);

map.put("b", new HashMap<>());
json = new JsonSerializer().excludeNulls(true).serialize(map);
assertEquals("{\"b\":{}}", json);

json = new JsonSerializer().excludeNulls(true).onValue(value -> {
if (value instanceof Map) {
if (((Map)value).isEmpty()) {
return new EmptyJsonSerializer();
}
}
return null;
}).serialize(map);
assertEquals("{}", json);
}

@Test
void testExcludeNullEmpty() {
Map<String, Object> map = new HashMap<>();
map.put("a", null);

map.put("b", new HashMap<>());

String json = new JsonSerializer().excludeNulls(true).serialize(map);
assertEquals("{\"b\":{}}", json);

json = new JsonSerializer().excludeNulls(true).excludeEmpty(true).serialize(map);
assertEquals("{}", json);

map.put("c", new ArrayList<>());
json = new JsonSerializer().excludeNulls(true).excludeEmpty(true).serialize(map);
assertEquals("{}", json);

map.put("d", new int[0]);
json = new JsonSerializer().excludeNulls(true).excludeEmpty(true).serialize(map);
assertEquals("{}", json);

map.put("e", new HashSet<>());
json = new JsonSerializer().excludeNulls(true).excludeEmpty(true).serialize(map);
assertEquals("{}", json);

map.put("f", new Object[0]);
json = new JsonSerializer().excludeNulls(true).excludeEmpty(true).serialize(map);
assertEquals("{}", json);
}

@Test
void testFiles_on_linux() {
assumeTrue(SystemUtil.info().isLinux() || SystemUtil.info().isMac(), "no linux host");
Expand Down
34 changes: 22 additions & 12 deletions jodd-mail/src/main/java/jodd/mail/ImapServer.java
Original file line number Diff line number Diff line change
Expand Up @@ -27,39 +27,45 @@

import jodd.util.StringPool;

import javax.mail.Authenticator;
import javax.mail.NoSuchProviderException;
import javax.mail.Session;
import javax.mail.Store;
import java.io.File;
import java.util.Properties;

/**
* IMAP Server.
*/
public class ImapServer extends MailServer<ReceiveMailSession> {

protected static final String MAIL_IMAP_PORT = "mail.imap.port";
protected static final String MAIL_IMAP_HOST = "mail.imap.host";
protected static final String MAIL_IMAP_PARTIALFETCH = "mail.imap.partialfetch";

protected static final String PROTOCOL_IMAP = "imap";

/**
* Default IMAP port.
*/
protected static final int DEFAULT_IMAP_PORT = 143;

public ImapServer(final String host, final int port, final Authenticator authenticator, final File attachmentStorage) {
super(host, port == -1 ? DEFAULT_IMAP_PORT : port, authenticator, attachmentStorage);
public ImapServer(final Builder builder) {
super(builder, DEFAULT_IMAP_PORT);
}
protected ImapServer(final Builder builder, final int defaultPort) {
super(builder, defaultPort);
}


@Override
protected Properties createSessionProperties() {
final Properties props = new Properties();
props.setProperty(MAIL_IMAP_HOST, getHost());
props.setProperty(MAIL_IMAP_PORT, String.valueOf(getPort()));
final Properties props = super.createSessionProperties();

props.setProperty(MAIL_IMAP_HOST, host);
props.setProperty(MAIL_IMAP_PORT, String.valueOf(port));
props.setProperty(MAIL_IMAP_PARTIALFETCH, StringPool.FALSE);

if (timeout > 0) {
final String timeoutValue = String.valueOf(timeout);
props.put(MAIL_IMAP_CONNECTIONTIMEOUT, timeoutValue);
props.put(MAIL_IMAP_TIMEOUT, timeoutValue);
}

return props;
}

Expand All @@ -80,7 +86,11 @@ protected Store getStore(final Session session) throws NoSuchProviderException {
*/
@Override
public ReceiveMailSession createSession() {
return EmailUtil.createSession(PROTOCOL_IMAP, getSessionProperties(), getAuthenticator(), getAttachmentStorage());
return EmailUtil.createSession(
PROTOCOL_IMAP,
createSessionProperties(),
authenticator,
attachmentStorage);
}

}
20 changes: 8 additions & 12 deletions jodd-mail/src/main/java/jodd/mail/ImapSslServer.java
Original file line number Diff line number Diff line change
Expand Up @@ -28,37 +28,33 @@
import com.sun.mail.imap.IMAPSSLStore;
import jodd.util.StringPool;

import javax.mail.Authenticator;
import javax.mail.PasswordAuthentication;
import javax.mail.Session;
import javax.mail.URLName;
import java.io.File;
import java.util.Properties;

/**
* IMAP SSL Server.
*/
public class ImapSslServer extends ImapServer {

protected static final String MAIL_IMAP_SOCKET_FACTORY_PORT = "mail.imap.socketFactory.port";
protected static final String MAIL_IMAP_SOCKET_FACTORY_CLASS = "mail.imap.socketFactory.class";
protected static final String MAIL_IMAP_SOCKET_FACTORY_FALLBACK = "mail.imap.socketFactory.fallback";

/**
* Default IMAP SSL port.
*/
protected static final int DEFAULT_SSL_PORT = 993;

public ImapSslServer(final String host, final int port, final Authenticator authenticator, final File attachmentStorage) {
super(host, port == -1 ? DEFAULT_SSL_PORT : port, authenticator, attachmentStorage);
public ImapSslServer(final Builder builder) {
super(builder, DEFAULT_SSL_PORT);
}

@Override
protected Properties createSessionProperties() {
final Properties props = super.createSessionProperties();
props.setProperty(MAIL_IMAP_SOCKET_FACTORY_PORT, String.valueOf(getPort()));

props.setProperty(MAIL_IMAP_SOCKET_FACTORY_PORT, String.valueOf(port));
props.setProperty(MAIL_IMAP_SOCKET_FACTORY_CLASS, "javax.net.ssl.SSLSocketFactory");
props.setProperty(MAIL_IMAP_SOCKET_FACTORY_FALLBACK, StringPool.FALSE);

return props;
}

Expand All @@ -70,21 +66,21 @@ protected Properties createSessionProperties() {
*/
@Override
protected IMAPSSLStore getStore(final Session session) {
SimpleAuthenticator simpleAuthenticator = (SimpleAuthenticator) getAuthenticator();
SimpleAuthenticator simpleAuthenticator = (SimpleAuthenticator) authenticator;

final URLName url;

if (simpleAuthenticator == null) {
url = new URLName(
PROTOCOL_IMAP,
getHost(), getPort(),
host, port,
StringPool.EMPTY, null, null);
}
else {
final PasswordAuthentication pa = simpleAuthenticator.getPasswordAuthentication();
url = new URLName(
PROTOCOL_IMAP,
getHost(), getPort(),
host, port,
StringPool.EMPTY,
pa.getUserName(), pa.getPassword());
}
Expand Down
191 changes: 127 additions & 64 deletions jodd-mail/src/main/java/jodd/mail/MailServer.java
Original file line number Diff line number Diff line change
Expand Up @@ -33,46 +33,92 @@

public abstract class MailServer<MailSessionImpl extends MailSession> {

public static final String MAIL_HOST = "mail.host";
public static final String MAIL_SMTP_HOST = "mail.smtp.host";
public static final String MAIL_SMTP_PORT = "mail.smtp.port";
public static final String MAIL_SMTP_AUTH = "mail.smtp.auth";
public static final String MAIL_TRANSPORT_PROTOCOL = "mail.transport.protocol";
//public static final String MAIL_SMTP_FROM = "mail.smtp.from";

public static final String MAIL_SMTP_CONNECTIONTIMEOUT = "mail.smtp.connectiontimeout";
public static final String MAIL_SMTP_TIMEOUT = "mail.smtp.timeout";
public static final String MAIL_SMTP_WRITETIMEOUT = "mail.smtp.writetimeout";
public static final String MAIL_DEBUG = "mail.debug";
public static final String MAIL_MIME_ADDRESS_STRICT = "mail.mime.address.strict";

public static final String MAIL_IMAP_CONNECTIONTIMEOUT = "mail.imap.connectiontimeout";
public static final String MAIL_IMAP_TIMEOUT = "mail.imap.timeout";
public static final String MAIL_IMAP_PORT = "mail.imap.port";
public static final String MAIL_IMAP_HOST = "mail.imap.host";
public static final String MAIL_IMAP_PARTIALFETCH = "mail.imap.partialfetch";

public static final String MAIL_IMAP_SOCKET_FACTORY_PORT = "mail.imap.socketFactory.port";
public static final String MAIL_IMAP_SOCKET_FACTORY_CLASS = "mail.imap.socketFactory.class";
public static final String MAIL_IMAP_SOCKET_FACTORY_FALLBACK = "mail.imap.socketFactory.fallback";

public static final String MAIL_SMTP_STARTTLS_REQUIRED = "mail.smtp.starttls.required";
public static final String MAIL_SMTP_STARTTLS_ENABLE = "mail.smtp.starttls.enable";
public static final String MAIL_SMTP_SOCKET_FACTORY_PORT = "mail.smtp.socketFactory.port";
public static final String MAIL_SMTP_SOCKET_FACTORY_CLASS = "mail.smtp.socketFactory.class";
public static final String MAIL_SMTP_SOCKET_FACTORY_FALLBACK = "mail.smtp.socketFactory.fallback";

public static final String MAIL_POP3_PORT = "mail.pop3.port";
public static final String MAIL_POP3_HOST = "mail.pop3.host";
public static final String MAIL_POP3_AUTH = "mail.pop3.auth";
public static final String MAIL_POP3_CONNECTIONTIMEOUT = "mail.pop3.connectiontimeout";
public static final String MAIL_POP3_TIMEOUT = "mail.pop3.timeout";

public static final String MAIL_POP3_SOCKET_FACTORY_PORT = "mail.pop3.socketFactory.port";
public static final String MAIL_POP3_SOCKET_FACTORY_CLASS = "mail.pop3.socketFactory.class";
public static final String MAIL_POP3_SOCKET_FACTORY_FALLBACK = "mail.pop3.socketFactory.fallback";

/**
* The host.
*/
private final String host;
protected final String host;

/**
* The port.
*/
private final int port;
protected final int port;

/**
* The {@link Authenticator}.
*/
private final Authenticator authenticator;
protected final Authenticator authenticator;

protected final File attachmentStorage;

private final File attachmentStorage;
protected final boolean debugMode;

/**
* The {@link MailSession} {@link Properties}.
* Whether strict address checking is turned on.
*/
private final Properties sessionProperties;
protected final boolean strictAddress;

/**
* Connection timeout.
*/
protected final int timeout;

protected final Properties customProperties;

/**
* {@link MailServer} defined with its host, port and {@link Authenticator}.
*
* @param host The host to use.
* @param port The port to use.
* @param authenticator The {@link Authenticator} to use.
*/
protected MailServer(final String host, final int port, final Authenticator authenticator, final File attachmentStorage) {
Objects.requireNonNull(host, "Host cannot be null");

this.host = host;
this.port = port;
this.authenticator = authenticator;
this.attachmentStorage = attachmentStorage;
this.sessionProperties = createSessionProperties();
protected MailServer(final Builder builder, final int defaultPort) {
Objects.requireNonNull(builder.host, "Host cannot be null");

this.host = builder.host;
this.port = builder.port == -1 ? defaultPort : builder.port;
this.authenticator = builder.authenticator;
this.attachmentStorage = builder.attachmentStorage;
this.timeout = builder.timeout;
this.strictAddress = builder.strictAddress;
this.debugMode = builder.debug;
this.customProperties = builder.customProperties;
}


/**
* Creates new mail session.
*
Expand All @@ -87,48 +133,20 @@ protected MailServer(final String host, final int port, final Authenticator auth
*
* @return session {@link Properties}
*/
protected abstract Properties createSessionProperties();

// ---------------------------------------------------------------- properties

/**
* Returns the host.
*
* @return The host.
*/
public String getHost() {
return host;
}
protected Properties createSessionProperties() {
final Properties props = new Properties();

/**
* Returns the {@link Authenticator}.
*
* @return The current {@link Authenticator}.
*/
public Authenticator getAuthenticator() {
return authenticator;
}
props.putAll(customProperties);

/**
* Returns current port.
*
* @return The current port.
*/
public int getPort() {
return port;
}
if (debugMode) {
props.put(MAIL_DEBUG, "true");
}

/**
* Returns {@link MailSession} {@link Properties}.
*
* @return The {@link MailSession} {@link Properties}.
*/
public Properties getSessionProperties() {
return sessionProperties;
}
if (!strictAddress) {
props.put(MAIL_MIME_ADDRESS_STRICT, "false");
}

public File getAttachmentStorage() {
return attachmentStorage;
return props;
}

/**
Expand Down Expand Up @@ -156,13 +174,16 @@ public static class Builder {
private boolean ssl = false;
private Authenticator authenticator;
private File attachmentStorage;
private boolean debug;
private int timeout = 0;
private boolean strictAddress = true;
private Properties customProperties = new Properties();

/**
* Sets the host.
*
* @param host The host to set.
* @return this
*
*/
public Builder host(final String host) {
this.host = host;
Expand Down Expand Up @@ -192,6 +213,9 @@ public Builder ssl(final boolean ssl) {
return this;
}

/**
* Defines attachment storage, a folder where attachments will be saved.
*/
public Builder storeAttachmentsIn(final File attachmentStorage) {
this.attachmentStorage = attachmentStorage;
return this;
Expand Down Expand Up @@ -222,6 +246,45 @@ public Builder auth(final Authenticator authenticator) {
return this;
}

/**
* Enable or disable debug mode.
*
* @param debug {@code true} to turn on debugging. By default, this is {@code false}.
* @return this
*/
public Builder debugMode(final boolean debug) {
this.debug = debug;
return this;
}


/**
* Defines timeout value in milliseconds for all mail-related operations.
*
* @param timeout timeout value in milliseconds.
* @return this
*/
public Builder timeout(final int timeout) {
this.timeout = timeout;
return this;
}

/**
* Disables the strict address.
*
* @param strictAddress {@code true} if strict address checking should be be turned on. By default, this is {@code true}.
* @return this
*/
public Builder strictAddress(final boolean strictAddress) {
this.strictAddress = strictAddress;
return this;
}

public Builder property(final String name, final String value) {
this.customProperties.put(name, value);
return this;
}

// ---------------------------------------------------------------- build

/**
Expand All @@ -231,9 +294,9 @@ public Builder auth(final Authenticator authenticator) {
*/
public ImapServer buildImapMailServer() {
if (ssl) {
return new ImapSslServer(host, port, authenticator, attachmentStorage);
return new ImapSslServer(this);
}
return new ImapServer(host, port, authenticator, attachmentStorage);
return new ImapServer(this);
}

/**
Expand All @@ -244,9 +307,9 @@ public ImapServer buildImapMailServer() {
*/
public Pop3Server buildPop3MailServer() {
if (ssl) {
return new Pop3SslServer(host, port, authenticator, attachmentStorage);
return new Pop3SslServer(this);
}
return new Pop3Server(host, port, authenticator, attachmentStorage);
return new Pop3Server(this);
}

/**
Expand All @@ -257,9 +320,9 @@ public Pop3Server buildPop3MailServer() {
*/
public SmtpServer buildSmtpMailServer() {
if (ssl) {
return new SmtpSslServer(host, port, authenticator);
return new SmtpSslServer(this);
}
return new SmtpServer(host, port, authenticator);
return new SmtpServer(this);
}
}
}
34 changes: 23 additions & 11 deletions jodd-mail/src/main/java/jodd/mail/Pop3Server.java
Original file line number Diff line number Diff line change
Expand Up @@ -39,29 +39,37 @@
*/
public class Pop3Server extends MailServer<ReceiveMailSession> {

protected static final String MAIL_POP3_PORT = "mail.pop3.port";
protected static final String MAIL_POP3_HOST = "mail.pop3.host";
protected static final String MAIL_POP3_AUTH = "mail.pop3.auth";

protected static final String PROTOCOL_POP3 = "pop3";

/**
* Default POP3 port.
*/
protected static final int DEFAULT_POP3_PORT = 110;

public Pop3Server(final String host, final int port, final Authenticator authenticator, final File attachmentStorage) {
super(host, port == -1 ? DEFAULT_POP3_PORT : port, authenticator, attachmentStorage);
public Pop3Server(final Builder builder) {
super(builder, DEFAULT_POP3_PORT);
}
protected Pop3Server(final Builder builder, final int defaultPort) {
super(builder, defaultPort);
}

@Override
protected Properties createSessionProperties() {
final Properties props = new Properties();
props.setProperty(MAIL_POP3_HOST, getHost());
props.setProperty(MAIL_POP3_PORT, String.valueOf(getPort()));
if (getAuthenticator() != null) {
final Properties props = super.createSessionProperties();

props.setProperty(MAIL_POP3_HOST, host);
props.setProperty(MAIL_POP3_PORT, String.valueOf(port));

if (authenticator != null) {
props.setProperty(MAIL_POP3_AUTH, TRUE);
}

if (timeout > 0) {
final String timeoutValue = String.valueOf(timeout);
props.put(MAIL_POP3_CONNECTIONTIMEOUT, timeoutValue);
props.put(MAIL_POP3_TIMEOUT, timeoutValue);
}

return props;
}

Expand All @@ -83,7 +91,11 @@ protected Store getStore(final Session session) throws NoSuchProviderException {
*/
@Override
public ReceiveMailSession createSession() {
return EmailUtil.createSession(PROTOCOL_POP3, getSessionProperties(), getAuthenticator(), getAttachmentStorage());
return EmailUtil.createSession(
PROTOCOL_POP3,
createSessionProperties(),
authenticator,
attachmentStorage);
}

}
19 changes: 8 additions & 11 deletions jodd-mail/src/main/java/jodd/mail/Pop3SslServer.java
Original file line number Diff line number Diff line change
Expand Up @@ -28,33 +28,30 @@
import com.sun.mail.pop3.POP3SSLStore;
import jodd.util.StringPool;

import javax.mail.Authenticator;
import javax.mail.PasswordAuthentication;
import javax.mail.Session;
import javax.mail.URLName;
import java.io.File;
import java.util.Properties;

/**
* POP3 SSL server.
*/
public class Pop3SslServer extends Pop3Server {

protected static final String MAIL_POP3_SOCKET_FACTORY_PORT = "mail.pop3.socketFactory.port";
protected static final String MAIL_POP3_SOCKET_FACTORY_CLASS = "mail.pop3.socketFactory.class";
protected static final String MAIL_POP3_SOCKET_FACTORY_FALLBACK = "mail.pop3.socketFactory.fallback";
protected static final int DEFAULT_SSL_PORT = 995;

public Pop3SslServer(final String host, final int port, final Authenticator authenticator, final File attachmentStorage) {
super(host, port == -1 ? DEFAULT_SSL_PORT : port, authenticator, attachmentStorage);
public Pop3SslServer(final Builder builder) {
super(builder, DEFAULT_SSL_PORT);
}

@Override
protected Properties createSessionProperties() {
final Properties props = super.createSessionProperties();
props.setProperty(MAIL_POP3_SOCKET_FACTORY_PORT, String.valueOf(getPort()));

props.setProperty(MAIL_POP3_SOCKET_FACTORY_PORT, String.valueOf(port));
props.setProperty(MAIL_POP3_SOCKET_FACTORY_CLASS, "javax.net.ssl.SSLSocketFactory");
props.setProperty(MAIL_POP3_SOCKET_FACTORY_FALLBACK, StringPool.FALSE);

return props;
}

Expand All @@ -66,21 +63,21 @@ protected Properties createSessionProperties() {
*/
@Override
protected POP3SSLStore getStore(final Session session) {
final SimpleAuthenticator simpleAuthenticator = (SimpleAuthenticator) getAuthenticator();
final SimpleAuthenticator simpleAuthenticator = (SimpleAuthenticator) authenticator;
final URLName url;

if (simpleAuthenticator == null) {
url = new URLName(
PROTOCOL_POP3,
getHost(), getPort(),
host, port,
StringPool.EMPTY,
null, null);
}
else {
final PasswordAuthentication pa = simpleAuthenticator.getPasswordAuthentication();
url = new URLName(
PROTOCOL_POP3,
getHost(), getPort(),
host, port,
StringPool.EMPTY,
pa.getUserName(), pa.getPassword());
}
Expand Down
7 changes: 3 additions & 4 deletions jodd-mail/src/main/java/jodd/mail/RFC2822AddressParser.java
Original file line number Diff line number Diff line change
Expand Up @@ -280,8 +280,7 @@ public String getReturnPathAddress() {
}

/**
* Parses email address. Returns {@code null} if parsing fails for some reason.
* Returns {@link ParsedAddress parsed address}, that might be valid or note.
* Parses email address. Returns {@link ParsedAddress parsed address}, that might be valid or not.
*/
public ParsedAddress parse(String email) {
email = email.trim();
Expand Down Expand Up @@ -327,7 +326,7 @@ public ParsedAddress parse(String email) {
public InternetAddress parseToInternetAddress(final String email) {
final ParsedAddress parsedAddress = parse(email);

if (parsedAddress == null) {
if (!parsedAddress.isValid()) {
return null;
}

Expand All @@ -340,7 +339,7 @@ public InternetAddress parseToInternetAddress(final String email) {
public EmailAddress parseToEmailAddress(final String email) {
final ParsedAddress parsedAddress = parse(email);

if (parsedAddress == null) {
if (!parsedAddress.isValid()) {
return null;
}

Expand Down
97 changes: 11 additions & 86 deletions jodd-mail/src/main/java/jodd/mail/SmtpServer.java
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@

package jodd.mail;

import javax.mail.Authenticator;
import javax.mail.NoSuchProviderException;
import javax.mail.Session;
import javax.mail.Transport;
Expand All @@ -36,20 +35,7 @@
/**
* Represents simple plain SMTP server for sending emails.
*/
public class SmtpServer<T extends SmtpServer<T>> extends MailServer<SendMailSession> {

public static final String MAIL_HOST = "mail.host";
public static final String MAIL_SMTP_HOST = "mail.smtp.host";
public static final String MAIL_SMTP_PORT = "mail.smtp.port";
public static final String MAIL_SMTP_AUTH = "mail.smtp.auth";
public static final String MAIL_TRANSPORT_PROTOCOL = "mail.transport.protocol";
public static final String MAIL_SMTP_FROM = "mail.smtp.from";

public static final String MAIL_SMTP_CONNECTIONTIMEOUT = "mail.smtp.connectiontimeout";
public static final String MAIL_SMTP_TIMEOUT = "mail.smtp.timeout";
public static final String MAIL_SMTP_WRITETIMEOUT = "mail.smtp.writetimeout";
public static final String MAIL_DEBUG = "mail.debug";
public static final String MAIL_MIME_ADDRESS_STRICT = "mail.mime.address.strict";
public class SmtpServer extends MailServer<SendMailSession> {

protected static final String PROTOCOL_SMTP = "smtp";

Expand All @@ -58,80 +44,27 @@ public class SmtpServer<T extends SmtpServer<T>> extends MailServer<SendMailSess
*/
protected static final int DEFAULT_SMTP_PORT = 25;

/**
* Whether debug mode is enabled.
*/
protected boolean debug = false;

/**
* Whether strict address checking is turned on.
*/
protected boolean strictAddress = true;

/**
* Connection timeout.
*/
private int timeout = 0;

// ---------------------------------------------------------------- create

@SuppressWarnings("unchecked")
protected T _this() {
return (T) this;
public SmtpServer(final Builder builder) {
super(builder, DEFAULT_SMTP_PORT);
}

public SmtpServer(final String host, final int port, final Authenticator authenticator) {
super(host, port == -1 ? DEFAULT_SMTP_PORT : port, authenticator, null);
}

// ---------------------------------------------------------------- builder

/**
* Defines timeout value in milliseconds for all mail-related operations.
*
* @param timeout timeout value in milliseconds.
* @return this
*/
public T timeout(final int timeout) {
this.timeout = timeout;
return _this();
}

/**
* Enable or disable debug mode.
*
* @param debug {@code true} to turn on debugging. By default, this is {@code false}.
* @return this
*/
public T debugMode(final boolean debug) {
this.debug = debug;
return _this();
}


/**
* Disables the strict address.
*
* @param strictAddress {@code true} if strict address checking should be be turned on. By default, this is {@code true}.
* @return this
*/
public T strictAddress(final boolean strictAddress) {
this.strictAddress = strictAddress;
return _this();
protected SmtpServer(final Builder builder, final int defaultPort) {
super(builder, defaultPort);
}

// ---------------------------------------------------------------- properties

@Override
protected Properties createSessionProperties() {
final Properties props = new Properties();
final Properties props = super.createSessionProperties();

props.setProperty(MAIL_TRANSPORT_PROTOCOL, PROTOCOL_SMTP);
props.setProperty(MAIL_HOST, getHost());
props.setProperty(MAIL_SMTP_HOST, getHost());
props.setProperty(MAIL_SMTP_PORT, String.valueOf(getPort()));
props.setProperty(MAIL_HOST, host);
props.setProperty(MAIL_SMTP_HOST, host);
props.setProperty(MAIL_SMTP_PORT, String.valueOf(port));

if (getAuthenticator() != null) {
if (authenticator != null) {
props.setProperty(MAIL_SMTP_AUTH, TRUE);
}

Expand All @@ -142,14 +75,6 @@ protected Properties createSessionProperties() {
props.put(MAIL_SMTP_WRITETIMEOUT, timeoutValue);
}

if (debug) {
props.put(MAIL_DEBUG, "true");
}

if (!strictAddress) {
props.put(MAIL_MIME_ADDRESS_STRICT, "false");
}

return props;
}

Expand All @@ -160,7 +85,7 @@ protected Properties createSessionProperties() {
*/
@Override
public SendMailSession createSession() {
final Session session = Session.getInstance(getSessionProperties(), getAuthenticator());
final Session session = Session.getInstance(createSessionProperties(), authenticator);
final Transport mailTransport;
try {
mailTransport = getTransport(session);
Expand Down
19 changes: 6 additions & 13 deletions jodd-mail/src/main/java/jodd/mail/SmtpSslServer.java
Original file line number Diff line number Diff line change
Expand Up @@ -27,27 +27,20 @@

import jodd.util.StringPool;

import javax.mail.Authenticator;
import java.util.Properties;

/**
* Secure SMTP server (STARTTLS) for sending emails.
*/
public class SmtpSslServer extends SmtpServer<SmtpSslServer> {

public static final String MAIL_SMTP_STARTTLS_REQUIRED = "mail.smtp.starttls.required";
public static final String MAIL_SMTP_STARTTLS_ENABLE = "mail.smtp.starttls.enable";
public static final String MAIL_SMTP_SOCKET_FACTORY_PORT = "mail.smtp.socketFactory.port";
public static final String MAIL_SMTP_SOCKET_FACTORY_CLASS = "mail.smtp.socketFactory.class";
public static final String MAIL_SMTP_SOCKET_FACTORY_FALLBACK = "mail.smtp.socketFactory.fallback";
public class SmtpSslServer extends SmtpServer {

/**
* Default SMTP SSL port.
*/
protected static final int DEFAULT_SSL_PORT = 465;

public SmtpSslServer(final String host, final int port, final Authenticator authenticator) {
super(host, port == -1 ? DEFAULT_SSL_PORT : port, authenticator);
public SmtpSslServer(final Builder builder) {
super(builder, DEFAULT_SSL_PORT);
}

// ---------------------------------------------------------------- properties
Expand Down Expand Up @@ -97,16 +90,16 @@ protected Properties createSessionProperties() {

props.setProperty(MAIL_SMTP_STARTTLS_ENABLE, StringPool.TRUE);

props.setProperty(MAIL_SMTP_SOCKET_FACTORY_PORT, String.valueOf(getPort()));
props.setProperty(MAIL_SMTP_SOCKET_FACTORY_PORT, String.valueOf(port));

props.setProperty(MAIL_SMTP_PORT, String.valueOf(getPort()));
props.setProperty(MAIL_SMTP_PORT, String.valueOf(port));

if (!plaintextOverTLS) {
props.setProperty(MAIL_SMTP_SOCKET_FACTORY_CLASS, "javax.net.ssl.SSLSocketFactory");
}

props.setProperty(MAIL_SMTP_SOCKET_FACTORY_FALLBACK, StringPool.FALSE);
props.setProperty(MAIL_HOST, getHost());
props.setProperty(MAIL_HOST, host);

return props;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertTrue;

class RFC2822AddressParserTest {
Expand Down Expand Up @@ -124,4 +125,10 @@ void testValidEmails2() {
assertFalse(new RFC2822AddressParser().parse("me\\@example.com").isValid());
}

@Test
void testInvalidEmailReturnsNull() {
assertNull(new RFC2822AddressParser().parseToEmailAddress("xxxx"));
assertNull(new RFC2822AddressParser().parseToInternetAddress("xxxx"));
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,9 @@

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

/**
* Resolves properties.
Expand All @@ -50,38 +52,51 @@ public PropertyResolver(final ReferencesResolver referencesResolver) {
/**
* Resolves all properties for given type.
*/
public PropertyInjectionPoint[] resolve(final Class type, final boolean autowire) {
public PropertyInjectionPoint[] resolve(Class type, final boolean autowire) {
final List<PropertyInjectionPoint> list = new ArrayList<>();
final Set<String> usedPropertyNames = new HashSet<>();

// lookup fields
ClassDescriptor cd = ClassIntrospector.get().lookup(type);
List<PropertyInjectionPoint> list = new ArrayList<>();
PropertyDescriptor[] allPropertyDescriptors = cd.getAllPropertyDescriptors();
while (type != Object.class) {

for (PropertyDescriptor propertyDescriptor : allPropertyDescriptors) {
final ClassDescriptor cd = ClassIntrospector.get().lookup(type);
final PropertyDescriptor[] allPropertyDescriptors = cd.getAllPropertyDescriptors();

if (propertyDescriptor.isGetterOnly()) {
continue;
}
for (PropertyDescriptor propertyDescriptor : allPropertyDescriptors) {

Class propertyType = propertyDescriptor.getType();
if (ClassUtil.isTypeOf(propertyType, Collection.class)) {
continue;
}
if (propertyDescriptor.isGetterOnly()) {
continue;
}

BeanReferences reference = referencesResolver.readReferenceFromAnnotation(propertyDescriptor);
if (usedPropertyNames.contains(propertyDescriptor.getName())) {
continue;
}

if (reference == null) {
if (!autowire) {
Class propertyType = propertyDescriptor.getType();
if (ClassUtil.isTypeOf(propertyType, Collection.class)) {
continue;
}
else {
reference = referencesResolver.buildDefaultReference(propertyDescriptor);

BeanReferences reference = referencesResolver.readReferenceFromAnnotation(propertyDescriptor);

if (reference == null) {
if (!autowire) {
continue;
} else {
reference = referencesResolver.buildDefaultReference(propertyDescriptor);
}
}

list.add(new PropertyInjectionPoint(propertyDescriptor, reference));

usedPropertyNames.add(propertyDescriptor.getName());
}

list.add(new PropertyInjectionPoint(propertyDescriptor, reference));
// go to the supertype
type = type.getSuperclass();
}

PropertyInjectionPoint[] fields;
final PropertyInjectionPoint[] fields;

if (list.isEmpty()) {
fields = PropertyInjectionPoint.EMPTY;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,27 @@ public BeanReferences readReferenceFromAnnotation(final PropertyDescriptor prope
return reference;
}

public BeanReferences readReferenceFromAnnotation(final FieldDescriptor fieldDescriptor) {
final PetiteInject ref = fieldDescriptor.getField().getAnnotation(PetiteInject.class);

if (ref == null) {
return null;
}

BeanReferences reference = null;

String name = ref.value().trim();
if (name.length() != 0) {
reference = BeanReferences.of(name);
}

//reference = updateReferencesWithDefaultsIfNeeded(propertyDescriptor, reference);

reference = reference.removeDuplicateNames();

return reference;
}

/**
* Extracts references from method or constructor annotation.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,58 +23,28 @@
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.

package jodd.mail;
package jodd.petite;

import jodd.petite.fixtures.tst.Loo;
import jodd.petite.fixtures.tst.YujinpingBaseService;
import jodd.petite.fixtures.tst.YujinpingUserService;
import org.junit.jupiter.api.Test;

import java.util.Properties;
import static org.junit.jupiter.api.Assertions.assertTrue;

import static jodd.mail.SmtpServer.MAIL_SMTP_FROM;
import static org.junit.jupiter.api.Assertions.assertEquals;

class SmtpServerTest {

private static final String SOME_HOST_COM = "some.host.com";
private static final int PORT = 587;
private static final String FROM = "bounce@jodd.org";
private static final String USERNAME = "test";
private static final String PASSWORD = "password";

@Test
void testAddsPropertyToServerSession() {
final Properties overridenProperties = new Properties();

overridenProperties.setProperty(MAIL_SMTP_FROM, FROM);

final SmtpServer smtpServer = MailServer.create()
.host(SOME_HOST_COM)
.port(PORT)
.auth(USERNAME, PASSWORD)
.buildSmtpMailServer()
.timeout(10);

smtpServer.getSessionProperties().putAll(overridenProperties);

assertFrom(smtpServer);
}
public class PrivateInjectionTest {

@Test
void testAddsPropertyToServerSession2() {
final SmtpServer smtpServer = MailServer.create()
.host(SOME_HOST_COM)
.port(PORT)
.auth(USERNAME, PASSWORD)
.ssl(true)
.buildSmtpMailServer()
.timeout(10);
void testPrivateInjection() {
PetiteContainer pc = new PetiteContainer();

smtpServer.getSessionProperties().setProperty(MAIL_SMTP_FROM, FROM);
pc.registerPetiteBean(Loo.class);
pc.registerPetiteBean(YujinpingBaseService.class);
pc.registerPetiteBean(YujinpingUserService.class);

assertFrom(smtpServer);
}
YujinpingUserService service = pc.getBean(YujinpingUserService.class);

private void assertFrom(final MailServer server) {
final Properties sessionProperties = server.createSession().getSession().getProperties();
assertEquals(FROM, sessionProperties.getProperty(MAIL_SMTP_FROM));
assertTrue(service.check2());
assertTrue(service.check());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
// Copyright (c) 2003-present, Jodd Team (http://jodd.org)
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright
// notice, this list of conditions and the following disclaimer in the
// documentation and/or other materials provided with the distribution.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.

package jodd.petite.fixtures.tst;

import jodd.petite.meta.PetiteInject;

public class YujinpingBaseService {

@PetiteInject
private Loo loo;

public boolean check() {
return loo != null;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
// Copyright (c) 2003-present, Jodd Team (http://jodd.org)
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright
// notice, this list of conditions and the following disclaimer in the
// documentation and/or other materials provided with the distribution.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.

package jodd.petite.fixtures.tst;

import jodd.petite.meta.PetiteInject;

public class YujinpingUserService extends YujinpingBaseService {

@PetiteInject
private Loo loo2;

public boolean check2() {
return loo2 != null;
}

}