001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.juneau.rest.server.httppart;
018
019import static org.apache.juneau.commons.httppart.HttpPartType.*;
020import static org.apache.juneau.commons.utils.CollectionUtils.*;
021import static org.apache.juneau.commons.utils.Shorts.*;
022import static org.apache.juneau.commons.utils.StringUtils.*;
023
024import java.util.*;
025import java.util.stream.*;
026
027import org.apache.juneau.commons.collections.*;
028import org.apache.juneau.commons.lang.*;
029import org.apache.juneau.commons.svl.*;
030import org.apache.juneau.commons.utils.*;
031import org.apache.juneau.http.*;
032import org.apache.juneau.http.HttpPart;
033import org.apache.juneau.http.header.*;
034import org.apache.juneau.http.part.*;
035import org.apache.juneau.http.response.*;
036import org.apache.juneau.marshall.httppart.*;
037import org.apache.juneau.rest.server.*;
038import org.apache.juneau.rest.server.util.*;
039
040import jakarta.servlet.http.Part;
041
042/**
043 * Represents the parsed form-data parameters in an HTTP request.
044 *
045 * <p>
046 *    The {@link RequestFormParamList} object is the API for accessing the HTTP request content as form data.
047 *    It can be accessed by passing it as a parameter on your REST Java method:
048 * </p>
049 * <p class='bjava'>
050 *    <ja>@RestPost</ja>(...)
051 *    <jk>public</jk> Object myMethod(RequestFormParamList <jv>formData</jv>) {...}
052 * </p>
053 *
054 * <h5 class='figure'>Example:</h5>
055 * <p class='bjava'>
056 *    <ja>@RestPost</ja>(...)
057 *    <jk>public</jk> Object myMethod(RequestFormParamList <jv>formData</jv>) {
058 *
059 *       <jc>// Get query parameters converted to various types.</jc>
060 *       <jk>int</jk> <jv>p1</jv> = <jv>formData</jv>.get(<js>"p1"</js>).asInteger().orElse(0);
061 *       String <jv>p2</jv> = <jv>formData</jv>.get(<js>"p2"</js>).orElse(<jk>null</jk>);
062 *       UUID <jv>p3</jv> = <jv>formData</jv>.get(<js>"p3"</js>).as(UUID.<jk>class</jk>).orElse(<jk>null</jk>);
063 *     }
064 * </p>
065 *
066 * <p>
067 *    Note that this object does NOT take GET parameters into account and only returns values found in the content of the request.
068 * </p>
069 *
070 * <p>
071 *    Some important methods on this class are:
072 * </p>
073 * <ul class='javatree'>
074 *    <li class='jc'>{@link RequestFormParamList}
075 *    <ul class='spaced-list'>
076 *       <li>Methods for retrieving form data parameters:
077 *       <ul class='javatreec'>
078 *          <li class='jm'>{@link RequestFormParamList#contains(String) contains(String)}
079 *          <li class='jm'>{@link RequestFormParamList#containsAny(String...) containsAny(String...)}
080 *          <li class='jm'>{@link RequestFormParamList#get(Class) get(Class)}
081 *          <li class='jm'>{@link RequestFormParamList#get(String) get(String)}
082 *          <li class='jm'>{@link RequestFormParamList#getAll(String) getAll(String)}
083 *          <li class='jm'>{@link RequestFormParamList#getFirst(String) getFirst(String)}
084 *          <li class='jm'>{@link RequestFormParamList#getLast(String) getLast(String)}
085 *       </ul>
086 *       <li>Methods overridding form data parameters:
087 *       <ul class='javatreec'>
088 *          <li class='jm'>{@link RequestFormParamList#add(HttpPart...) add(HttpPart...)}
089 *          <li class='jm'>{@link RequestFormParamList#add(Part) add(Part)}
090 *          <li class='jm'>{@link RequestFormParamList#add(String,Object) add(String,Object)}
091 *          <li class='jm'>{@link RequestFormParamList#addDefault(List) addDefault(List)}
092 *          <li class='jm'>{@link RequestFormParamList#addDefault(HttpPart...) addDefault(HttpPart...)}
093 *          <li class='jm'>{@link RequestFormParamList#addDefault(String,String) addDefault(String,String)}
094 *          <li class='jm'>{@link RequestFormParamList#remove(String) remove(String)}
095 *          <li class='jm'>{@link RequestFormParamList#set(HttpPart...) set(HttpPart...)}
096 *          <li class='jm'>{@link RequestFormParamList#set(String,Object) set(String,Object)}
097 *       </ul>
098 *       <li>Other methods:
099 *       <ul class='javatreec'>
100 *          <li class='jm'>{@link RequestFormParamList#asQueryString() asQueryString()}
101 *          <li class='jm'>{@link RequestFormParamList#copy() copy()}
102 *          <li class='jm'>{@link RequestFormParamList#isEmpty() isEmpty()}
103 *       </ul>
104 *    </ul>
105 * </ul>
106 *
107 * <h5 class='section'>See Also:</h5><ul>
108 *    <li class='jc'>{@link RequestFormParam}
109 *    <li class='ja'>{@link FormData}
110 *    <li class='ja'>{@link HasFormData}
111 *    <li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/RestServerHttpParts">HTTP parts in REST servers</a>
112 * </ul>
113 */
114@SuppressWarnings({
115   "java:S1192", // Duplicated literals (argument/property names) read more clearly inline than as constants
116   "resource" // Resource management handled externally
117})
118public class RequestFormParamList extends ArrayList<RequestFormParam> {
119
120   private static final long serialVersionUID = 1L;
121
122   private final transient RestRequest req;
123   private boolean caseSensitive;
124   private transient HttpPartParserSession parser;
125   private final transient VarResolverSession vs;
126
127   /**
128    * Constructor.
129    *
130    * <p>
131    * For a {@code multipart/form-data} request, the effective {@code @Rest(maxInput)} ceiling is enforced
132    * against both the declared {@code Content-Length} (checked before the servlet container parses the body)
133    * and the cumulative size of the parsed parts (checked afterward, as a backstop for a declared length that
134    * was absent, chunked, or simply didn't match) &mdash; a request whose multipart body is too large is
135    * rejected with a {@link PayloadTooLarge} (413) response rather than being handed off to the servlet
136    * container's own (often much larger, or unset) multipart limits.
137    *
138    * @param req The request creating this bean.  Must not be <jk>null</jk>.
139    * @param caseSensitive Whether case-sensitive name matching is enabled.
140    * @throws Exception Any exception can be thrown.
141    */
142   
143   @SuppressWarnings({
144      "java:S112", // Generic exception thrown; acceptable for framework/lifecycle methods
145      "java:S3776", // Cognitive complexity acceptable for this specific logic
146      "null" // throws Exception intentional - callback/lifecycle method
147   })
148   public RequestFormParamList(RestRequest req, boolean caseSensitive) throws Exception {
149      this.req = req;
150      this.caseSensitive = caseSensitive;
151      this.vs = req.getVarResolverSession();
152
153      Map<String,String[]> m = null;
154      Collection<Part> c = null;
155
156      RequestContent content = req.getContent();
157      if (content.isLoaded() || ! req.getHeader(ContentType.class).orElse(ContentType.NULL).equalsIgnoreCase("multipart/form-data")) {
158         var listMap = RestUtils.parseQuery(content.getReader());
159         m = map();
160         for (var e : listMap.entrySet()) {
161            if (e.getValue() == null)
162               m.put(e.getKey(), null);
163            else
164               m.put(e.getKey(), array(e.getValue(), String.class));
165         }
166      } else {
167         var maxInput = req.getOpContext().getMaxInput();
168         checkMultipartLength(maxInput, req.getHttpServletRequest().getContentLengthLong());
169         c = req.getHttpServletRequest().getParts();
170         checkMultipartLength(maxInput, c);
171         if (ie(c))
172            m = req.getHttpServletRequest().getParameterMap();
173      }
174
175      if (nn(m)) {
176         for (var e : m.entrySet()) {
177            var name = e.getKey();
178
179            var values = e.getValue();
180            if (values == null)
181               values = new String[0];
182
183            // Fix for behavior difference between Tomcat and WAS.
184            // getParameter("foo") on "&foo" in Tomcat returns "".
185            // getParameter("foo") on "&foo" in WAS returns null.
186            if (values.length == 1 && values[0] == null)
187               values[0] = "";
188
189            if (values.length == 0)
190               values = a((String)null);
191
192            for (var value : values)
193               add(new RequestFormParam(req, name, value));
194         }
195      } else if (nn(c)) {
196         c.forEach(this::add);
197      }
198   }
199
200   /**
201    * Rejects a {@code multipart/form-data} request outright when its declared {@code Content-Length} already
202    * exceeds {@code maxInput}, before the servlet container is asked to parse (and potentially buffer to disk)
203    * a body the resource has no intention of accepting.
204    *
205    * <p>
206    * Package-private for direct unit-test coverage.
207    *
208    * @param maxInput The effective {@code @Rest(maxInput)} ceiling in bytes. Values {@code <= 0} disable the check.
209    * @param declaredContentLength The request's declared {@code Content-Length}, as returned by
210    *    {@code HttpServletRequest.getContentLengthLong()}. Values {@code < 0} (absent/unparsable) are ignored;
211    *    the post-parse {@link #checkMultipartLength(long, Collection)} check still applies.
212    * @throws PayloadTooLarge If {@code declaredContentLength} exceeds {@code maxInput}.
213    */
214   static void checkMultipartLength(long maxInput, long declaredContentLength) {
215      if (maxInput > 0 && declaredContentLength > maxInput)
216         throw new PayloadTooLarge("Multipart request content exceeds the maximum allowed input size of %d bytes.", maxInput);
217   }
218
219   /**
220    * Rejects a {@code multipart/form-data} request once the cumulative size of its parsed parts exceeds
221    * {@code maxInput}, as a backstop for the case where the declared {@code Content-Length} was absent,
222    * chunked, or simply didn't match what the container actually buffered.
223    *
224    * <p>
225    * Package-private for direct unit-test coverage.
226    *
227    * @param maxInput The effective {@code @Rest(maxInput)} ceiling in bytes. Values {@code <= 0} disable the check.
228    * @param parts The parts already parsed by the servlet container. Can be <jk>null</jk> or empty (no-op).
229    * @throws PayloadTooLarge If the cumulative {@link Part#getSize()} across {@code parts} exceeds {@code maxInput}.
230    */
231   static void checkMultipartLength(long maxInput, Collection<Part> parts) {
232      if (maxInput <= 0 || ie(parts))
233         return;
234      var total = 0L;
235      for (var p : parts) {
236         total += p.getSize();
237         if (total > maxInput)
238            throw new PayloadTooLarge("Multipart request content exceeds the maximum allowed input size of %d bytes.", maxInput);
239      }
240   }
241
242   /**
243    * Copy constructor.
244    */
245   private RequestFormParamList(RequestFormParamList copyFrom) {
246      req = copyFrom.req;
247      caseSensitive = copyFrom.caseSensitive;
248      parser = copyFrom.parser;
249      addAll(copyFrom);
250      vs = copyFrom.vs;
251   }
252
253   /**
254    * Subset constructor.
255    */
256   private RequestFormParamList(RequestFormParamList copyFrom, String...names) {
257      this.req = copyFrom.req;
258      caseSensitive = copyFrom.caseSensitive;
259      parser = copyFrom.parser;
260      vs = copyFrom.vs;
261      for (var n : names)
262         copyFrom.stream().filter(x -> eq(x.getName(), n)).forEach(this::add);
263   }
264
265   /**
266    * Adds request parameter values.
267    *
268    * <p>
269    * Parameters are added to the end.
270    * <br>Existing parameters with the same name are not changed.
271    *
272    * @param parameters The parameter objects.  Must not be <jk>null</jk>.
273    * @return This object.
274    */
275   public RequestFormParamList add(HttpPart...parameters) {
276      reqnn("parameters", parameters);
277      for (var p : parameters)
278         if (nn(p))
279            add(p.getName(), p.getValue());
280      return this;
281   }
282
283   /**
284    * Adds a parameter value.
285    *
286    * <p>
287    * Parameter is added to the end.
288    * <br>Existing parameter with the same name are not changed.
289    *
290    * @param part The parameter part.  Must not be <jk>null</jk>.
291    * @return This object.
292    */
293   public RequestFormParamList add(Part part) {
294      reqnn("part", part);
295      add(new RequestFormParam(req, part).parser(parser));
296      return this;
297   }
298
299   /**
300    * Adds a parameter value.
301    *
302    * <p>
303    * Parameter is added to the end.
304    * <br>Existing parameter with the same name are not changed.
305    *
306    * @param name The parameter name.  Must not be <jk>null</jk>.
307    * @param value The parameter value.
308    * @return This object.
309    */
310   public RequestFormParamList add(String name, Object value) {
311      reqnn("name", name);
312      add(new RequestFormParam(req, name, s(value)).parser(parser));
313      return this;
314   }
315
316   /**
317    * Adds default entries to these parameters.
318    *
319    * <p>
320    * Similar to {@link #set(String, Object)} but doesn't override existing values.
321    *
322    * @param pairs
323    *    The default entries.
324    *    <br>Can be <jk>null</jk> (no-op).
325    * @return This object.
326    */
327   public RequestFormParamList addDefault(List<? extends HttpPart> pairs) {
328      if (pairs == null)
329         return this;
330      for (var p : pairs) {
331         var name = p.getName();
332         var l = stream(name);
333         var hasAllBlanks = l.allMatch(x -> Shorts.ie(x.getValue()));  // NOAI
334         if (hasAllBlanks) {
335            removeAll(getAll(name));
336            add(new RequestFormParam(req, name, vs.resolve(p.getValue())));
337         }
338      }
339      return this;
340   }
341
342   /**
343    * Adds default entries to these parameters.
344    *
345    * <p>
346    * Similar to {@link #set(String, Object)} but doesn't override existing values.
347    *
348    * @param pairs
349    *    The default entries.
350    *    <br>Can be <jk>null</jk> (no-op).
351    * @return This object.
352    */
353   public RequestFormParamList addDefault(HttpPart...pairs) {
354      return addDefault(l(pairs));
355   }
356
357   /**
358    * Adds a default entry to the form data parameters.
359    *
360    * @param name The name.
361    * @param value The value.
362    * @return This object.
363    */
364   public RequestFormParamList addDefault(String name, String value) {
365      return addDefault(HttpStringPart.of(name, value));
366   }
367
368   /**
369    * Converts this object to a query string.
370    *
371    * <p>
372    * Returned query string does not start with <js>'?'</js>.
373    *
374    * @return A new query string, or an empty string if this object is empty.
375    */
376   public String asQueryString() {
377      var sb = new StringBuilder();
378      for (var e : this) {
379         if (!sb.isEmpty())
380            sb.append("&");
381         sb.append(urlEncode(e.getName())).append('=').append(urlEncode(e.getValue()));
382      }
383      return sb.toString();
384   }
385
386   /**
387    * Sets case sensitivity for names in this list.
388    *
389    * @param value The new value for this setting.
390    * @return This object (for method chaining).
391    */
392   public RequestFormParamList caseSensitive(boolean value) {
393      caseSensitive = value;
394      return this;
395   }
396
397   /**
398    * Returns <jk>true</jk> if the parameters with the specified names are present.
399    *
400    * @param name The parameter name.  Must not be <jk>null</jk>.
401    * @return <jk>true</jk> if the parameters with the specified names are present.
402    */
403   public boolean contains(String name) {
404      return stream(name).findAny().isPresent();
405   }
406
407   /**
408    * Returns <jk>true</jk> if the parameter with any of the specified names are present.
409    *
410    * @param names The parameter names.  Must not be <jk>null</jk>.
411    * @return <jk>true</jk> if the parameter with any of the specified names are present.
412    */
413   public boolean containsAny(String...names) {
414      reqnn("names", names);
415      for (var n : names)
416         if (stream(n).findAny().isPresent())
417            return true;
418      return false;
419   }
420
421   /**
422    * Makes a copy of these parameters.
423    *
424    * @return A new parameters object.
425    */
426   public RequestFormParamList copy() {
427      return new RequestFormParamList(this);
428   }
429
430   /**
431    * Returns the form data parameter as the specified bean type.
432    *
433    * <p>
434    * Type must have a name specified via the {@link FormData} annotation
435    * and a public constructor that takes in either <c>value</c> or <c>name,value</c> as strings.
436    *
437    * @param <T> The bean type to create.
438    * @param type The bean type to create.
439    * @return The bean, or {@link Optional#empty()} if the parameter is not present.
440    */
441   public <T> Optional<T> get(Class<T> type) {
442      var cm = req.getMarshallingSession().getClassMeta(type);
443      var name = HttpParts.getName(FORMDATA, cm).orElseThrow(() -> rex("@FormData(name) not found on class %s", cn(type)));
444      return get(name).as(type);
445   }
446
447   /**
448    * Returns the condensed parameter with the specified name.
449    *
450    * <p>
451    * If multiple parameters are present, they will be combined into a single comma-delimited list.
452    *
453    * @param name The parameter name.
454    * @return The parameter, never <jk>null</jk>.
455    */
456   public RequestFormParam get(String name) {
457      List<RequestFormParam> l = getAll(name);
458      if (l.isEmpty())
459         return new RequestFormParam(req, name, null).parser(parser);
460      if (l.size() == 1)
461         return l.get(0);
462      var sb = new StringBuilder(128);
463      for (var i = 0; i < l.size(); i++) {
464         if (i > 0)
465            sb.append(", ");
466         sb.append(l.get(i).getValue());
467      }
468      return new RequestFormParam(req, name, sb.toString()).parser(parser);
469   }
470
471   /**
472    * Returns all the parameters with the specified name.
473    *
474    * @param name The parameter name.
475    * @return The list of all parameters with the specified name, or an empty list if none are found.
476    */
477   public List<RequestFormParam> getAll(String name) {
478      return stream(name).toList();
479   }
480
481   /**
482    * Returns the first parameter with the specified name.
483    *
484    * <p>
485    * Note that this method never returns <jk>null</jk> and that {@link RequestFormParam#isPresent()} can be used
486    * to test for the existence of the parameter.
487    *
488    * @param name The parameter name.
489    * @return The parameter.  Never <jk>null</jk>.
490    */
491   public RequestFormParam getFirst(String name) {
492      reqnn("name", name);
493      return stream(name).findFirst().orElseGet(() -> new RequestFormParam(req, name, null).parser(parser));
494   }
495
496   /**
497    * Returns the last parameter with the specified name.
498    *
499    * <p>
500    * Note that this method never returns <jk>null</jk> and that {@link RequestFormParam#isPresent()} can be used
501    * to test for the existence of the parameter.
502    *
503    * @param name The parameter name.
504    * @return The parameter.  Never <jk>null</jk>.
505    */
506   public RequestFormParam getLast(String name) {
507      reqnn("name", name);
508      var v = Holder.<RequestFormParam>empty();
509      stream(name).forEach(v::set);
510      return v.orElseGet(() -> new RequestFormParam(req, name, null).parser(parser));
511   }
512
513   /**
514    * Returns all the unique header names in this list.
515    * @return The list of all unique header names in this list.
516    */
517   public List<String> getNames() { return stream().map(RequestFormParam::getName).map(x -> caseSensitive ? x : lcr(x)).distinct().toList(); }
518
519   /**
520    * Returns all headers in sorted order.
521    *
522    * @return The stream of all headers in sorted order.
523    */
524   public Stream<RequestFormParam> getSorted() {
525      Comparator<RequestFormParam> x;
526      if (caseSensitive)
527         x = Comparator.comparing(RequestFormParam::getName);
528      else
529         x = (x1, x2) -> String.CASE_INSENSITIVE_ORDER.compare(x1.getName(), x2.getName());
530      return stream().sorted(x);
531   }
532
533   /**
534    * Sets the parser to use for part values.
535    *
536    * @param value The new value for this setting.
537    * @return This object.
538    */
539   public RequestFormParamList parser(HttpPartParserSession value) {
540      parser = value;
541      forEach(x -> x.parser(parser));
542      return this;
543   }
544
545   /**
546    * Remove parameters.
547    *
548    * @param name The parameter names.  Must not be <jk>null</jk>.
549    * @return This object.
550    */
551   public RequestFormParamList remove(String name) {
552      reqnn("name", name);
553      removeIf(x -> eq(x.getName(), name));
554      return this;
555   }
556
557   /**
558    * Sets request header values.
559    *
560    * <p>
561    * Parameters are added to the end of the headers.
562    * <br>Any previous parameters with the same name are removed.
563    *
564    * @param parameters The parameters to set.  Must not be <jk>null</jk> or contain <jk>null</jk>.
565    * @return This object.
566    */
567   public RequestFormParamList set(HttpPart...parameters) {
568      reqnn("headers", parameters);
569      for (var p : parameters)
570         remove(p.getName());
571      for (var p : parameters)
572         add(p);
573      return this;
574   }
575
576   /**
577    * Sets a parameter value.
578    *
579    * <p>
580    * Parameter is added to the end.
581    * <br>Any previous parameters with the same name are removed.
582    *
583    * @param name The parameter name.  Must not be <jk>null</jk>.
584    * @param value
585    *    The parameter value.
586    *    <br>Converted to a string using {@link Object#toString()}.
587    *    <br>Can be <jk>null</jk> (the parameter is set with a <jk>null</jk> value).
588    * @return This object.
589    */
590   public RequestFormParamList set(String name, Object value) {
591      reqnn("name", name);
592      set(new RequestFormParam(req, name, s(value)).parser(parser));
593      return this;
594   }
595
596   /**
597    * Returns all headers with the specified name.
598    *
599    * @param name The header name.
600    * @return The stream of all headers with matching names.  Never <jk>null</jk>.
601    */
602   public Stream<RequestFormParam> stream(String name) {
603      return stream().filter(x -> eq(x.getName(), name));
604   }
605
606   /**
607    * Returns a copy of this object but only with the specified param names copied.
608    *
609    * @param names The list to include in the copy.
610    * @return A new list object.
611    */
612   public RequestFormParamList subset(String...names) {
613      return new RequestFormParamList(this, names);
614   }
615
616   protected FluentMap<String,Object> properties() {
617      // @formatter:off
618      var m = filteredBeanPropertyMap();
619      for (var n : getNames())
620         m.a(n, get(n).asString().orElse(null));
621      return m;
622      // @formatter:on
623   }
624
625   @Override
626   public boolean equals(Object o) {
627      return this == o || (o instanceof RequestFormParamList o2 && super.equals(o2));
628   }
629
630   @Override
631   public int hashCode() {
632      return super.hashCode();
633   }
634
635   @Override /* Overridden from Object */
636   public String toString() {
637      return r(properties());
638   }
639
640   private boolean eq(String s1, String s2) {
641      return Shorts.eq(! caseSensitive, s1, s2);  // NOAI
642   }
643}