@@ -46,7 +46,12 @@ public void methodAdvice(MethodTransformer transformer) {
4646 transformer .applyAdvice (
4747 named ("extractContentParameters" ).and (takesArguments (0 )).or (named ("getParts" )),
4848 getClass ().getName () + "$ExtractContentParametersAdvice" );
49- transformer .applyAdvice (named ("getParts" ), getClass ().getName () + "$GetFilenamesAdvice" );
49+ transformer .applyAdvice (
50+ named ("getParts" ).and (takesArguments (0 )),
51+ getClass ().getName () + "$GetFilenamesAdvice" );
52+ transformer .applyAdvice (
53+ named ("getParts" ).and (takesArguments (1 )),
54+ getClass ().getName () + "$GetFilenamesFromMultiPartAdvice" );
5055 }
5156
5257 private static final Reference REQUEST_REFERENCE =
@@ -105,11 +110,15 @@ static void after(
105110 }
106111 }
107112
113+ /**
114+ * Fires the {@code requestFilesFilenames} event when the application calls public {@code
115+ * getParts()}. The {@code _contentParameters == null} guard ensures the WAF is invoked only on
116+ * the first call — subsequent calls return the cached result without re-processing.
117+ */
108118 @ RequiresRequestContext (RequestContextSlot .APPSEC )
109119 public static class GetFilenamesAdvice {
110120 @ Advice .OnMethodEnter (suppress = Throwable .class )
111- static boolean before (
112- @ Advice .FieldValue ("_contentParameters" ) final MultiMap <String > map ) {
121+ static boolean before (@ Advice .FieldValue ("_contentParameters" ) final MultiMap <String > map ) {
113122 final int callDepth = CallDepthThreadLocalMap .incrementCallDepth (Collection .class );
114123 return callDepth == 0 && map == null ;
115124 }
@@ -166,4 +175,73 @@ static void after(
166175 }
167176 }
168177 }
178+
179+ /**
180+ * Fires the {@code requestFilesFilenames} event when multipart content is parsed via the
181+ * internal {@code getParts(MultiMap)} path triggered by {@code getParameter*()} /
182+ * {@code getParameterMap()} — i.e. when the application never calls public {@code getParts()}.
183+ * In Jetty 9.3+, {@code extractContentParameters()} assigns {@code _contentParameters} before
184+ * calling this method, so {@code map == null} cannot be used as a "first parse" guard here;
185+ * the call-depth guard prevents double-firing when {@code getParts()} internally delegates to
186+ * this method.
187+ */
188+ @ RequiresRequestContext (RequestContextSlot .APPSEC )
189+ public static class GetFilenamesFromMultiPartAdvice {
190+ @ Advice .OnMethodEnter (suppress = Throwable .class )
191+ static boolean before () {
192+ return CallDepthThreadLocalMap .incrementCallDepth (Collection .class ) == 0 ;
193+ }
194+
195+ @ Advice .OnMethodExit (suppress = Throwable .class , onThrowable = Throwable .class )
196+ static void after (
197+ @ Advice .Enter boolean proceed ,
198+ @ Advice .Return Collection parts ,
199+ @ ActiveRequestContext RequestContext reqCtx ,
200+ @ Advice .Thrown (readOnly = false ) Throwable t ) {
201+ CallDepthThreadLocalMap .decrementCallDepth (Collection .class );
202+ if (!proceed || t != null || parts == null || parts .isEmpty ()) {
203+ return ;
204+ }
205+ Method getSubmittedFileName = null ;
206+ try {
207+ getSubmittedFileName = parts .iterator ().next ().getClass ().getMethod ("getSubmittedFileName" );
208+ } catch (Exception ignored ) {
209+ }
210+ if (getSubmittedFileName == null ) {
211+ return ;
212+ }
213+ List <String > filenames = new ArrayList <>();
214+ for (Object part : parts ) {
215+ try {
216+ String name = (String ) getSubmittedFileName .invoke (part );
217+ if (name != null && !name .isEmpty ()) {
218+ filenames .add (name );
219+ }
220+ } catch (Exception ignored ) {
221+ }
222+ }
223+ if (filenames .isEmpty ()) {
224+ return ;
225+ }
226+ CallbackProvider cbp = AgentTracer .get ().getCallbackProvider (RequestContextSlot .APPSEC );
227+ BiFunction <RequestContext , List <String >, Flow <Void >> callback =
228+ cbp .getCallback (EVENTS .requestFilesFilenames ());
229+ if (callback == null ) {
230+ return ;
231+ }
232+ Flow <Void > flow = callback .apply (reqCtx , filenames );
233+ Flow .Action action = flow .getAction ();
234+ if (action instanceof Flow .Action .RequestBlockingAction ) {
235+ Flow .Action .RequestBlockingAction rba = (Flow .Action .RequestBlockingAction ) action ;
236+ BlockResponseFunction brf = reqCtx .getBlockResponseFunction ();
237+ if (brf != null ) {
238+ brf .tryCommitBlockingResponse (reqCtx .getTraceSegment (), rba );
239+ if (t == null ) {
240+ t = new BlockingException ("Blocked request (multipart file upload)" );
241+ reqCtx .getTraceSegment ().effectivelyBlocked ();
242+ }
243+ }
244+ }
245+ }
246+ }
169247}
0 commit comments