/*
* Copyright 2017 ZhangJiupeng
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package cc.agentx.wrapper;
import cc.agentx.masq.HttpFaker;
import cc.agentx.protocol.Http;
import cc.agentx.util.KeyHelper;
import org.bouncycastle.util.Arrays;
import java.util.Base64;
import java.util.regex.Matcher;
public class FakedHttpWrapper extends Wrapper {
private boolean requestMode;
private Base64.Encoder encoder;
private Base64.Decoder decoder;
public FakedHttpWrapper(boolean requestMode) {
this.requestMode = requestMode;
this.encoder = Base64.getUrlEncoder();
this.decoder = Base64.getUrlDecoder();
}
@Override
public byte[] wrap(final byte[] bytes) {
if (requestMode) {
return warpInRequest(bytes);
} else {
return wrapInResponse(bytes);
}
}
private byte[] warpInRequest(final byte[] bytes) {
String encodedStr = encoder.encodeToString(bytes).replaceAll("=", "");
// 20 percent of small data are faked by post method requests
if (bytes.length > 512 || KeyHelper.generateRandomInteger(0, 10) < 2) {
String header = HttpFaker.getRandomRequestHeader(Http.METHOD_POST, true);
header = header.replaceAll(Matcher.quoteReplacement("$"), String.valueOf(encodedStr.length()));
return Arrays.concatenate(header.getBytes(), encodedStr.getBytes());
} else {
String header = HttpFaker.getRandomRequestHeader(Http.METHOD_GET, true);
header = header.replaceAll(Matcher.quoteReplacement("$"), encodedStr);
return header.getBytes();
}
}
private byte[] wrapInResponse(final byte[] bytes) {
String header = HttpFaker.getRandomResponseHeader(Http.RESPONSE_200, true);
header = header.replaceAll(Matcher.quoteReplacement("$"), String.valueOf(bytes.length));
return Arrays.concatenate(header.getBytes(), bytes);
}
@Override
public byte[] unwrap(final byte[] bytes) {
if (requestMode) {
return unwrapFromRequest(bytes);
} else {
return unwrapFromResponse(bytes);
}
}
private byte[] unwrapFromRequest(final byte[] bytes) {
String decodedStr = new String(bytes);
String rawStr = null;
if (decodedStr.startsWith(Http.METHOD_GET)) {
for (String line : decodedStr.split(Http.CRLF)) {
if (line.startsWith("Cookie: ")) {
for (String cookie : line.substring(5).split("; ")) {
String[] kvPair = cookie.split("=");
if (kvPair.length == 2 && kvPair[0].contains("_")) {
rawStr = kvPair[1];
break;
}
}
break;
}
}
if (rawStr == null) {
return new byte[0];
}
} else if (decodedStr.startsWith(Http.METHOD_POST)) {
String[] parts = decodedStr.split(Http.CRLF.concat(Http.CRLF));
if (parts.length != 2) {
return new byte[0];
}
rawStr = parts[1];
} else {
throw new RuntimeException("unknown format");
}
StringBuilder buffer = new StringBuilder(rawStr);
while (buffer.length() % 4 != 0)
buffer.append('=');
return decoder.decode(buffer.toString());
}
public byte[] unwrapFromResponse(final byte[] bytes) {
// caution: placeholder bytes' end-pos must less than 200
String fuzzyHeader = new String(Arrays.copyOfRange(bytes, 0, 200));
if (!fuzzyHeader.startsWith(Http.VERSION_1_1)) {
throw new RuntimeException("unknown format");
}
fuzzyHeader = fuzzyHeader.substring(fuzzyHeader.indexOf("Content-Length: ") + "Content-Length: ".length());
fuzzyHeader = fuzzyHeader.substring(0, fuzzyHeader.indexOf(Http.CRLF));
int rawLen = Integer.parseInt(fuzzyHeader);
return Arrays.copyOfRange(bytes, bytes.length - rawLen, bytes.length);
}
}