1
+ import com .fasterxml .jackson .databind .ObjectMapper ;
2
+ import com .fasterxml .jackson .databind .node .ObjectNode ;
3
+ import com .fasterxml .jackson .databind .node .POJONode ;
4
+ import com .fasterxml .jackson .databind .JsonSerializable ;
5
+ import com .fasterxml .jackson .databind .node .ArrayNode ;
6
+ import com .fasterxml .jackson .databind .JsonNode ;
7
+ import com .google .common .collect .ImmutableList ;
8
+ import com .google .common .collect .ImmutableMap ;
9
+ import ratpack .core .handling .Context ;
10
+ import ratpack .core .http .TypedData ;
11
+ import ratpack .core .form .Form ;
12
+ import ratpack .core .form .UploadedFile ;
13
+ import ratpack .core .parse .Parse ;
14
+ import ratpack .exec .Promise ;
15
+ import ratpack .func .Action ;
16
+ import ratpack .func .Function ;
17
+ import ratpack .func .MultiValueMap ;
18
+
19
+ import java .io .OutputStream ;
20
+ import java .io .IOException ;
21
+ import java .util .Collection ;
22
+ import java .util .HashMap ;
23
+ import java .util .Map ;
24
+ import java .util .List ;
25
+ import java .util .function .Predicate ;
26
+
27
+ import static ratpack .jackson .Jackson .jsonNode ;
28
+
29
+ class IntegrationTest {
30
+
31
+ static class Pojo {
32
+ String value ;
33
+
34
+ String getValue () {
35
+ return value ;
36
+ }
37
+ }
38
+
39
+ private final ObjectMapper objectMapper = new ObjectMapper ();
40
+
41
+ void sink (Object o ) {}
42
+
43
+ String taint () {
44
+ return null ;
45
+ }
46
+
47
+ void test1 (Context ctx ) {
48
+ bindJson (ctx , Pojo .class )
49
+ .then (pojo ->{
50
+ sink (pojo ); //$hasTaintFlow
51
+ sink (pojo .value ); //$hasTaintFlow
52
+ sink (pojo .getValue ()); //$hasTaintFlow
53
+ });
54
+ }
55
+
56
+ void test2 (Context ctx ) {
57
+ bindForm (ctx , Pojo .class , defaults -> defaults .put ("another" , "potato" ))
58
+ .then (pojo ->{
59
+ sink (pojo ); //$hasTaintFlow
60
+ sink (pojo .value ); //$hasTaintFlow
61
+ sink (pojo .getValue ()); //$hasTaintFlow
62
+ });
63
+ }
64
+
65
+ void test3 () {
66
+ Object value = extractSingleValueIfPossible (ImmutableList .of ("a" , taint ()));
67
+ sink (value ); //$hasTaintFlow
68
+ }
69
+
70
+ void test4 (Context ctx ) {
71
+ parseToForm (ctx , Pojo .class )
72
+ .map (pojoForm -> {
73
+ Map <String , Object > mergedParams = new HashMap <>();
74
+ filterAndMerge (pojoForm , mergedParams , name -> false );
75
+ return mergedParams ;
76
+ }).then (pojoMap -> {
77
+ sink (pojoMap ); //$hasTaintFlow
78
+ sink (pojoMap .get ("value" )); //$hasTaintFlow
79
+ });
80
+ }
81
+
82
+ private <T > Promise <T > bindJson (Context ctx , Class <T > type ) {
83
+ return ctx .getRequest ().getBody ()
84
+ .map (data -> {
85
+ String dataText = data .getText ();
86
+
87
+ try {
88
+ return ctx .parse (data , jsonNode (objectMapper ));
89
+ } catch (Exception e ) {
90
+ String msg = "Unable to parse json data while binding type " + type .getCanonicalName () + " [jsonData: " + dataText + "]" ;
91
+ throw new RuntimeException (msg , e );
92
+ }
93
+ })
94
+ .map (json ->
95
+ bind (ctx , json , type )
96
+ );
97
+ }
98
+
99
+ private <T > T bind (Context ctx , JsonNode input , Class <T > type ) {
100
+ T value ;
101
+ try {
102
+ value = objectMapper .convertValue (input , type );
103
+ } catch (Exception e ) {
104
+ throw new RuntimeException ("Failed to convert input to " + type .getName (), e );
105
+ }
106
+ return value ;
107
+ }
108
+
109
+ private static Promise <Form > parseToForm (Context ctx , Class <?> type ) {
110
+ return ctx .getRequest ().getBody ()
111
+ .map (data -> {
112
+ try {
113
+ return ctx .parse (data , Form .form ());
114
+ } catch (Exception e ) {
115
+ String msg = "Unable to parse form data while binding type " + type .getCanonicalName () + " [formData: " + data .getText () + "]" ;
116
+ throw new RuntimeException (msg , e );
117
+ }
118
+ });
119
+ }
120
+
121
+ private <T > Promise <T > bindForm (Context ctx , Class <T > type , Action <? super ImmutableMap .Builder <String , Object >> defaults ) {
122
+ return parseToForm (ctx , type )
123
+ .map (form -> {
124
+ ObjectNode input = toObjectNode (form , defaults , s -> false );
125
+ Map <String , List <UploadedFile >> filesMap = form .files ().getAll ();
126
+ filesMap .forEach ((name , files ) -> {
127
+ ArrayNode array = input .putArray (name );
128
+ files .forEach (f -> array .add (new POJONode (new UploadedFileWrapper (f ))));
129
+ });
130
+ return bind (ctx , input , type );
131
+ });
132
+ }
133
+
134
+ private ObjectNode toObjectNode (MultiValueMap <String , String > params , Action <? super ImmutableMap .Builder <String , Object >> defaults , Predicate <String > paramFilter ) throws Exception {
135
+ Map <String , Object > mergedParams = new HashMap <>(defaults .with (ImmutableMap .builder ()).build ());
136
+ filterAndMerge (params , mergedParams , paramFilter );
137
+ return objectMapper .valueToTree (mergedParams );
138
+ }
139
+
140
+ private static void filterAndMerge (MultiValueMap <String , String > params , Map <String , Object > defaults , Predicate <String > filter ) {
141
+ params .asMultimap ().asMap ().forEach ((name , values ) -> {
142
+ if (!isEmptyAndHasDefault (name , values , defaults ) && !filter .test (name )) {
143
+ defaults .put (name , extractSingleValueIfPossible (values ));
144
+ }
145
+ });
146
+ }
147
+
148
+ private static boolean isEmptyAndHasDefault (String name , Collection <String > values , Map <String , Object > defaults ) {
149
+ // STUB - This is to make the compiler happy
150
+ return false ;
151
+ }
152
+
153
+ private static Object extractSingleValueIfPossible (Collection <String > values ) {
154
+ return values .size () == 1 ? values .iterator ().next () : ImmutableList .copyOf (values );
155
+ }
156
+
157
+ private static class UploadedFileWrapper implements JsonSerializable {
158
+
159
+ private final UploadedFile file ;
160
+
161
+ private UploadedFileWrapper (UploadedFile file ) {
162
+ this .file = file ;
163
+ }
164
+
165
+ @ Override
166
+ public void serialize (Object gen , Object serializers ) throws IOException {
167
+ // empty
168
+ }
169
+
170
+ @ Override
171
+ public void serializeWithType (Object gen , Object serializers , Object typeSer ) throws IOException {
172
+ // empty
173
+ }
174
+ }
175
+ }
0 commit comments