| 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 → TIMED_OUT |
if (this.equals(EMPTY)) { |
| 102 | return ""; | |
| 103 | } | |
| 104 |
1
1. asString : replaced return value with "" for com/renomad/minum/web/Body::asString → TIMED_OUT |
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 |