package rfx.server.http.common;
import static io.netty.handler.codec.http.HttpHeaders.isKeepAlive;
import static io.netty.handler.codec.http.HttpHeaders.setContentLength;
import static io.netty.handler.codec.http.HttpHeaders.Names.CONNECTION;
import static io.netty.handler.codec.http.HttpHeaders.Names.CONTENT_LENGTH;
import static io.netty.handler.codec.http.HttpHeaders.Names.CONTENT_TYPE;
import static io.netty.handler.codec.http.HttpHeaders.Names.COOKIE;
import static io.netty.handler.codec.http.HttpHeaders.Names.REFERER;
import static io.netty.handler.codec.http.HttpHeaders.Names.USER_AGENT;
import static io.netty.handler.codec.http.HttpVersion.HTTP_1_1;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.http.DefaultFullHttpResponse;
import io.netty.handler.codec.http.FullHttpRequest;
import io.netty.handler.codec.http.FullHttpResponse;
import io.netty.handler.codec.http.HttpHeaders;
import io.netty.handler.codec.http.HttpRequest;
import io.netty.handler.codec.http.HttpResponseStatus;
import io.netty.handler.codec.http.QueryStringDecoder;
import io.netty.util.CharsetUtil;
import java.io.UnsupportedEncodingException;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.net.URLDecoder;
import java.util.List;
import java.util.Map;
import org.apache.commons.lang3.StringUtils;
import rfx.server.configs.ContentTypePool;
import rfx.server.http.HttpOutputResource;
import rfx.server.log.handlers.StaticFileHandler;
import rfx.server.util.CharPool;
import rfx.server.util.LogUtil;
import rfx.server.util.StringPool;
import rfx.server.util.StringUtil;
import com.google.gson.Gson;
public class NettyHttpUtil {
public static final String FAVICON_URI = "/favicon.ico";
public static final String HEADER_REFERER_NAME = "Referer";
public static final String HEADER_REFRESH_NAME = "Refresh";
public static final String HEADER_LOCATION_NAME = "Location";
public static final String HEADER_CONNECTION_CLOSE = "Close";
public static final String[] REFERER_SEARCH_LIST = new String[]{"\t%s","\t","%s","\r\n","\n","\r"};
public static final String[] REFERER_REPLACE_LIST = new String[]{"","","","","",""};
//redirect to url using HTML+JavaScript to preserve the referer, solution at https://coderwall.com/p/7a09ja
static final String HTML_FOR_REDIRECT;
static {
StringBuilder s= new StringBuilder();
s.append("<!DOCTYPE html><html><head><title></title></head><body>");
s.append("<script type='text/javascript' >window.location=\"$url\";</script>");
//tracking when javascript can not redirect to targeted url
//s.append("<noscript><img src='http://localhost:8080/ar?redirect=$autourl' /></noscript>");
s.append("</body></html>");
HTML_FOR_REDIRECT = s.toString();
}
public static FullHttpResponse redirectPath(String uri)
throws UnsupportedEncodingException {
int i = uri.indexOf("/http");
if (i > 0) {
// String metaUri = uri.substring(0, i);
// do something with metaUri, E.g:
// /r/13083/142/zizgzlzmzqzlzizhzizrzoziznzhzozizgzjzrzgzozizizgzdzizlzhzdzizkzmzdzmzgzozjzm21zjzmzq1t1u1t20201v21zjzjzr
String url = uri.substring(i + 1);
// System.out.println(metaUri + " " + url) ;
return redirect(URLDecoder.decode(url, StringPool.UTF_8));
}
return null;
}
public static FullHttpResponse redirect(String url) {
FullHttpResponse response = new DefaultFullHttpResponse(HTTP_1_1,HttpResponseStatus.MOVED_PERMANENTLY);
response.headers().set(HEADER_LOCATION_NAME, url);
response.headers().set(CONNECTION, HEADER_CONNECTION_CLOSE);
return response;
}
/**
* redirect url and preserve the referer in header
*
* @param url
* @return FullHttpResponse
*/
public static FullHttpResponse redirectWithReferer(String url) {
String html = HTML_FOR_REDIRECT.replace("$url", url);
ByteBuf byteBuf = Unpooled.copiedBuffer(html.getBytes());
FullHttpResponse response = new DefaultFullHttpResponse(HTTP_1_1,HttpResponseStatus.OK, byteBuf);
response.headers().set(HEADER_LOCATION_NAME, url);
response.headers().set(CONTENT_TYPE, ContentTypePool.HTML_UTF8);
response.headers().set(CONTENT_LENGTH, byteBuf.readableBytes());
response.headers().set(CONNECTION, HEADER_CONNECTION_CLOSE);
return response;
}
public static FullHttpResponse theHttpContent(String str) {
ByteBuf byteBuf = Unpooled.copiedBuffer(str.getBytes());
FullHttpResponse response = new DefaultFullHttpResponse(HTTP_1_1, HttpResponseStatus.OK ,byteBuf);
response.headers().set(CONTENT_TYPE, ContentTypePool.TEXT_UTF8);
response.headers().set(CONTENT_LENGTH, byteBuf.readableBytes());
response.headers().set(CONNECTION, HEADER_CONNECTION_CLOSE);
return response;
}
public static FullHttpResponse theHttpContent(String str, String contentType) {
ByteBuf byteBuf = Unpooled.copiedBuffer(str.getBytes());
FullHttpResponse response = new DefaultFullHttpResponse(HTTP_1_1, HttpResponseStatus.OK ,byteBuf);
response.headers().set(CONTENT_TYPE, contentType);
response.headers().set(CONTENT_LENGTH, byteBuf.readableBytes());
response.headers().set(CONNECTION, HEADER_CONNECTION_CLOSE);
return response;
}
public static FullHttpResponse theHttpContent(HttpOutputResource re, String contentType) {
FullHttpResponse response = new DefaultFullHttpResponse(HTTP_1_1, HttpResponseStatus.OK , re.getByteBuf());
response.headers().set(CONTENT_TYPE, contentType);
response.headers().set(CONTENT_LENGTH, re.getLength());
response.headers().set(CONNECTION, HEADER_CONNECTION_CLOSE);
//System.out.println("CONTENT_LENGTH:"+re.getLength());
//System.out.println();
return response;
}
public static FullHttpResponse theHttpContent(String str, HttpResponseStatus status) {
ByteBuf byteBuf = Unpooled.copiedBuffer(str.getBytes());
FullHttpResponse response = new DefaultFullHttpResponse(HTTP_1_1, status ,byteBuf);
response.headers().set(CONTENT_TYPE, ContentTypePool.TEXT_UTF8);
response.headers().set(CONTENT_LENGTH, byteBuf.readableBytes());
response.headers().set(CONNECTION, HEADER_CONNECTION_CLOSE);
return response;
}
public static String getParamValue(String name, Map<String, List<String>> params) {
return getParamValue(name, params, StringPool.BLANK);
}
public static String getParamValue(String name, Map<String, List<String>> params, String defaultVal) {
List<String> vals = params.get(name);
if (vals != null) {
if (vals.size()>0) {
return vals.get(0);
}
}
return defaultVal;
}
public static String getRemoteIP(ChannelHandlerContext ctx) {
try {
SocketAddress address = ctx.channel().remoteAddress();
if(address instanceof InetSocketAddress){
return ((InetSocketAddress)address).getAddress().getHostAddress();
}
return address.toString().split("/")[1].split(":")[0];
} catch (Throwable e) {
e.printStackTrace();
}
return "0.0.0.0";
}
public static String getLocalIP(ChannelHandlerContext ctx) {
try {
SocketAddress address = ctx.channel().localAddress();
if(address instanceof InetSocketAddress){
return ((InetSocketAddress)address).getAddress().getHostAddress();
}
return address.toString().split("/")[1].split(":")[0];
} catch (Throwable e) {
e.printStackTrace();
}
return "0.0.0.0";
}
static final String unknown = "unknown" ;
//http://r.va.gg/2011/07/handling-x-forwarded-for-in-java-and-tomcat.html
public static String getRemoteIP(ChannelHandlerContext ctx, HttpRequest request){
String ipAddress = request.headers().get("X-Forwarded-For");
if ( ! StringUtil.isNullOrEmpty(ipAddress) && ! unknown.equalsIgnoreCase(ipAddress)) {
//LogUtil.dumpToFileIpLog(ipAddress);
String[] toks = ipAddress.split(",");
int len = toks.length;
if(len > 1){
ipAddress = toks[len-1];
} else {
return ipAddress;
}
} else {
ipAddress = NettyHttpUtil.getRemoteIP(ctx);
}
return ipAddress;
}
/**
* quick log data for error (video tracking)
*
* @param ipAddress
* @param request
* @param uri
*/
public static void logErrorData(String ipAddress, HttpRequest request,String uri){
if(StringUtil.isEmpty(uri)){
return;
}
int idx = uri.indexOf("?");
if(idx < 0){
return;
}
String queryDetails = uri.substring(idx+1);
if(StringUtil.isEmpty(queryDetails)){
return;
}
try {
QueryStringDecoder queryStringDecoder = new QueryStringDecoder(uri);
Map<String, List<String>> params = queryStringDecoder.parameters();
String error = NettyHttpUtil.getParamValue("error", params, "");
if( ! StringUtil.isEmpty(error) ){
String userAgent = request.headers().get(USER_AGENT);
String cookieString = request.headers().get(COOKIE);
long time = System.currentTimeMillis() / 1000L;
StringBuilder logLine = new StringBuilder();
char tab = CharPool.TAB;
logLine.append(ipAddress).append(tab);
logLine.append(time).append(tab);
logLine.append(userAgent).append(tab);
logLine.append(queryDetails).append(tab);
logLine.append(cookieString);
logLine.append("\n");
LogUtil.dumpErrorLogData(logLine.toString());
}
} catch (Exception e) {}
}
public static boolean isBadLogRequest(String uri){
if(StringUtil.isEmpty(uri)){
return true;
}
int idx = uri.indexOf("?");
if(idx < 0){
return true;
}
String queryDetails = uri.substring(idx+1);
if(StringUtil.isEmpty(queryDetails)){
return true;
}
return false;
}
//TODO
// public static boolean filterLogsByDomain(String uri){
// QueryStringDecoder queryStringDecoder = new QueryStringDecoder(uri);
// Map<String, List<String>> params = queryStringDecoder.parameters();
// String origin = NettyHttpUtil.getParamValue("origin", params);
// LogFilterConfigs logFilterConfigs = LogFilterConfigs.load();
// Map<String, String> domains = logFilterConfigs.getOnlyWriteForDomains();
// Set<String> keys = domains.keySet();
// for (String key : keys) {
// return origin.contains(key);
// }
// return false;
// }
public static String responseAsJsonp(String callbackFunc, Map<String, Object> data){
String jsonData = new Gson().toJson(data);
return responseAsJsonp(callbackFunc, jsonData);
}
public static String responseAsJsonp(String callbackFunc, String jsonData){
if( StringUtil.isEmpty(callbackFunc) ){
return jsonData;
} else {
StringBuilder jsonp = new StringBuilder(callbackFunc);
jsonp.append("(").append(jsonData).append(")");
return jsonp.toString();
}
}
public static void sendHttpResponse(ChannelHandlerContext ctx, FullHttpRequest req, FullHttpResponse res) {
// Generate an error page if response getStatus code is not OK (200).
if (res.getStatus().code() != 200) {
ByteBuf buf = Unpooled.copiedBuffer(res.getStatus().toString(),CharsetUtil.UTF_8);
res.content().writeBytes(buf);
buf.release();
setContentLength(res, res.content().readableBytes());
}
// Send the response and close the connection if necessary.
ChannelFuture f = ctx.channel().writeAndFlush(res);
if (!isKeepAlive(req) || res.getStatus().code() != 200) {
f.addListener(ChannelFutureListener.CLOSE);
}
}
public static void response1pxGifImage(ChannelHandlerContext ctx) {
FullHttpResponse response = StaticFileHandler.theBase64Image1pxGif();
ChannelFuture future = ctx.write(response);
ctx.flush();
ctx.close();
//Close the non-keep-alive connection after the write operation is done.
future.addListener(ChannelFutureListener.CLOSE);
}
public static String getRefererUrl(HttpHeaders headers){
String refererUrl = headers.get(REFERER);
if(StringUtil.isNotEmpty(refererUrl)){
refererUrl = StringUtils.replaceEach(refererUrl, REFERER_SEARCH_LIST, REFERER_REPLACE_LIST);
}
return refererUrl;
}
}