33import com .jbytescanner .model .ApiRoute ;
44import soot .*;
55import soot .tagkit .AnnotationTag ;
6+ import soot .tagkit .VisibilityAnnotationTag ;
7+ import soot .tagkit .VisibilityParameterAnnotationTag ;
68import org .slf4j .Logger ;
79import org .slf4j .LoggerFactory ;
810
911import java .util .ArrayList ;
1012import java .util .Arrays ;
1113import java .util .List ;
14+ import java .util .Map ;
15+ import java .util .HashMap ;
1216
1317public class RouteExtractor {
1418 private static final Logger logger = LoggerFactory .getLogger (RouteExtractor .class );
@@ -21,6 +25,11 @@ public class RouteExtractor {
2125 private static final String ANN_POST_MAPPING = "org.springframework.web.bind.annotation.PostMapping" ;
2226 private static final String ANN_PUT_MAPPING = "org.springframework.web.bind.annotation.PutMapping" ;
2327 private static final String ANN_DELETE_MAPPING = "org.springframework.web.bind.annotation.DeleteMapping" ;
28+
29+ // Param Annotations
30+ private static final String ANN_REQUEST_BODY = "org.springframework.web.bind.annotation.RequestBody" ;
31+ private static final String ANN_REQUEST_PARAM = "org.springframework.web.bind.annotation.RequestParam" ;
32+ private static final String ANN_PATH_VARIABLE = "org.springframework.web.bind.annotation.PathVariable" ;
2433
2534 // Servlet
2635 private static final String CLASS_HTTP_SERVLET = "javax.servlet.http.HttpServlet" ;
@@ -135,8 +144,15 @@ private List<ApiRoute> extractSpringRoutes(SootClass sc) {
135144 methodPaths .addAll (AnnotationHelper .getAnnotationValues (tag , "value" ));
136145 methodPaths .addAll (AnnotationHelper .getAnnotationValues (tag , "path" ));
137146 // Attempt to extract method from RequestMethod enum is complex in bytecode,
138- // often defaults to ALL if not easily statically resolved
139- httpMethod = "ALL" ;
147+ // often defaults to ALL if not easily statically resolved.
148+ // For PoC, we default to GET if unknown, or try to parse 'method' attribute.
149+ List <String > methods = AnnotationHelper .getAnnotationValues (tag , "method" );
150+ if (!methods .isEmpty ()) {
151+ httpMethod = methods .get (0 ).replace ("RequestMethod." , "" );
152+ } else {
153+ httpMethod = "ALL" ;
154+ }
155+
140156 } else if (AnnotationHelper .hasAnnotation (sm , ANN_GET_MAPPING )) {
141157 AnnotationTag tag = AnnotationHelper .getAnnotation (sm , ANN_GET_MAPPING );
142158 methodPaths .addAll (AnnotationHelper .getAnnotationValues (tag , "value" ));
@@ -147,16 +163,33 @@ private List<ApiRoute> extractSpringRoutes(SootClass sc) {
147163 methodPaths .addAll (AnnotationHelper .getAnnotationValues (tag , "value" ));
148164 methodPaths .addAll (AnnotationHelper .getAnnotationValues (tag , "path" ));
149165 httpMethod = "POST" ;
166+ } else if (AnnotationHelper .hasAnnotation (sm , ANN_PUT_MAPPING )) {
167+ AnnotationTag tag = AnnotationHelper .getAnnotation (sm , ANN_PUT_MAPPING );
168+ methodPaths .addAll (AnnotationHelper .getAnnotationValues (tag , "value" ));
169+ methodPaths .addAll (AnnotationHelper .getAnnotationValues (tag , "path" ));
170+ httpMethod = "PUT" ;
171+ } else if (AnnotationHelper .hasAnnotation (sm , ANN_DELETE_MAPPING )) {
172+ AnnotationTag tag = AnnotationHelper .getAnnotation (sm , ANN_DELETE_MAPPING );
173+ methodPaths .addAll (AnnotationHelper .getAnnotationValues (tag , "value" ));
174+ methodPaths .addAll (AnnotationHelper .getAnnotationValues (tag , "path" ));
175+ httpMethod = "DELETE" ;
150176 }
151- // Add PUT, DELETE similarly...
152177
153178 if (httpMethod != null ) {
154179 if (methodPaths .isEmpty ()) methodPaths .add ("" );
155180
181+ // Extract Parameters Info
182+ RouteMetadata metadata = extractRouteMetadata (sm );
183+
156184 for (String cp : classPaths ) {
157185 for (String mp : methodPaths ) {
158186 String fullPath = combinePaths (cp , mp );
159- routes .add (new ApiRoute (httpMethod , fullPath , sc .getName (), sm .getSubSignature ()));
187+
188+ ApiRoute route = new ApiRoute (
189+ httpMethod , fullPath , sc .getName (), sm .getSubSignature (),
190+ metadata .parameters , metadata .paramAnnotations , metadata .contentType
191+ );
192+ routes .add (route );
160193 }
161194 }
162195 }
@@ -168,6 +201,71 @@ private String combinePaths(String p1, String p2) {
168201 if (!p1 .startsWith ("/" )) p1 = "/" + p1 ;
169202 if (!p2 .startsWith ("/" ) && !p2 .isEmpty ()) p2 = "/" + p2 ;
170203 if (p1 .endsWith ("/" ) && p2 .startsWith ("/" )) return p1 + p2 .substring (1 );
204+ if (p1 .equals ("/" ) && p2 .startsWith ("/" )) return p2 ; // Avoid //api
171205 return p1 + p2 ;
172206 }
207+
208+ // --- Phase 8.3 Metadata Extraction ---
209+
210+ private static class RouteMetadata {
211+ List <String > parameters = new ArrayList <>();
212+ Map <String , String > paramAnnotations = new HashMap <>();
213+ String contentType = "application/x-www-form-urlencoded" ; // Default
214+ }
215+
216+ private RouteMetadata extractRouteMetadata (SootMethod sm ) {
217+ RouteMetadata meta = new RouteMetadata ();
218+
219+ // 1. Basic Parameter Info (Name:Type)
220+ // Try to get names from ActiveBody (LocalVariableTable) if available
221+ List <String > paramNames = new ArrayList <>();
222+ try {
223+ if (sm .hasActiveBody ()) {
224+ // Not reliable for interfaces/abstract, but Controllers usually have bodies
225+ // However, without debug info (-g:vars), names are arg0, arg1...
226+ // Spring uses -parameters flag usually.
227+ // We rely on simple counting for now: arg0...
228+ }
229+ } catch (Exception e ) {}
230+
231+ // 2. Annotation Analysis (VisibilityParameterAnnotationTag)
232+ // Format: Tag -> Annotations[] -> Annotation
233+ VisibilityParameterAnnotationTag tag = (VisibilityParameterAnnotationTag ) sm .getTag ("VisibilityParameterAnnotationTag" );
234+
235+ int paramCount = sm .getParameterCount ();
236+ for (int i = 0 ; i < paramCount ; i ++) {
237+ String name = "arg" + i ;
238+ Type type = sm .getParameterType (i );
239+ meta .parameters .add (name + ":" + type .toString ());
240+
241+ // Check for Multipart
242+ if (type .toString ().contains ("MultipartFile" )) {
243+ meta .contentType = "multipart/form-data" ;
244+ }
245+
246+ if (tag != null && tag .getVisibilityAnnotations () != null && i < tag .getVisibilityAnnotations ().size ()) {
247+ VisibilityAnnotationTag paramTags = tag .getVisibilityAnnotations ().get (i );
248+ if (paramTags != null && paramTags .getAnnotations () != null ) {
249+ for (AnnotationTag at : paramTags .getAnnotations ()) {
250+ String typeName = at .getType ().replace ("/" , "." ).replace (";" , "" );
251+ if (typeName .startsWith ("L" )) typeName = typeName .substring (1 ); // Remove L prefix
252+
253+ if (typeName .equals (ANN_REQUEST_BODY )) {
254+ meta .paramAnnotations .put (name , "RequestBody" );
255+ meta .contentType = "application/json" ;
256+ } else if (typeName .equals (ANN_REQUEST_PARAM )) {
257+ meta .paramAnnotations .put (name , "RequestParam" );
258+ // If we have @RequestParam("alias"), we should extract it.
259+ // But parsing annotation values here is complex (requires searching elems).
260+ // For MVP, we stick to argX or rely on parameter name preservation.
261+ } else if (typeName .equals (ANN_PATH_VARIABLE )) {
262+ meta .paramAnnotations .put (name , "PathVariable" );
263+ }
264+ }
265+ }
266+ }
267+ }
268+
269+ return meta ;
270+ }
173271}
0 commit comments