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.
Related
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.
I looked at ByteBuddy documentation and have also looked at few of the Java Agent implementations that are using ByteBuddy. But I am still not very clear on what is the right way to use the ByteBuddy agent.
This is the code I used to bootstrap my agent:
public class ByteBuddyAgent {
public static void premain(String arguments, Instrumentation instrumentation) {
System.out.println("Bootstrapping ByteBuddy Agent");
new AgentBuilder.Default()
.with(new AgentBuilder.InitializationStrategy.SelfInjection.Eager())
.type(ElementMatchers.hasSuperClass(ElementMatchers.nameContains("Level1")))
.transform(new ByteBuddyTransformer()
.installOn(instrumentation);
}
private static class ByteBuddyTransformer implements AgentBuilder.Transformer{
#Override
public DynamicType.Builder<?> transform(DynamicType.Builder<?> builder, TypeDescription typeDescription,
ClassLoader classLoader, JavaModule module) {
return builder.method(ElementMatchers.named("printLevel"))
.intercept(Advice.to(MethodTracker.class));
}
}
}
This is my Advice class where I have tried different annotations supported by Advice and it worked fine.
public class MethodTracker {
#Advice.OnMethodEnter
public static Object onMethodBegin(#Advice.This Object invokedObject, #Advice.AllArguments Object[] arguments,
#Advice.FieldValue("name") Object fieldValue, #Advice.Origin Object origin,
#Advice.Local("localVariable") Object localVariable) {
System.out.println("=======on Method Begin Running with ByteBuddy=======, " + invokedObject);
System.out.println("======Printing arguments=======");
for(Object obj: arguments){
System.out.println("Argument:: " + obj);
}
localVariable = "Gunika";
System.out.println("FieldValue:: " + fieldValue);
System.out.println("Origin:: " + origin);
return "ReturningStateFromOnMethodBegin";
}
#Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class)
public static void onMethodEnd(#Advice.This Object invokedObject, #Advice.Return Object returnValue,
#Advice.FieldValue("name") Object fieldValue, #Advice.Enter Object state,
#Advice.Local("localVariable") Object localVariable){
System.out.println("=======on Method End Running with ByteBuddy======= " + invokedObject);
System.out.println("Return value is " + returnValue);
System.out.println("FieldValue:: " + fieldValue);
System.out.println("FieldValue:: " + fieldValue);
System.out.println("State:: " + state);
System.out.println("LocalVariable:: " + localVariable);
}
}
The questions that I have are as follows:
Right now in my sample app, I have just tried matching with 1 rule.
But if there are ānā rules that I want to apply what is the right away to achieve that.
There are initial set of rules that are applied/provided to the agent. Now let's say at some point of time I want to add another rule. What is the correct way to achieve that?
Does the AgentBuilder instance that we create should be created only once in the Java Agent?
Any other information that should be taken care while using ByteBuddy agent would be helpful.
Advice.to(MethodTracker.class) is pretty expensive. If you instrument many classes and want to spend the memory, I'd recommend you to reuse the instance, it is immutable.
Other than that, #Advice.AllArguments is more expensive than using specialized advice classes that read particular arguments, if you can afford it. This is however rather a JVM question than a Byte Buddy-specific one. You can use -Dnet.bytebuddy.dump=/some/folder to see what byte code is generated. Expensiveness often lies burried what constructs you go for.
I have the following Akka actor:
public class MyActor extends AbstractActor {
protected Logger log = LoggerFactory.getLogger(this.getClass());
#Override
public Receive createReceive() {
return receiveBuilder()
.matchAny(message -> {
String myFullName = self().path().toString();
String myName = self().path().name();
ActorRef reincarnatedMe = context().actorFor(self().path().name());
String reincarnatedFullName = reincarnatedMe.path().toString();
String reincarnatedName = reincarnatedMe.path().name();
log.info("myFullName: {}", myFullName);
log.info("myName: {}", myName);
log.info("reincarnatedFullName: {}", reincarnatedFullName);
log.info("reincarnatedName: {}", reincarnatedName);
}).build();
}
}
At runtime it produces this output:
05:43:14.617 [MySystem-akka.actor.default-dispatcher-4] INFO myapp.actors.MyActor - myFullName: akka://MySystem/user/MyActor
05:43:14.623 [MySystem-akka.actor.default-dispatcher-4] INFO myapp.actors.MyActor - myName: MyActor
05:43:14.623 [MySystem-akka.actor.default-dispatcher-4] INFO myapp.actors.MyActor - reincarnatedFullName: akka://MySystem/user/MyActor/MyActor
05:43:14.623 [MySystem-akka.actor.default-dispatcher-4] INFO myapp.actors.MyActor - reincarnatedName: MyActor
My understanding was that context().actorFor(...) doesn't create a new actor, rather it finds an existing actor that matches the path/string you provide and returns a reference to it.
However, it appears that in my code above, self() becomes the parent of reincarnatedMe as evidenced by myFullName simply being "MySystem/user/MyActor" whereas reincarnatedFullName is "MySystem/user/MyActor/MyActor"...
Am I reading this right? If so, how can I invoke context().actorFor(...) (or any other method for that matter) such that myFullName becomes the same as reincarnatedFullName (so that self() and reincarnatedMe reference the same actor? And if I'm not reading this right, why is myFullName different than reincarnatedFullName?
Update:
public class AnotherActor extends AbstractActor { ... }
// Inside MyActor#createReceive:
ActorSelection anotherActorSel = context().actorSelection("AnotherActor");
anotherActorSel.tell(new SomeMessage(), self());
First, ActorContext.actorFor(String) is deprecated in favor of ActorContext.actorSelection(String). This method returns an ActorSelection, but you can still send a message to an ActorSelection (such as an Identify, which response with an ActorIdentity message automatically).
The documentation for the actorFor method says that, "Absolute URIs like akka://appname/user/actorA are looked up as described for look-ups by actorOf(ActorPath)." I can't find documentation on an actorOf(ActorPath) method, but the other actorOf methods state they create new actors, so I suspect this does the same. The behavior you've found is likely the reason for the deprecation -- or because it was deprecated and the methods used for something else.
As title said, I want to use xposed to log all methods called in an app from it start till I stop it. I only want to log Class name, Method name, don't want to hook all method.
I try this code, but get error getMethod not found.
findAndHookMethod("java.lang.Class", lpparam.classLoader, "getMethod", String.class, Object.class, new XC_MethodHook()
Thanks in advance!
There is no one line solution like what you seem to be searching.
Hooking all methods will let log what methods were called by app from it start till stop (sort of - see below), but if (for some reason) you don't want to hook all methods, the only solution I can think of is modifying the java VM itself (NOT something I would recommend.)
A solution that (sort of) works
What I did was first use apktool to decompile my apk and get the names of all the methods in all the classes.
Then I used xposed to hook into every single method of every class and print to the dlog the current function name.
Why it only sort of works
Xposed has an overhead whenever it hook a methods. For general usage of xposed apps, it isnt much. But when you start hooking each and every methods of an app, the overhead very quickly becomes ridiculously large - So much so that while the above methods works for small apps, for any large app it very quickly causes the app to hang and then crash.
An alternative that also sort-of works
FRIDA is a way to inject javascript to native apps. Here they show you how to log all function calls. While in the above link they log all function calls in a piece of python code, the same code also works for Android.
There is a way to log all Java methods.Modify XposedBridge.
Xposed hook java method through XposedBridge.java's method
"handleHookedMethod(Member method, int originalMethodId, Object additionalInfoObj, thisObject, Object[] args)"
Log.v(TAG, "className " + method.getClass().getName() + ",methodName " + method.getName());
As mentioned before Xposed is not the way to go in this situation due to its overhead.
The simplest solution is just to use dmtracedump as provided by Google. Most x86 Android images and emulator come with the debuggable flag on (ro.debuggable) so you can even use it for closed source apps.
Additionally other tools such as Emma are known to work with Android as well, but these might need modifications to the source code.
I found a solution.
See this code snippet below.
package com.kyunggi.logcalls;
import android.content.pm.*;
import android.util.*;
import dalvik.system.*;
import de.robv.android.xposed.*;
import de.robv.android.xposed.callbacks.XC_LoadPackage.*;
import java.io.*;
import java.lang.reflect.*;
import java.util.*;
import static de.robv.android.xposed.XposedHelpers.findAndHookMethod;
import android.app.*;
public class Main implements IXposedHookLoadPackage {
private String TAG = "LogCall";
public void handleLoadPackage(final LoadPackageParam lpparam) throws Throwable {
if (!lpparam.packageName.equals("com.android.bluetooth")) {
Log.i(TAG, "Not: " + lpparam.packageName);
return;
}
Log.i(TAG, "Yes " + lpparam.packageName);
//Modified https://d3adend.org/blog/?p=589
ApplicationInfo applicationInfo = AndroidAppHelper.currentApplicationInfo();
if (applicationInfo.processName.equals("com.android.bluetooth")) {
Set<String> classes = new HashSet<>();
DexFile dex;
try {
dex = new DexFile(applicationInfo.sourceDir);
Enumeration entries = dex.entries();
while (entries.hasMoreElements()) {
String entry = (String) entries.nextElement();
classes.add(entry);
}
dex.close();
} catch (IOException e) {
Log.e("HookDetection", e.toString());
}
for (String className : classes) {
boolean obex = false;
if (className.startsWith("com.android.bluetooth") || (obex = className.startsWith("javax.obex"))) {
try {
final Class clazz = lpparam.classLoader.loadClass(className);
for (final Method method : clazz.getDeclaredMethods()) {
if (obex) {
if (!Modifier.isPublic(method.getModifiers())) {
continue; //on javax.obex package, hook only public APIs
}
}
XposedBridge.hookMethod(method, new XC_MethodHook() {
final String methodNam = method.getName();
final String classNam = clazz.getName();
final StringBuilder sb = new StringBuilder("[");
final String logstr = "className " + classNam + ",methodName " + methodNam;
#Override
protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
//Method method=(Method)param.args[0];
sb.setLength(0);
sb.append(logstr);
//Log.v(TAG,logstr);
for (Object o : param.args) {
String typnam = "";
String value = "null";
if (o != null) {
typnam = o.getClass().getName();
value = o.toString();
}
sb.append(typnam).append(" ").append(value).append(", ");
}
sb.append("]");
Log.v(TAG, sb.toString());
}
});
}
} catch (ClassNotFoundException e) {
Log.wtf("HookDetection", e.toString());
}
}
}
}
// ClassLoader rootcl=lpparam.classLoader.getSystemClassLoader();
//findAndHookMethod("de.robv.android.xposed.XposedBridge", rootcl, "handleHookedMethod", Member.class, int.class, Object.class, Object.class, Object[].class, );
}
}
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.