package org.webpieces.frontend2.impl.translation;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import org.webpieces.data.api.DataWrapper;
import org.webpieces.httpparser.api.common.Header;
import org.webpieces.httpparser.api.common.KnownHeaderName;
import org.webpieces.httpparser.api.dto.HttpChunk;
import org.webpieces.httpparser.api.dto.HttpLastChunk;
import org.webpieces.httpparser.api.dto.HttpPayload;
import org.webpieces.httpparser.api.dto.HttpRequest;
import org.webpieces.httpparser.api.dto.HttpRequestLine;
import org.webpieces.httpparser.api.dto.HttpRequestMethod;
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.HttpUri;
import org.webpieces.httpparser.api.dto.KnownStatusCode;
import org.webpieces.httpparser.api.dto.UrlInfo;
import com.webpieces.hpack.api.dto.Http2HeaderStruct;
import com.webpieces.hpack.api.dto.Http2Headers;
import com.webpieces.http2parser.api.dto.DataFrame;
import com.webpieces.http2parser.api.dto.lib.Http2Header;
import com.webpieces.http2parser.api.dto.lib.Http2HeaderName;
import com.webpieces.http2parser.api.dto.lib.Http2Msg;
import com.webpieces.http2parser.api.dto.lib.PartialStream;
public class Http2Translations {
private static Set<String> headersToSkip = new HashSet<>();
static {
headersToSkip.add(Http2HeaderName.METHOD.getHeaderName());
headersToSkip.add(Http2HeaderName.PATH.getHeaderName());
headersToSkip.add(Http2HeaderName.SCHEME.getHeaderName());
}
public static List<HttpPayload> translate(PartialStream data) {
if(data instanceof DataFrame) {
return translateData((DataFrame)data);
} else if(data instanceof Http2Headers) {
//trailing headers
throw new UnsupportedOperationException("not done yet");
}
throw new UnsupportedOperationException("This frame type is not supported in http1.1="+data);
}
public static Http2Msg translate(HttpPayload payload, boolean isHttps) {
if(payload instanceof HttpRequest)
return requestToHeaders((HttpRequest) payload, isHttps);
else if(payload instanceof HttpLastChunk)
return translateChunk((HttpChunk)payload, true);
else if(payload instanceof HttpChunk)
return translateChunk((HttpChunk)payload, false);
throw new UnsupportedOperationException("not supported yet");
}
private static Http2Msg translateChunk(HttpChunk payload, boolean eos) {
DataFrame frame = new DataFrame();
frame.setData(payload.getBodyNonNull());
frame.setEndOfStream(eos);
return frame;
}
public static Http2Msg responseToHeaders(HttpResponse response, boolean isHttps) {
List<Http2Header> headers = new ArrayList<>();
headers.add(new Http2Header(Http2HeaderName.STATUS, response.getStatusLine().getStatus().getCode().toString()));
String scheme = "http";
if(isHttps)
scheme = "https";
headers.add(new Http2Header(Http2HeaderName.SCHEME, scheme));
for(Header header: response.getHeaders()) {
headers.add(new Http2Header(header.getName(), header.getValue()));
}
Http2Headers resp = new Http2Headers(headers);
resp.setEndOfStream(false);
return resp;
}
private static Http2Headers requestToHeaders(HttpRequest request, boolean fromSslChannel) {
HttpRequestLine requestLine = request.getRequestLine();
List<Header> requestHeaders = request.getHeaders();
LinkedList<Http2Header> headerList = new LinkedList<>();
// add special headers
headerList.add(new Http2Header(":method", requestLine.getMethod().getMethodAsString()));
UrlInfo urlInfo = requestLine.getUri().getUriBreakdown();
headerList.add(new Http2Header(":path", urlInfo.getFullPath()));
// Figure out scheme
if(urlInfo.getPrefix() != null) {
headerList.add(new Http2Header(":scheme", urlInfo.getPrefix()));
} else if(fromSslChannel) {
headerList.add(new Http2Header(":scheme", "https"));
} else {
headerList.add(new Http2Header(":scheme", "http"));
}
// Figure out authority
Header hostHeader = request.getHeaderLookupStruct().getHeader(KnownHeaderName.HOST);
if(hostHeader == null)
throw new IllegalArgumentException("Host header is required in http1.1");
// Add regular headers
for(Header header: requestHeaders) {
if(header.getKnownName() == KnownHeaderName.HOST) {
//keeps headers in order of http1 headers
String h = hostHeader.getValue();
headerList.add(new Http2Header(":authority", h));
continue;
}
headerList.add(new Http2Header(header.getName().toLowerCase(), header.getValue()));
}
Http2Headers headers = new Http2Headers(headerList);
headers.setEndOfStream(false);
return headers;
}
private static List<HttpPayload> translateData(DataFrame data) {
List<HttpPayload> chunks = new ArrayList<>();
HttpChunk chunk = new HttpChunk();
chunk.setBody(data.getData());
chunks.add(chunk);
if(data.isEndOfStream())
chunks.add(new HttpLastChunk());
return chunks;
}
public static HttpResponse translateResponse(Http2Headers headers) {
HttpResponseStatus status = new HttpResponseStatus();
HttpResponseStatusLine statusLine = new HttpResponseStatusLine();
statusLine.setStatus(status);
HttpResponse response = new HttpResponse();
response.setStatusLine(statusLine);
for(Http2Header header : headers.getHeaders()) {
if(header.getKnownName() == Http2HeaderName.STATUS) {
fillStatus(header, status);
} else if(header.getKnownName() == Http2HeaderName.SCHEME) {
//do nothing and drop it
} else {
Header http1Header = convertHeader(header);
response.addHeader(http1Header);
}
}
if(status.getCode() == null)
throw new IllegalArgumentException("The header :status is required to send the response");
return response;
}
private static Header convertHeader(Http2Header header) {
return new Header(header.getName(), header.getValue());
}
private static void fillStatus(Http2Header statusHeader, HttpResponseStatus status) {
int code = Integer.parseInt(statusHeader.getValue());
KnownStatusCode knownStatusCode = KnownStatusCode.lookup(code);
if(knownStatusCode != null) {
status.setKnownStatus(knownStatusCode);
} else {
status.setCode(code);
}
}
public static HttpRequest translateRequest(Http2Headers headers) {
HttpRequestLine requestLine = new HttpRequestLine();
HttpRequest req = new HttpRequest();
req.setRequestLine(requestLine);
for(Http2Header header : headers.getHeaders()) {
insertInfo(req, header);
}
Http2HeaderStruct headerMap = headers.getHeaderLookupStruct();
Http2Header method = headerMap.getHeader(Http2HeaderName.METHOD);
if(method == null)
throw new IllegalArgumentException(Http2HeaderName.METHOD.name()+"is a required header to translate to http1");
req.getRequestLine().setMethod(new HttpRequestMethod(method.getValue()));
Http2Header host = headerMap.getHeader(Http2HeaderName.AUTHORITY);
if(host == null)
throw new IllegalArgumentException(Http2HeaderName.AUTHORITY.name()+"is a required header to translate to http1");
Http2Header path = headerMap.getHeader(Http2HeaderName.PATH);
if(path == null)
throw new IllegalArgumentException(Http2HeaderName.PATH.name()+"is a required header to translate to http1");
HttpUri httpUri = new HttpUri(path.getValue());
req.getRequestLine().setUri(httpUri );
return req;
}
private static void insertInfo(HttpRequest req, Http2Header header) {
if(headersToSkip.contains(header.getName()))
return;
if(header.getKnownName() == Http2HeaderName.AUTHORITY) {
//this keeps header order which is sometimes important
req.addHeader(new Header(KnownHeaderName.HOST, header.getValue()));
return;
}
String name = translateName(header.getName());
req.addHeader(new Header(name, header.getValue()));
}
private static String translateName(String name) {
char[] charArray = name.toCharArray();
boolean previousDash = false;
for(int i = 0; i < charArray.length; i++) {
if(i == 0)
charArray[i] = Character.toUpperCase(charArray[i]);
else if(previousDash)
charArray[i] = Character.toUpperCase(charArray[i]);
if(charArray[i] == '-')
previousDash = true;
else
previousDash = false;
}
return new String(charArray);
}
public static DataFrame translateBody(DataWrapper body) {
DataFrame data = new DataFrame();
data.setData(body);
data.setEndOfStream(true);
return data;
}
}