package communitycommons;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.StringReader;
import java.security.DigestException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Random;
import java.util.UUID;
import java.util.regex.MatchResult;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.crypto.Cipher;
import javax.crypto.Mac;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import javax.swing.text.MutableAttributeSet;
import javax.swing.text.html.HTML;
import javax.swing.text.html.HTMLEditorKit;
import javax.swing.text.html.parser.ParserDelegator;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringEscapeUtils;
import org.owasp.validator.html.AntiSamy;
import org.owasp.validator.html.CleanResults;
import org.owasp.validator.html.Policy;
import com.google.common.base.Function;
import com.mendix.core.Core;
import com.mendix.systemwideinterfaces.MendixRuntimeException;
import com.mendix.systemwideinterfaces.core.IContext;
import com.mendix.systemwideinterfaces.core.IMendixObject;
import communitycommons.proxies.XSSPolicy;
import system.proxies.FileDocument;
public class StringUtils
{
public static final String HASH_ALGORITHM = "SHA-256";
public static String hash(String value, int length) throws NoSuchAlgorithmException, DigestException
{
byte[] inBytes = value.getBytes();
byte[] outBytes = new byte[length];
MessageDigest alg=MessageDigest.getInstance(HASH_ALGORITHM);
alg.update(inBytes);
alg.digest(outBytes, 0, length);
return String.valueOf(outBytes);
}
public static String regexReplaceAll(String haystack, String needleRegex,
String replacement)
{
Pattern pattern = Pattern.compile(needleRegex);
Matcher matcher = pattern.matcher(haystack);
return matcher.replaceAll(replacement);
}
public static boolean regexTest(String value, String regex)
{
return Pattern.matches(regex, value);
}
public static String leftPad(String value, Long amount, String fillCharacter)
{
if (fillCharacter == null || fillCharacter.length() == 0) {
return org.apache.commons.lang3.StringUtils.leftPad(value, amount.intValue(), " ");
}
return org.apache.commons.lang3.StringUtils.leftPad(value, amount.intValue(), fillCharacter);
}
public static String rightPad(String value, Long amount, String fillCharacter)
{
if (fillCharacter == null || fillCharacter.length() == 0) {
return org.apache.commons.lang3.StringUtils.rightPad(value, amount.intValue(), " ");
}
return org.apache.commons.lang3.StringUtils.rightPad(value, amount.intValue(), fillCharacter);
}
public static String randomString(int length)
{
return org.apache.commons.lang3.RandomStringUtils.randomAlphanumeric(length);
}
public static String substituteTemplate(final IContext context, String template,
final IMendixObject substitute, final boolean HTMLEncode, final String datetimeformat) {
return regexReplaceAll(template, "\\{(@)?([\\w./]+)\\}", new Function<MatchResult, String>() {
@Override
public String apply(MatchResult match)
{
String value;
String path = match.group(2);
if (match.group(1) != null)
value = String.valueOf(Core.getConfiguration().getConstantValue(path));
else {
try
{
value = ORM.getValueOfPath(context, substitute, path, datetimeformat);
}
catch (Exception e)
{
throw new RuntimeException(e);
}
}
return HTMLEncode ? HTMLEncode(value) : value;
}
});
}
public static String regexReplaceAll(String source, String regexString, Function<MatchResult, String> replaceFunction) {
if (source == null || source.trim().isEmpty()) // avoid NPE's, save CPU
return "";
StringBuffer resultString = new StringBuffer();
Pattern regex = Pattern.compile(regexString);
Matcher regexMatcher = regex.matcher(source);
while (regexMatcher.find()) {
MatchResult match = regexMatcher.toMatchResult();
String value = replaceFunction.apply(match);
regexMatcher.appendReplacement(resultString, Matcher.quoteReplacement(value));
}
regexMatcher.appendTail(resultString);
return resultString.toString();
}
public static String HTMLEncode(String value)
{
return StringEscapeUtils.escapeHtml4(value);
}
public static String randomHash()
{
return UUID.randomUUID().toString();
}
public static String base64Decode(String encoded)
{
if (encoded == null)
return null;
return new String(Base64.decodeBase64(encoded.getBytes()));
}
public static void base64DecodeToFile(IContext context, String encoded, FileDocument targetFile) throws Exception
{
if (targetFile == null)
throw new IllegalArgumentException("Source file is null");
if (encoded == null)
throw new IllegalArgumentException("Source data is null");
byte [] decoded = Base64.decodeBase64(encoded.getBytes());
Core.storeFileDocumentContent(context, targetFile.getMendixObject(), new ByteArrayInputStream(decoded));
}
public static String base64Encode(String value)
{
if (value == null)
return null;
return new String(Base64.encodeBase64(value.getBytes()));
}
public static String base64EncodeFile(IContext context, FileDocument file) throws IOException
{
if (file == null)
throw new IllegalArgumentException("Source file is null");
if (!file.getHasContents())
throw new IllegalArgumentException("Source file has no contents!");
InputStream f = Core.getFileDocumentContent(context, file.getMendixObject());
return new String(Base64.encodeBase64(IOUtils.toByteArray(f)));
}
public static String stringFromFile(IContext context, FileDocument source) throws IOException
{
if (source == null)
return null;
InputStream f = Core.getFileDocumentContent(context, source.getMendixObject());
return org.apache.commons.io.IOUtils.toString(f);
}
public static void stringToFile(IContext context, String value, FileDocument destination)
{
if (destination == null)
throw new IllegalArgumentException("Destination file is null");
if (value == null)
throw new IllegalArgumentException("Value to write is null");
Core.storeFileDocumentContent(context, destination.getMendixObject(), IOUtils.toInputStream(value));
}
public static String HTMLToPlainText(String html) throws IOException
{
if (html == null)
return "";
final StringBuffer result = new StringBuffer();
HTMLEditorKit.ParserCallback callback =
new HTMLEditorKit.ParserCallback () {
@Override
public void handleText(char[] data, int pos) {
result.append(data); //TODO: needds to be html entity decode?
}
@Override
public void handleComment(char[] data, int pos) {
//Do nothing
}
@Override
public void handleError(String errorMsg, int pos) {
//Do nothing
}
@Override
public void handleSimpleTag(HTML.Tag tag, MutableAttributeSet a, int pos) {
if (tag == HTML.Tag.BR)
result.append("\r\n");
}
@Override
public void handleEndTag(HTML.Tag tag, int pos){
if (tag == HTML.Tag.P)
result.append("\r\n");
}
};
new ParserDelegator().parse(new StringReader(html), callback, true);
return result.toString();
}
public static String XSSSanitize(String html, XSSPolicy policy)
throws Exception {
if (html == null)
return "";
// return HtmlSanitizer.sanitize(html);
String policyString = policy == null ? "tinymce" : policy.toString()
.toLowerCase();
return XSSSanitize(html, policyString);
}
public static String XSSSanitize(String html, String policyString)
throws Exception {
if (html == null)
return "";
if (policyString == null)
throw new Exception("Unable to perform XSS sanitization: policyString is null");
String filename = Core.getConfiguration().getResourcesPath() + File.separator
+ "communitycommons" + File.separator + "antisamy"
+ File.separator + "antisamy-" + policyString + "-1.4.4.xml";
AntiSamy as = new AntiSamy(); // Create AntiSamy object
Policy p = Policy.getInstance(filename);
try {
CleanResults cr = as.scan(html, p, AntiSamy.SAX);
return cr.getCleanHTML();
} catch (Exception e) {
throw new Exception("Unable to perform XSS sanitization: "
+ e.getMessage(), e);
}
}
private static final String ALPHA_CAPS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
private static final String ALPHA = "abcdefghijklmnopqrstuvwxyz";
private static final String NUM = "0123456789";
private static final String SPL_CHARS = "!@#$%^&*_=+-/";
/**
* Returns a random strong password containing at least one number, lowercase character, uppercase character and strange character
* @param length
* @return
*/
public static String randomStrongPassword(int minLen, int maxLen, int noOfCAPSAlpha,
int noOfDigits, int noOfSplChars) {
if(minLen > maxLen)
throw new IllegalArgumentException("Min. Length > Max. Length!");
if( (noOfCAPSAlpha + noOfDigits + noOfSplChars) > minLen )
throw new IllegalArgumentException
("Min. Length should be atleast sum of (CAPS, DIGITS, SPL CHARS) Length!");
Random rnd = new Random();
int len = rnd.nextInt(maxLen - minLen + 1) + minLen;
char[] pswd = new char[len];
int index = 0;
for (int i = 0; i < noOfCAPSAlpha; i++) {
index = getNextIndex(rnd, len, pswd);
pswd[index] = ALPHA_CAPS.charAt(rnd.nextInt(ALPHA_CAPS.length()));
}
for (int i = 0; i < noOfDigits; i++) {
index = getNextIndex(rnd, len, pswd);
pswd[index] = NUM.charAt(rnd.nextInt(NUM.length()));
}
for (int i = 0; i < noOfSplChars; i++) {
index = getNextIndex(rnd, len, pswd);
pswd[index] = SPL_CHARS.charAt(rnd.nextInt(SPL_CHARS.length()));
}
for(int i = 0; i < len; i++) {
if(pswd[i] == 0) {
pswd[i] = ALPHA.charAt(rnd.nextInt(ALPHA.length()));
}
}
return String.valueOf(pswd);
}
private static int getNextIndex(Random rnd, int len, char[] pswd) {
int index = rnd.nextInt(len);
while(pswd[index = rnd.nextInt(len)] != 0);
return index;
}
public static String encryptString(String key, String valueToEncrypt) throws Exception
{
if (valueToEncrypt == null)
return null;
if (key == null)
throw new MendixRuntimeException("Key should not be empty");
if (key.length() != 16)
throw new MendixRuntimeException("Key length should be 16");
Cipher c = Cipher.getInstance("AES/CBC/PKCS5PADDING");
SecretKeySpec k = new SecretKeySpec(key.getBytes(), "AES");
c.init(Cipher.ENCRYPT_MODE, k);
byte[] encryptedData = c.doFinal(valueToEncrypt.getBytes());
byte[] iv = c.getIV();
return new String(Base64.encodeBase64(iv)) + ";" + new String(Base64.encodeBase64(encryptedData));
}
public static String decryptString(String key, String valueToDecrypt) throws Exception
{
if (valueToDecrypt == null)
return null;
if (key == null)
throw new MendixRuntimeException("Key should not be empty");
if (key.length() != 16)
throw new MendixRuntimeException("Key length should be 16");
Cipher c = Cipher.getInstance("AES/CBC/PKCS5PADDING");
SecretKeySpec k = new SecretKeySpec(key.getBytes(), "AES");
String[] s = valueToDecrypt.split(";");
if (s.length < 2) //Not an encrypted string, just return the original value.
return valueToDecrypt;
byte[] iv = Base64.decodeBase64(s[0].getBytes());
byte[] encryptedData = Base64.decodeBase64(s[1].getBytes());
c.init(Cipher.DECRYPT_MODE, k, new IvParameterSpec(iv));
return new String(c.doFinal(encryptedData));
}
public static String generateHmacSha256Hash(String key, String valueToEncrypt)
{
try {
SecretKeySpec secretKey = new SecretKeySpec(key.getBytes("UTF-8"), "HmacSHA256");
Mac mac = Mac.getInstance("HmacSHA256");
mac.init(secretKey);
mac.update(valueToEncrypt.getBytes("UTF-8"));
byte[] hmacData = mac.doFinal();
return new String(Base64.encodeBase64(hmacData));
}
catch (Exception e) {
throw new RuntimeException("CommunityCommons::EncodeHmacSha256::Unable to encode: " + e.getMessage(), e);
}
}
public static String escapeHTML(String input) {
return input.replace("\"", """)
.replace("&", "&")
.replace("<", "<")
.replace(">", ">")
.replace("'", "'");// notice this one: for xml "'" would be "'" (http://blogs.msdn.com/b/kirillosenkov/archive/2010/03/19/apos-is-in-xml-in-html-use-39.aspx)
// OWASP also advises to escape "/" but give no convincing reason why: https://www.owasp.org/index.php/XSS_%28Cross_Site_Scripting%29_Prevention_Cheat_Sheet
}
public static String regexQuote(String unquotedLiteral) {
return Pattern.quote(unquotedLiteral);
}
}