diff --git a/grails-plugin-url-mappings/src/main/groovy/org/codehaus/groovy/grails/web/mapping/RegexUrlMapping.java b/grails-plugin-url-mappings/src/main/groovy/org/codehaus/groovy/grails/web/mapping/RegexUrlMapping.java index 73b04d38b53..bceef6cfdae 100644 --- a/grails-plugin-url-mappings/src/main/groovy/org/codehaus/groovy/grails/web/mapping/RegexUrlMapping.java +++ b/grails-plugin-url-mappings/src/main/groovy/org/codehaus/groovy/grails/web/mapping/RegexUrlMapping.java @@ -220,7 +220,6 @@ protected Pattern convertToRegex(String url) { String urlRoot = lastSlash > -1 ? pattern.substring(0, lastSlash) : pattern; String urlEnd = lastSlash > -1 ? pattern.substring(lastSlash, pattern.length()) : ""; - // Now replace "*" with "[^/]" and "**" with ".*". pattern = "^" + urlRoot .replace("(\\.(*))", "\\.?([^/]+)?") @@ -230,9 +229,12 @@ protected Pattern convertToRegex(String url) { pattern += urlEnd .replace("(\\.(*))", "\\.?([^/]+)?") - .replaceAll("([^\\*])\\*([^\\*])", "$1[^/\\.]+$2") - .replaceAll("([^\\*])\\*$", "$1[^/\\.]+") - .replaceAll("\\*\\*", ".*"); + .replaceAll("([^\\*])\\*([^\\*])", "$1[^/]+$2") + .replaceAll("([^\\*])\\*$", "$1[^/]+") + .replaceAll("\\*\\*", ".*") + .replaceAll("\\(\\[\\^\\/\\]\\+\\)\\\\\\.", "([^/.]+)\\\\.") + .replaceAll("\\(\\[\\^\\/\\]\\+\\)\\?\\\\\\.", "([^/.]+)\\?\\\\.") + ; pattern += "/??$"; regex = Pattern.compile(pattern); diff --git a/grails-plugin-url-mappings/src/test/groovy/org/codehaus/groovy/grails/web/mapping/UrlMappingsWithOptionalExtensionSpec.groovy b/grails-plugin-url-mappings/src/test/groovy/org/codehaus/groovy/grails/web/mapping/UrlMappingsWithOptionalExtensionSpec.groovy index db6ec4dbf66..83a95a98bac 100644 --- a/grails-plugin-url-mappings/src/test/groovy/org/codehaus/groovy/grails/web/mapping/UrlMappingsWithOptionalExtensionSpec.groovy +++ b/grails-plugin-url-mappings/src/test/groovy/org/codehaus/groovy/grails/web/mapping/UrlMappingsWithOptionalExtensionSpec.groovy @@ -34,7 +34,7 @@ class UrlMappingRsWithOptionalExtensionSpec extends AbstractUrlMappingsSpec { } - void "Test that dynamic URL mappings can be specified with an optional extension"() { + void "Test that dynamic URL mappings can be specified with an optional parameter and an optional extension"() { given:"A URL mapping with an optional extension" def urlMappingsHolder = getUrlMappingsHolder { "/$controller/$action?(.$format)?"() @@ -46,8 +46,19 @@ class UrlMappingRsWithOptionalExtensionSpec extends AbstractUrlMappingsSpec { urlMappingsHolder.match('/book') urlMappingsHolder.match('/book/list') urlMappingsHolder.match('/book/list').parameters.format == null + } - + void "Test that dynamic URL mappings can be specified with a required parameter and an optional extension"() { + given:"A URL mapping with an optional extension" + def urlMappingsHolder = getUrlMappingsHolder { + "/$controller/$action(.$format)?"() + } + + expect:"URLs with and without the format specified match" + urlMappingsHolder.match('/book/list.xml') + urlMappingsHolder.match('/book/list.xml').parameters.format == 'xml' + urlMappingsHolder.match('/book/list') + urlMappingsHolder.match('/book/list').parameters.format == null } void "Test deep dynamic URL mappings can be specified with an optional extension"() { diff --git a/grails-test-suite-web/src/test/groovy/org/codehaus/groovy/grails/web/mapping/RegexUrlMappingTests.groovy b/grails-test-suite-web/src/test/groovy/org/codehaus/groovy/grails/web/mapping/RegexUrlMappingTests.groovy index a72f77301da..381d5bbc852 100644 --- a/grails-test-suite-web/src/test/groovy/org/codehaus/groovy/grails/web/mapping/RegexUrlMappingTests.groovy +++ b/grails-test-suite-web/src/test/groovy/org/codehaus/groovy/grails/web/mapping/RegexUrlMappingTests.groovy @@ -61,6 +61,10 @@ mappings { controller = "survey" action = "viewByName" } + "/reports/$foo" { + controller = 'reporting' + action = 'view' + } } ''' void testMaptoURI() { @@ -352,6 +356,22 @@ mappings { assertEquals 'survey', info.controllerName assertEquals 'viewByName', info.actionName } + + void testParameterContainingADot() { + def holder = new DefaultUrlMappingsHolder(evaluator.evaluateMappings(new ByteArrayResource(mappingScript.bytes))) + + def info = holder.match("/reports/my") + assertNotNull info + assertEquals 'reporting', info.controllerName + assertEquals 'view', info.actionName + assertEquals 'my', info.params.foo + + info = holder.match("/reports/my.id") + assertNotNull info + assertEquals 'reporting', info.controllerName + assertEquals 'view', info.actionName + assertEquals 'my.id', info.params.foo + } void testInit() { def parser = new DefaultUrlMappingParser()