1 | package com.renomad.minum.web; | |
2 | ||
3 | import com.renomad.minum.utils.StringUtils; | |
4 | ||
5 | import java.util.*; | |
6 | ||
7 | /** | |
8 | * This class represents the body of an HTML message. | |
9 | * See <a href="https://en.wikipedia.org/wiki/HTTP_message_body">Message Body on Wikipedia</a> | |
10 | *<br> | |
11 | * <pre>{@code | |
12 | * This could be a response from the web server: | |
13 | * | |
14 | * HTTP/1.1 200 OK | |
15 | * Date: Sun, 10 Oct 2010 23:26:07 GMT | |
16 | * Server: Apache/2.2.8 (Ubuntu) mod_ssl/2.2.8 OpenSSL/0.9.8g | |
17 | * Last-Modified: Sun, 26 Sep 2010 22:04:35 GMT | |
18 | * ETag: "45b6-834-49130cc1182c0" | |
19 | * Accept-Ranges: bytes | |
20 | * Content-Length: 12 | |
21 | * Connection: close | |
22 | * Content-Type: text/html | |
23 | * | |
24 | * Hello world! | |
25 | * }</pre> | |
26 | * <p> | |
27 | * The message body (or content) in this example is the text <pre>Hello world!</pre>. | |
28 | * </p> | |
29 | */ | |
30 | public final class Body { | |
31 | ||
32 | private static final byte[] EMPTY_BYTES = new byte[0]; | |
33 | private final Map<String, byte[]> bodyMap; | |
34 | private final byte[] raw; | |
35 | private final List<Partition> partitions; | |
36 | private final BodyType bodyType; | |
37 | ||
38 | /** | |
39 | * An empty body instance, useful when you | |
40 | * need an instantiated body. | |
41 | */ | |
42 | public static final Body EMPTY = new Body(Map.of(), EMPTY_BYTES, List.of(), BodyType.NONE); | |
43 | ||
44 | /** | |
45 | * Build a body for an HTTP message | |
46 | * @param bodyMap a map of key-value pairs, presumably extracted from form data. Empty | |
47 | * if our body isn't one of the form data protocols we understand. | |
48 | * @param raw the raw bytes of this body | |
49 | * @param partitions if the body is of type form/multipart, these will be the list of partitions | |
50 | */ | |
51 | public Body(Map<String, byte[]> bodyMap, byte[] raw, List<Partition> partitions, BodyType bodyType) { | |
52 | this.bodyMap = new HashMap<>(bodyMap); | |
53 | this.raw = raw.clone(); | |
54 | this.partitions = partitions; | |
55 | this.bodyType = bodyType; | |
56 | } | |
57 | ||
58 | /** | |
59 | * Return the value for a key, as a string. This method | |
60 | * presumes the data was sent URL-encoded. | |
61 | * <p> | |
62 | * If there is no value found for the | |
63 | * provided key, an empty string will be | |
64 | * returned. | |
65 | * </p> | |
66 | * <p> | |
67 | * Otherwise, the value found will be converted | |
68 | * to a string, and trimmed. | |
69 | * </p> | |
70 | * <p> | |
71 | * Note: if the request is a multipart/form-data, this | |
72 | * method will throw a helpful exception to indicate that. | |
73 | * </p> | |
74 | * | |
75 | */ | |
76 | public String asString(String key) { | |
77 |
1
1. asString : negated conditional → KILLED |
if (this.equals(EMPTY)) { |
78 | return ""; | |
79 | } | |
80 |
1
1. asString : negated conditional → KILLED |
if (this.bodyType.equals(BodyType.MULTIPART)) { |
81 | throw new WebServerException("Request body is in multipart format. Use .getPartitionByName instead"); | |
82 | } | |
83 |
1
1. asString : negated conditional → KILLED |
if (this.bodyType.equals(BodyType.UNRECOGNIZED)) { |
84 | throw new WebServerException("Request body is not in a recognized key-value encoding. Use .asString() to obtain the body data"); | |
85 | } | |
86 | byte[] byteArray = bodyMap.get(key); | |
87 |
1
1. asString : negated conditional → KILLED |
if (byteArray == null) { |
88 | return ""; | |
89 | } else { | |
90 |
1
1. asString : replaced return value with "" for com/renomad/minum/web/Body::asString → KILLED |
return StringUtils.byteArrayToString(byteArray).trim(); |
91 | } | |
92 | ||
93 | } | |
94 | ||
95 | /** | |
96 | * Return the entire raw contents of the body of this | |
97 | * request, as a string. No processing involved other | |
98 | * than converting the bytes to a string. | |
99 | */ | |
100 | public String asString() { | |
101 |
1
1. asString : negated conditional → KILLED |
if (this.equals(EMPTY)) { |
102 | return ""; | |
103 | } | |
104 |
1
1. asString : replaced return value with "" for com/renomad/minum/web/Body::asString → KILLED |
return StringUtils.byteArrayToString(raw).trim(); |
105 | } | |
106 | ||
107 | /** | |
108 | * Return the bytes of this request body by its key. This method | |
109 | * presumes the data was sent URL-encoded. | |
110 | */ | |
111 | public byte[] asBytes(String key) { | |
112 |
1
1. asBytes : negated conditional → KILLED |
if (this.equals(EMPTY)) { |
113 |
1
1. asBytes : replaced return value with null for com/renomad/minum/web/Body::asBytes → KILLED |
return new byte[0]; |
114 | } | |
115 |
1
1. asBytes : negated conditional → KILLED |
if (this.bodyType.equals(BodyType.MULTIPART)) { |
116 | throw new WebServerException("Request body is in multipart format. Use .getPartitionByName instead"); | |
117 | } | |
118 |
1
1. asBytes : negated conditional → KILLED |
if (this.bodyType.equals(BodyType.UNRECOGNIZED)) { |
119 | throw new WebServerException("Request body is not in a recognized key-value encoding. Use .asBytes() to obtain the body data"); | |
120 | } | |
121 |
1
1. asBytes : replaced return value with null for com/renomad/minum/web/Body::asBytes → KILLED |
return bodyMap.get(key); |
122 | } | |
123 | ||
124 | /** | |
125 | * Returns the raw bytes of this HTTP message's body. This method | |
126 | * presumes the data was sent URL-encoded. | |
127 | */ | |
128 | public byte[] asBytes() { | |
129 |
1
1. asBytes : negated conditional → KILLED |
if (this.equals(EMPTY)) { |
130 |
1
1. asBytes : replaced return value with null for com/renomad/minum/web/Body::asBytes → KILLED |
return new byte[0]; |
131 | } | |
132 |
1
1. asBytes : replaced return value with null for com/renomad/minum/web/Body::asBytes → KILLED |
return this.raw.clone(); |
133 | } | |
134 | ||
135 | /** | |
136 | * If the body is of type form/multipart, return the partitions | |
137 | * <p> | |
138 | * For example: | |
139 | * </p> | |
140 | * <pre> | |
141 | * --i_am_a_boundary | |
142 | * Content-Type: text/plain | |
143 | * Content-Disposition: form-data; name="text1" | |
144 | * | |
145 | * I am a value that is text | |
146 | * --i_am_a_boundary | |
147 | * Content-Type: application/octet-stream | |
148 | * Content-Disposition: form-data; name="image_uploads"; filename="photo_preview.jpg" | |
149 | * </pre> | |
150 | */ | |
151 | public List<Partition> getPartitionHeaders() { | |
152 |
1
1. getPartitionHeaders : negated conditional → KILLED |
if (this.equals(EMPTY)) { |
153 | return List.of(); | |
154 | } | |
155 |
1
1. getPartitionHeaders : negated conditional → KILLED |
if (this.bodyType.equals(BodyType.FORM_URL_ENCODED)) { |
156 | throw new WebServerException("Request body encoded in form-urlencoded format. getPartitionHeaders is only used with multipart encoded data."); | |
157 | } | |
158 |
1
1. getPartitionHeaders : negated conditional → KILLED |
if (this.bodyType.equals(BodyType.UNRECOGNIZED)) { |
159 | throw new WebServerException("Request body encoded is not encoded in a recognized format. getPartitionHeaders is only used with multipart encoded data."); | |
160 | } | |
161 |
1
1. getPartitionHeaders : replaced return value with Collections.emptyList for com/renomad/minum/web/Body::getPartitionHeaders → KILLED |
return new ArrayList<>(partitions); |
162 | } | |
163 | ||
164 | /** | |
165 | * A helper method for getting the partitions with a particular name set in its | |
166 | * content-disposition. This returns a list of partitions because there is nothing | |
167 | * preventing the browser doing this, and in fact it will typically send partitions | |
168 | * with the same name when sending multiple files from one input. (HTML5 provides the | |
169 | * ability to select multiple files on the input with type=file) | |
170 | */ | |
171 | public List<Partition> getPartitionByName(String name) { | |
172 |
1
1. getPartitionByName : negated conditional → KILLED |
if (this.equals(EMPTY)) { |
173 | return List.of(); | |
174 | } | |
175 |
1
1. getPartitionByName : negated conditional → KILLED |
if (this.bodyType.equals(BodyType.FORM_URL_ENCODED)) { |
176 | throw new WebServerException("Request body encoded in form-urlencoded format. use .asString(key) or asBytes(key)"); | |
177 | } | |
178 |
1
1. getPartitionByName : negated conditional → KILLED |
if (this.bodyType.equals(BodyType.UNRECOGNIZED)) { |
179 | throw new WebServerException("Request body encoded is not encoded in a recognized format. use .asString() or asBytes()"); | |
180 | } | |
181 |
3
1. getPartitionByName : replaced return value with Collections.emptyList for com/renomad/minum/web/Body::getPartitionByName → KILLED 2. lambda$getPartitionByName$0 : replaced boolean return with true for com/renomad/minum/web/Body::lambda$getPartitionByName$0 → KILLED 3. lambda$getPartitionByName$0 : replaced boolean return with false for com/renomad/minum/web/Body::lambda$getPartitionByName$0 → KILLED |
return getPartitionHeaders().stream().filter(x -> x.getContentDisposition().getName().equalsIgnoreCase(name)).toList(); |
182 | } | |
183 | ||
184 | /** | |
185 | * Returns the {@link BodyType}, which is necessary to distinguish | |
186 | * which methods to run for accessing data. For instance, if the body | |
187 | * is of type FORM_URL_ENCODED, you may use methods | |
188 | * like {@link #getKeys()}, {@link #asBytes(String)}, or {@link #asString(String)} | |
189 | * <br> | |
190 | * On the other hand, if the type is MULTIPART, you will need to use {@link #getPartitionHeaders()} | |
191 | * to get a list of the partitions. | |
192 | * <br> | |
193 | * If the body type is UNRECOGNIZED, you can use {@link #asBytes()} to get the body. | |
194 | * <br> | |
195 | * Don't forget, there is also an option to obtain the body's {@link java.io.InputStream} by | |
196 | * using {@link Request#getSocketWrapper()}, but that needs to be done before running {@link Request#getBody()} | |
197 | */ | |
198 | public BodyType getBodyType() { | |
199 |
1
1. getBodyType : replaced return value with null for com/renomad/minum/web/Body::getBodyType → KILLED |
return bodyType; |
200 | } | |
201 | ||
202 | /** | |
203 | * Get all the keys for the key-value pairs in the body | |
204 | */ | |
205 | public Set<String> getKeys() { | |
206 |
1
1. getKeys : negated conditional → KILLED |
if (this.equals(EMPTY)) { |
207 | return Set.of(); | |
208 | } | |
209 |
1
1. getKeys : replaced return value with Collections.emptySet for com/renomad/minum/web/Body::getKeys → KILLED |
return bodyMap.keySet(); |
210 | } | |
211 | ||
212 | @Override | |
213 | public boolean equals(Object o) { | |
214 |
2
1. equals : negated conditional → KILLED 2. equals : replaced boolean return with false for com/renomad/minum/web/Body::equals → KILLED |
if (this == o) return true; |
215 |
3
1. equals : replaced boolean return with true for com/renomad/minum/web/Body::equals → TIMED_OUT 2. equals : negated conditional → KILLED 3. equals : negated conditional → KILLED |
if (o == null || getClass() != o.getClass()) return false; |
216 | Body body = (Body) o; | |
217 |
5
1. equals : negated conditional → KILLED 2. equals : replaced boolean return with true for com/renomad/minum/web/Body::equals → KILLED 3. equals : negated conditional → KILLED 4. equals : negated conditional → KILLED 5. equals : negated conditional → KILLED |
return Objects.equals(bodyMap, body.bodyMap) && Arrays.equals(raw, body.raw) && Objects.equals(partitions, body.partitions) && bodyType == body.bodyType; |
218 | } | |
219 | ||
220 | @Override | |
221 | public int hashCode() { | |
222 | int result = Objects.hash(bodyMap, partitions, bodyType); | |
223 |
2
1. hashCode : Replaced integer multiplication with division → TIMED_OUT 2. hashCode : Replaced integer addition with subtraction → TIMED_OUT |
result = 31 * result + Arrays.hashCode(raw); |
224 |
1
1. hashCode : replaced int return with 0 for com/renomad/minum/web/Body::hashCode → TIMED_OUT |
return result; |
225 | } | |
226 | ||
227 | @Override | |
228 | public String toString() { | |
229 |
1
1. toString : replaced return value with "" for com/renomad/minum/web/Body::toString → KILLED |
return "Body{" + |
230 | "bodyMap=" + bodyMap + | |
231 | ", bodyType=" + bodyType + | |
232 | '}'; | |
233 | } | |
234 | ||
235 | } | |
Mutations | ||
77 |
1.1 |
|
80 |
1.1 |
|
83 |
1.1 |
|
87 |
1.1 |
|
90 |
1.1 |
|
101 |
1.1 |
|
104 |
1.1 |
|
112 |
1.1 |
|
113 |
1.1 |
|
115 |
1.1 |
|
118 |
1.1 |
|
121 |
1.1 |
|
129 |
1.1 |
|
130 |
1.1 |
|
132 |
1.1 |
|
152 |
1.1 |
|
155 |
1.1 |
|
158 |
1.1 |
|
161 |
1.1 |
|
172 |
1.1 |
|
175 |
1.1 |
|
178 |
1.1 |
|
181 |
1.1 2.2 3.3 |
|
199 |
1.1 |
|
206 |
1.1 |
|
209 |
1.1 |
|
214 |
1.1 2.2 |
|
215 |
1.1 2.2 3.3 |
|
217 |
1.1 2.2 3.3 4.4 5.5 |
|
223 |
1.1 2.2 |
|
224 |
1.1 |
|
229 |
1.1 |