Index: web/src/main/java/org/openmrs/web/WebUtil.java =================================================================== --- web/src/main/java/org/openmrs/web/WebUtil.java (revision 18766) +++ web/src/main/java/org/openmrs/web/WebUtil.java (working copy) @@ -13,6 +13,12 @@ */ package org.openmrs.web; +import java.util.Arrays; +import java.util.Iterator; +import java.util.List; +import java.util.Locale; +import java.util.MissingResourceException; + import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -88,4 +94,117 @@ return filename; } + /** + * This method checks if input locale string contains control characters and + * tries to clean up actually contained ones. Also it parses locale object from + * string representation and validates it object. + * + * @param localeString + * input string with locale parameter + * @return locale object for input string if CTLs were cleaned up or weren't + * exist or null if could not to clean up CTLs from input string + */ + public static Locale normalizeLocale(String localeString) { + if (localeString == null) + return null; + int len = localeString.trim().length(); + for (int i = 0; i < len; i++) { + char c = localeString.charAt(i); + // allow only ASCII letters and "_" character + if ((c <= 0x20 || c >= 0x7f) || ((c >= 0x20 || c <= 0x7f) && (!Character.isLetter(c) && c != 0x5f))) { + if (c == 0x09) + continue; // allow horizontal tabs + localeString = removeCharAt(localeString, i); + len--; + i--; + } + } + Locale locale = parseLocale(localeString); + if (isValid(locale)) + return locale; + else + return null; + } + + /** + * Very convinient method that parces string object, that contains locale parameters which are + * separeted by comma, and tries to clean up CTLs and other unsupported chars within input string + * + * @param localesString + * input string with locale parameters separeted by comma (e.g. "en, fr_RW, gh") + * @return cleaned up string (or same string) if success or null otherwise + */ + public static String sanitizeLocales(String localesString) { + if (localesString == null) + return null; + String result = null; + StringBuffer buf = new StringBuffer(); + List locales = Arrays.asList(localesString.split(",")); + for (Iterator iterator = locales.iterator(); iterator.hasNext();) { + String locale = (String) iterator.next(); + Locale loc = normalizeLocale(locale); + if (loc != null) { + buf.append(loc.toString()); + buf.append(", "); + } + if (!iterator.hasNext() && buf.length() > 3) + result = buf.toString().substring(0, buf.length() - 2); + + } + return result; + } + + /** + * Removes character in string by specified position + * + * @param s + * input string + * @param pos + * position from character will be removed + * @return new string object + */ + private static String removeCharAt(String s, int pos) { + StringBuffer buf = new StringBuffer(s.length() - 1); + buf.append(s.substring(0, pos)).append(s.substring(pos + 1)); + return buf.toString(); + } + + /** + * Parse locale parameter from input string + * + * @param locale + * string that contains locale + * @return new locale object from input string or null in case of parsing + * error + */ + private static Locale parseLocale(String locale) { + String[] parts = locale.split("_"); + switch (parts.length) { + case 3: + return new Locale(parts[0], parts[1], parts[2]); + case 2: + return new Locale(parts[0], parts[1]); + case 1: + return new Locale(parts[0]); + default: + throw new IllegalArgumentException("Invalid locale: " + locale); + } + } + + /** + * Checks if specified locale object is valid + * + * @param locale + * object for validation + * @return true if locale is available + */ + private static boolean isValid(Locale locale) { + try { + return locale.getISO3Language() != null && locale.getISO3Country() != null; + } + catch (MissingResourceException e) { + return false; + } + } + } Index: web/src/main/java/org/openmrs/web/controller/OptionsFormController.java =================================================================== --- web/src/main/java/org/openmrs/web/controller/OptionsFormController.java (revision 18766) +++ web/src/main/java/org/openmrs/web/controller/OptionsFormController.java (working copy) @@ -15,6 +15,7 @@ import java.util.Date; import java.util.HashMap; +import java.util.Locale; import java.util.Map; import javax.servlet.ServletException; @@ -36,6 +37,7 @@ import org.openmrs.util.PrivilegeConstants; import org.openmrs.web.OptionsForm; import org.openmrs.web.WebConstants; +import org.openmrs.web.WebUtil; import org.openmrs.web.user.UserProperties; import org.springframework.validation.BindException; import org.springframework.web.servlet.ModelAndView; @@ -130,8 +132,13 @@ Map properties = user.getUserProperties(); properties.put(OpenmrsConstants.USER_PROPERTY_DEFAULT_LOCATION, opts.getDefaultLocation()); - properties.put(OpenmrsConstants.USER_PROPERTY_DEFAULT_LOCALE, opts.getDefaultLocale()); - properties.put(OpenmrsConstants.USER_PROPERTY_PROFICIENT_LOCALES, opts.getProficientLocales()); + + Locale locale = WebUtil.normalizeLocale(opts.getDefaultLocale()); + if (locale != null) + properties.put(OpenmrsConstants.USER_PROPERTY_DEFAULT_LOCALE, locale.toString()); + + properties.put(OpenmrsConstants.USER_PROPERTY_PROFICIENT_LOCALES, WebUtil.sanitizeLocales(opts + .getProficientLocales())); properties.put(OpenmrsConstants.USER_PROPERTY_SHOW_RETIRED, opts.getShowRetiredMessage().toString()); properties.put(OpenmrsConstants.USER_PROPERTY_SHOW_VERBOSE, opts.getVerbose().toString()); properties.put(OpenmrsConstants.USER_PROPERTY_NOTIFICATION, opts.getNotification() == null ? "" : opts Index: web/src/main/java/org/openmrs/web/servlet/LoginServlet.java =================================================================== --- web/src/main/java/org/openmrs/web/servlet/LoginServlet.java (revision 18766) +++ web/src/main/java/org/openmrs/web/servlet/LoginServlet.java (working copy) @@ -36,6 +36,7 @@ import org.openmrs.util.OpenmrsConstants; import org.openmrs.web.OpenmrsCookieLocaleResolver; import org.openmrs.web.WebConstants; +import org.openmrs.web.WebUtil; import org.openmrs.web.user.CurrentUsers; import org.openmrs.web.user.UserProperties; @@ -134,18 +135,12 @@ if (user.getUserProperties() != null) { if (user.getUserProperties().containsKey(OpenmrsConstants.USER_PROPERTY_DEFAULT_LOCALE)) { String localeString = user.getUserProperty(OpenmrsConstants.USER_PROPERTY_DEFAULT_LOCALE); - Locale locale = null; - if (localeString.length() == 5) { - //user's locale is language_COUNTRY (i.e. en_US) - String lang = localeString.substring(0, 2); - String country = localeString.substring(3, 5); - locale = new Locale(lang, country); - } else { - // user's locale is only the language (language plus greater than 2 char country code - locale = new Locale(localeString); + Locale locale = WebUtil.normalizeLocale(localeString); + // if locale object is valid we should store it + if (locale != null) { + OpenmrsCookieLocaleResolver oclr = new OpenmrsCookieLocaleResolver(); + oclr.setLocale(request, response, locale); } - OpenmrsCookieLocaleResolver oclr = new OpenmrsCookieLocaleResolver(); - oclr.setLocale(request, response, locale); } }