package org.webpieces.webserver.impl;
import java.util.ArrayList;
import java.util.List;
import javax.inject.Inject;
import javax.inject.Singleton;
import org.joda.time.DateTime;
import org.joda.time.DateTimeZone;
import org.joda.time.format.DateTimeFormat;
import org.joda.time.format.DateTimeFormatter;
import org.webpieces.ctx.api.Current;
import org.webpieces.ctx.api.RouterCookie;
import org.webpieces.httpparser.api.HttpParserFactory;
import org.webpieces.httpparser.api.common.Header;
import org.webpieces.httpparser.api.common.KnownHeaderName;
import org.webpieces.httpparser.api.common.ResponseCookie;
import org.webpieces.httpparser.api.dto.HttpRequest;
import org.webpieces.httpparser.api.dto.HttpResponse;
import org.webpieces.httpparser.api.dto.HttpResponseStatus;
import org.webpieces.httpparser.api.dto.HttpResponseStatusLine;
import org.webpieces.httpparser.api.dto.KnownStatusCode;
import org.webpieces.httpparser.api.subparsers.HeaderPriorityParser;
import org.webpieces.router.api.exceptions.CookieTooLargeException;
import org.webpieces.router.impl.CookieTranslator;
import org.webpieces.router.impl.compression.MimeTypes;
import org.webpieces.router.impl.compression.MimeTypes.MimeTypeResult;
import org.webpieces.util.logging.Logger;
import org.webpieces.util.logging.LoggerFactory;
@Singleton
public class ResponseCreator {
private static final Logger log = LoggerFactory.getLogger(ResponseCreator.class);
private static final HeaderPriorityParser httpSubParser = HttpParserFactory.createHeaderParser();
private static final DateTimeFormatter formatter = DateTimeFormat.forPattern("E, dd MMM Y HH:mm:ss");
@Inject
private CookieTranslator cookieTranslator;
@Inject
private MimeTypes mimeTypes;
public ResponseEncodingTuple createResponse(HttpRequest request, KnownStatusCode statusCode,
String extension, String defaultMime, boolean isDynamicPartOfWebsite) {
MimeTypeResult mimeType = mimeTypes.extensionToContentType(extension, defaultMime);
return createContentResponse(request, statusCode.getCode(), isDynamicPartOfWebsite, mimeType);
}
ResponseEncodingTuple createContentResponse(HttpRequest request, int statusCode,
boolean isDynamicPartOfWebsite, MimeTypeResult mimeType) {
KnownStatusCode code = KnownStatusCode.lookup(statusCode);
HttpResponseStatus status = new HttpResponseStatus();
if(code != null) {
status.setKnownStatus(code);
} else {
status.setCode(statusCode);
status.setReason("");
}
HttpResponseStatusLine statusLine = new HttpResponseStatusLine();
statusLine.setStatus(status);
HttpResponse response = new HttpResponse();
response.setStatusLine(statusLine);
response.addHeader(new Header(KnownHeaderName.CONTENT_TYPE, mimeType.mime));
addCommonHeaders(request, response, isDynamicPartOfWebsite);
return new ResponseEncodingTuple(response, mimeType);
}
public static class ResponseEncodingTuple {
public HttpResponse response;
public MimeTypeResult mimeType;
public ResponseEncodingTuple(HttpResponse response, MimeTypeResult mimeType) {
this.response = response;
this.mimeType = mimeType;
}
}
public void addCommonHeaders(HttpRequest request, HttpResponse response, boolean isDynamicPartOfWebsite) {
KnownStatusCode statusCode = response.getStatusLine().getStatus().getKnownStatus();
Header connHeader = request.getHeaderLookupStruct().getHeader(KnownHeaderName.CONNECTION);
DateTime now = DateTime.now().toDateTime(DateTimeZone.UTC);
String dateStr = formatter.print(now)+" GMT";
//in general, nearly all these headers are desired..
Header date = new Header(KnownHeaderName.DATE, dateStr);
response.addHeader(date);
// Header xFrame = new Header("X-Frame-Options", "SAMEORIGIN");
// response.addHeader(xFrame);
if(isDynamicPartOfWebsite) {
List<RouterCookie> cookies = createCookies(statusCode);
for(RouterCookie c : cookies) {
Header cookieHeader = create(c);
response.addHeader(cookieHeader);
}
}
//X-XSS-Protection: 1; mode=block
//X-Frame-Options: SAMEORIGIN
//Expires: Mon, 20 Jun 2016 02:33:52 GMT\r\n
//Cache-Control: private, max-age=31536000\r\n
//Last-Modified: Mon, 02 Apr 2012 02:13:37 GMT\r\n
//X-Content-Type-Options: nosniff\r\n
if(connHeader == null)
return;
else if(!"keep-alive".equals(connHeader.getValue()))
return;
//just re-use the connHeader from the request...
response.addHeader(connHeader);
}
private List<RouterCookie> createCookies(KnownStatusCode statusCode) {
if(!Current.isContextSet())
return new ArrayList<>(); //in some exceptional cases like incoming cookies failing to parse, there will be no cookies
try {
List<RouterCookie> cookies = new ArrayList<>();
cookieTranslator.addScopeToCookieIfExist(cookies, Current.flash());
cookieTranslator.addScopeToCookieIfExist(cookies, Current.validation());
cookieTranslator.addScopeToCookieIfExist(cookies, Current.session());
return cookies;
} catch(CookieTooLargeException e) {
if(statusCode != KnownStatusCode.HTTP_500_INTERNAL_SVR_ERROR)
throw e;
//ELSE this is the second time we are rendering a response AND it was MOST likely caused by the same
//thing when we tried to marshal out cookies to strings and they were too big, sooooooooooo in this
//case, clear the cookie that failed. One log of this should have already occurred but just in case
//add one more log here but not with stack trace(so we don't get the annoying double stack trace on
//failing. (The throws above is logged in catch statement elsewhere)
log.error("Could not marshal cookie on http 500. msg="+e.getMessage());
return new ArrayList<>();
}
}
private Header create(RouterCookie c) {
ResponseCookie cookie = new ResponseCookie();
cookie.setName(c.name);
cookie.setValue(c.value);
cookie.setDomain(c.domain);
cookie.setPath(c.path);
cookie.setMaxAgeSeconds(c.maxAgeSeconds);
cookie.setSecure(c.isSecure);
cookie.setHttpOnly(c.isHttpOnly);
return httpSubParser.createHeader(cookie);
}
public void addDeleteCookie(HttpResponse response, String badCookieName) {
RouterCookie cookie = cookieTranslator.createDeleteCookie(badCookieName);
Header cookieHeader = create(cookie);
response.addHeader(cookieHeader);
}
}