If I look into the source code of UriComponentsBuilder, it looks like it is collecting all paths that will eventually be used to create final path.
#Override
public UriComponentsBuilder path(String path) {
this.pathBuilder.addPath(path);
resetSchemeSpecificPart();
return this;
}
However, based on this assumption I wrote this test which fails. It seems like that UriComponentsBuilder combines them as one simple path instead of separating it by /.
#Test
public void testFunctioning(){
String url = UriComponentsBuilder.newInstance()
.path("one")
.path("two")
.path("three")
.toUriString();
assertEquals("one/two/three", url);
}
Expected :one/two/three
Actual :onetwothree
Is my understanding itself regarding behaviour of UriComponentsBuilder is wrong ?
I think you should use pathSegment(String... segment), so something like:
UriComponentsBuilder.newInstance()
.pathSegment("one", "two", "three")
.toUriString();
Which is outputting /one/two/three.
Related
I have a GenericFilterBean that redirects calls to my controller based on the hostname. A call to demo.example.com/foobar -> live.example.com/demo/foobar
Once I've determined that I want to forward:
path = "/demo" + path;
request.getRequestDispatcher(path).forward(servletRequest, servletResponse);
I'm not overly worried about path, as I already have defenses against path traversal attacks, but I got the bright idea that I could check whether "/demo" + path would lead to a valid method in my controller:
#Autowired
#Qualifier("requestMappingHandlerMapping") // An hour of my life I'll never get back
private RequestMappingHandlerMapping requestHandlerMapping;
...
List<HandlerMethod> methodlist = this.requestHandlerMapping
.getHandlerMethodsForMappingName(path);
Nothing I feed getHandlerMethodsForMappingName finds a method. If I look at the output of
Set<RequestMappingInfo> myKeys = this.requestHandlerMapping.getHandlerMethods().keySet();
for (RequestMappingInfo key : myKeys) {
System.out.println(key.toString());
}
I see many lines, including {GET [/demo/start]}, but nothing gets found for:
{GET [/demo/start]}
GET [/demo/start]
[/demo/start]
/demo/start
demo/start
I've read the fine manual, but I can't find any guidance on this. I recognize that there may be other ways to achieve my goal, but I'd really like to know what the proper format for getHandlerMethodsForMappingName is.
Update:
I can achieve what I need with:
private HashSet<String> demoPaths = new HashSet<String>();
#Override
public void initFilterBean() {
this.requestHandlerMapping.getHandlerMethods().keySet().forEach(info -> {
info.getDirectPaths().forEach(path -> {
if (path.startsWith("/demo")) {
demoPaths.add(path);
}
});
});
}
...
String newpath = "/demo" + path;
if (demoPaths.contains(newpath)) {
request.getRequestDispatcher(newpath).forward(request, response);
} else {
filterChain.doFilter(request, response);
}
This works for my situation, and probably could be extended to handle dynamic paths, but still doesn't answer my original question.
I got this piece of code, I am learning from tutorial. I want to return an element by url which looks like clients/1 instead of clients?id=1. How can I achieve this? Also, can the code below be made easier way?
#GetMapping
public Client getClient(#RequestParam int id) {
Optional<Client> first = clientList.stream().filter(element -> element.getId() == id).findFirst();
return first.get();
}
You may want to use #PathVariable as follows:
#Controller
#RequestMapping("/clients")
public class MyController {
#GetMapping("/{id}")
public Client getClient(#PathVariable int id) {
return clientList.stream().filter(element -> element.getId() == id).findFirst().orElseThrow();
}
Please note, the Optional can be unpacked with orElseThrow method. This will throw a NoSuchElementException in case there is no element found for the id.
Other solution would be to use orElse(new Client(...)) to return a default value if nothing is found.
get() is not really recommended to be used. From the JavaDoc of the get() method:
API Note:
The preferred alternative to this method is orElseThrow().
Even though get() may also throw a NoSuchElementException, similar to orElseThrow, usually the consensus is that get should not be used without isPresent, or should not be used at all. There several other methods to unpack the Optional without forcing you write an if.
The whole idea of the Optional is to overcome this by forcing you to think about the case when there is no value inside.
How to force URIBuilder.path(...) to encode parameters like "%AD"?
The methods path, replacePath and segment of URIBuilder do not always encode parameters with percentage, correctly.
When a parameter contains the character "%" followed by two characters that together form an URL-encoded character, the "%" is not encoded as "%25".
For example
URI uri = UriBuilder.fromUri("https://dummy.com").queryParam("param", "%AD");
String test = uri.build().toString();
"test" is "https://dummy.com?param=%AD"
But it should be "https://dummy.com?param=%25AD" (with the character "%" encoded as "%25")
The method UriBuilderImpl.queryParam(...) behaves like this when the two characters following the "%" are hexadecimal. I.e, the method "com.sun.jersey.api.uri.UriComponent.isHexCharacter(char)" returns true for the characters following the "%".
I think the behavior of UriBuilderImpl is correct because I guess it tries to not encode parameters that already are encoded. But in my scenario, I will never try to create URLs with parameters that already encoded.
What should I do?
My Web application uses Jersey and in many places I build URIs using the class UriBuilder or invoke the method getBaseUriBuilder of UriInfo objects.
I can replace "%" with "%25", every time I invoke the methods queryParam, replaceQueryParam or segment. But I am looking for a less cumbersome solution.
How can I make Jersey to return my own implementation of UriBuilder?
I thought of creating a class that extends UriBuilderImpl that overrides these methods and that perform this replacing before invoking super.queryParam(...) or whatever.
Is there any way of making Jersey to return my own UriBuilder instead of UriBuilderImpl, when invoking UriBuilder.fromURL(...), UriInfo.getBaseUriBuilder(...), etc?
Looking at the method RuntimeDelegate, I thought of extending RuntimeDelegateImpl. My implementation would override the method createUriBuilder(...), which would return my own UriBuilder, instead of UriBuilderImpl.
Then, I would add the file META-INF/services/javax.ws.rs.ext.RuntimeDelegate and in it, a the full class name of my RuntimeDelegateImpl.
The problem is that the jersey-bundle.jar already contains a META-INF/services/javax.ws.rs.ext.RuntimeDelegate that points to com.sun.jersey.server.impl.provider.RuntimeDelegateImpl, so the container loads that file instead of my javax.ws.rs.ext.RuntimeDelegate. Therefore, it does not load my RuntimeDelegateimplementation.
Is it possible to provide my own implementation of RuntimeDelegate?
Should I take a different approach?
UriBuilder
This is possible with help of UriComponent from Jersey or URLEncoder directly from Java:
UriBuilder.fromUri("https://dummy.com")
.queryParam("param",
UriComponent.encode("%AD",
UriComponent.Type.QUERY_PARAM_SPACE_ENCODED))
.build();
Which result in:
https://dummy.com/?param=%25AD
Or:
UriBuilder.fromUri("https://dummy.com")
.queryParam("param", URLEncoder.encode("%AD", "UTF-8"))
.build()
Will result in:
https://dummy.com/?param=%25AD
For a more complex examples (i.e. encoding JSON in query param) this approach is also possible. Let's assume you have a JSON like {"Entity":{"foo":"foo","bar":"bar"}}. When encoded using UriComponent the result for query param would look like:
https://dummy.com/?param=%7B%22Entity%22:%7B%22foo%22:%22foo%22,%22bar%22:%22bar%22%7D%7D
JSON like this could be even injected via #QueryParam into resource field / method param (see JSON in Query Params or How to Inject Custom Java Types via JAX-RS Parameter Annotations).
Which Jersey version do you use? In the tags you mention Jersey 2 but in the RuntimeDelegate section you're using Jersey 1 stuff.
See if the following examples help. The thread linked below has an extensive discussion on the available functions and their differing outputs.
The following:
UriBuilder.fromUri("http://localhost:8080").queryParam("name", "{value}").build("%20");
UriBuilder.fromUri("http://localhost:8080").queryParam("name", "{value}").buildFromEncoded("%20");
UriBuilder.fromUri("http://localhost:8080").replaceQuery("name={value}).build("%20");
UriBuilder.fromUri("http://localhost:8080").replaceQuery("name={value}).buildFromEncoded("%20");
Will output:
http://localhost:8080?name=%2520
http://localhost:8080?name=%20
http://localhost:8080?name=%2520
http://localhost:8080?name=%20
via http://comments.gmane.org/gmane.comp.java.jsr311.user/71
Also, based on the Class UriBuilder documentation, the following example shows how to obtain what you're after.
URI templates are allowed in most components of a URI but their value
is restricted to a particular component. E.g.
UriBuilder.fromPath("{arg1}").build("foo#bar");
would result in encoding of the '#' such that the resulting URI is
"foo%23bar". To create a URI "foo#bar" use
UriBuilder.fromPath("{arg1}").fragment("{arg2}").build("foo", "bar")
instead. URI template names and delimiters are never encoded but their
values are encoded when a URI is built. Template parameter regular
expressions are ignored when building a URI, i.e. no validation is
performed.
It is possible to overwrite the default behavior in jersey manually at start up e.g. with a static helper that calls RuntimeDelegate.setInstance(yourRuntimeDelegateImpl).
So if you want to have an UriBuilder that encodes percents even if they look like they are part of an already encoded sequence, this would look like:
[...]
import javax.ws.rs.core.UriBuilder;
import javax.ws.rs.ext.RuntimeDelegate;
import com.sun.jersey.api.uri.UriBuilderImpl;
import com.sun.ws.rs.ext.RuntimeDelegateImpl;
// or for jersey2:
// import org.glassfish.jersey.uri.internal.JerseyUriBuilder;
// import org.glassfish.jersey.internal.RuntimeDelegateImpl;
public class SomeBaseClass {
[...]
// this is the lengthier custom implementation of UriBuilder
// replace this with your own according to your needs
public static class AlwaysPercentEncodingUriBuilder extends UriBuilderImpl {
#Override
public UriBuilder queryParam(String name, Object... values) {
Object[] encValues = new Object[values.length];
for (int i=0; i<values.length; i++) {
String value = values[i].toString(); // TODO: better null check here, like in base class
encValues[i] = percentEncode(value);
}
return super.queryParam(name, encValues);
}
private String percentEncode(String value) {
StringBuilder sb = null;
for (int i=0; i < value.length(); i++) {
char c = value.charAt(i);
// if this condition is is true, the base class will not encode the percent
if (c == '%'
&& i + 2 < value.length()
&& isHexCharacter(value.charAt(i + 1))
&& isHexCharacter(value.charAt(i + 2))) {
if (sb == null) {
sb = new StringBuilder(value.substring(0, i));
}
sb.append("%25");
} else {
if (sb != null) sb.append(c);
}
}
return (sb != null) ? sb.toString() : value;
}
// in jersey2 one can call public UriComponent.isHexCharacter
// but in jersey1 we need to provide this on our own
private static boolean isHexCharacter(char c) {
return ('0' <= c && c <= '9')
|| ('A' <=c && c <= 'F')
|| ('a' <=c && c <= 'f');
}
}
// here starts the code to hook up the implementation
public static class AlwaysPercentEncodingRuntimeDelegateImpl extends RuntimeDelegateImpl {
#Override
public UriBuilder createUriBuilder() {
return new AlwaysPercentEncodingUriBuilder();
}
}
static {
RuntimeDelegate myDelegate = new AlwaysPercentEncodingRuntimeDelegateImpl();
RuntimeDelegate.setInstance(myDelegate);
}
}
Caveat: Of course that way it is not very configurable, and if you do that in some library code that might be reused by others, this might cause some irritation.
For example I had the same problem as the OP when writing a rest client in a Confluence plugin, and ended up with the "manual encode every parameter" solution instead, as the plugins are loaded via OSGi and thus are simply not able to touch the RuntimeDelegateImpl (getting java.lang.ClassNotFoundException: com.sun.ws.rs.ext.RuntimeDelegateImpl at runtime instead).
(And just for the record, in jersey2 this looks very similar; especially the code to hook the custom RuntimeDelegateImpl is the same.)
Im creating an annotator called "NewAnnotator" and try to make it works in a pipeline with others annotators in ClearTK like:
SentenceAnnotator, PosTaggerAnnotator, etc. So I want to be able to run pipeline:
aggregate.add(SentenceAnnotator.getDescription());
aggregate.add(PosTaggerAnnotator.getDescription());
aggregate.add(NewAnnotator.getDescription());
// run the classification pipeline on the new texts
SimplePipeline.runPipeline(reader, aggregate.createAggregateDescription());
I wrote the code with no error, but when running it returns a lot of errors, which I think from this part in my NewAnnotator code:
public static AnalysisEngineDescription getDescription() throws ResourceInitializationException {
return AnalysisEngineFactory.createPrimitiveDescription(
NewAnnotator.class,
PARAM_POSTAG_MODEL_FILE,
ParamUtil.getParameterValue(PARAM_POSTAG_MODEL_FILE, "/somepath"));
}
public static final String PARAM_POSTAG_MODEL_FILE = ConfigurationParameterFactory.createConfigurationParameterName(
PosTaggerAnnotator.class,
"postagModelFile");
I almost copy this part from PosTaggerAnnotator, but it has no use in my NewAnnotator, I just add in so that I can use:
aggregate.add(NewAnnotator.getDescription());
because I don't know any other way to add to aggregate without .getDescription(); and I also don't know how to declare a correct getDescription() in my annotator, even it can works fine without it.
So please give me some advise here if you have experienced it! Thank you!
getDescription() is a convenience method to create a default description for your annotator. It uses AnalysisEngineFactory.createPrimitiveDescription(), to which you need to provide the right arguments, like this:
public static AnalysisEngineDescription getDescription() throws ResourceInitializationException {
return AnalysisEngineFactory.createPrimitiveDescription(
NewAnnotator.class,
first_parameter_name, first_parameter_value,
second_parameter_name, second_parameter_value,
... );
}
There are more examples in the uimaFIT codebase.
I'm managing an MVC3 app where I need to support the ability of 3rd parties to create link to assets within my domain. Because some of the links are sliced and diced by mail merges and other text editing problems, URLs with typos have been introduced, e.g.:
/Content/ima!+ges/email/spacer.gif
or
/Content/image++s/email+/spacer.gif
I'd like to strip these extraneous characters by RegEx before attempting to serve them. I _think this is something a Route method could accomplish and I'd welcome a pointer or two to articles that demonstrate this approach.
ADDENDUM (cuz I need the formatting):
Implementing #Nathan's routing I'm unable to send the filename to the controller handler - it's always seeing a null value passed in. I've tried both 'filepath' and 'path' with the same 'null' result.
routes.MapRoute(
"MangledFilename",
"{*filepath}",
new { controller = "MangledFilename", action = "ServeFile" }
);
I think this is a matter of configuring wildcard handling on IISExpress and am looking for that solution separately. The more serious immediate problem is how your suggestion returns the HttpNotFound - i'm getting a hard IIS exception (execution halts with a YellowScreenDeath) instead of the silent 404 result.
public ActionResult ServeFile(string filePath)
{
if (filePath != null) // workaround the null
{
...
}
return HttpNotFound();
}
thx
I think something along this approach should work:
First add a route like this to the end of your route registering declarations:
routes.MapRoute(
"MangledFilename",
"{*filepath}",
new { controller = "MangledFilename", action = "ServeFile" });
If you haven't seen them before, a route parameter with an * after the opening { is a wildcard parameter, in this case it will match the entire path. You could also write it like content/{*filepath} if you wanted to restrict this behavior to your content directory.
And then a controller something like this should do the trick:
public class MangledFilenameController : Controller
{
public ActionResult ServeFile(string filePath)
{
filePath = CleanFilePath(filePath);
var absolutePath = Server.MapPath(filePath);
if (System.IO.File.Exists(absolutePath))
{
var extension = System.IO.Path.GetExtension(absolutePath);
var contentType = GetContentTypeForExtenion(extension);
return File(absolutePath, contentType);
}
return HttpNotFound();
}
private string CleanFilePath(string filepath)
{
//clean the path up
return filepath;
}
private string GetContentTypeForExtenion(string extension)
{
//you will want code here to map extensions to content types
return "image/gif";
}
}
In regards to mapping an extension to a MIME / content type for the GetContentTypeForExtension method, you could choose to hard code types you are expecting to serve, or use one of the solutions detailed in this post:
File extensions and MIME Types in .NET
EDIT:
After thinking about it, I realized there's another way you can handle the ServeFile action. Redirecting to the existing file could be simpler. I'm leaving the original method I wrote above and adding the alternative one here:
public ActionResult ServeFile(string filePath)
{
filePath = CleanFilePath(filePath);
var absolutePath = Server.MapPath(filePath);
if (System.IO.File.Exists(absolutePath))
{
return RedirectPermanent(filePath);
}
return HttpNotFound();
}
I believe #Nathan Anderson provided a good answer but it seems incomplete.
If you want to correct the typos and the types are as simple as those you mentioned then you can use Nathan code but before trying to find the file, you remove any plus or exclamation point characters in the path and you can do it like this:
String sourcestring = "source string to match with pattern";
String matchpattern = #"[+!]";
String replacementpattern = #"";
Console.WriteLine(Regex.Replace(sourcestring,matchpattern,replacementpattern));
Generated this code from the My Regex Tester tool.
This is the code you need. This code also removes any + character from the filename. If you don't want that behavior, you may select a substring without the filename and only replace + and ! characters before the filename.