/**
* RequestHeader
* Copyright 2008 by Michael Peter Christen, mc@yacy.net, Frankfurt a. M., Germany
* First released 22.08.2008 at http://yacy.net
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program in the file lgpl21.txt
* If not, see <http://www.gnu.org/licenses/>.
*/
package net.yacy.cora.protocol;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.MalformedURLException;
import java.security.Principal;
import java.util.Collection;
import java.util.Date;
import java.util.Enumeration;
import java.util.Locale;
import java.util.Map;
import javax.servlet.AsyncContext;
import javax.servlet.DispatcherType;
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletInputStream;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import javax.servlet.http.HttpUpgradeHandler;
import javax.servlet.http.Part;
import net.yacy.cora.document.id.DigestURL;
import net.yacy.cora.document.id.MultiProtocolURL;
import net.yacy.cora.util.NumberTools;
import org.eclipse.jetty.server.CookieCutter;
import org.eclipse.jetty.util.URIUtil;
/**
* YaCy servlet request header.
* YaCy runs in a servlet container (Jetty), starting 2016 this implements the
* widely used HttpServletRequest for tighter and further standardization and
* adherence to common standards, to make the use of HttpServletRequest parameters
* available to YaCy servlets.
*/
public class RequestHeader extends HeaderFramework implements HttpServletRequest {
// request header properties
public static final String CONNECTION = "Connection";
public static final String PROXY_CONNECTION = "Proxy-Connection";
public static final String KEEP_ALIVE = "Keep-Alive";
public static final String AUTHORIZATION = "Authorization";
public static final String WWW_AUTHENTICATE = "WWW-Authenticate";
public static final String PROXY_AUTHORIZATION = "Proxy-Authorization";
public static final String PROXY_AUTHENTICATE = "Proxy-Authenticate";
public static final String UPGRADE = "Upgrade";
public static final String TE = "TE";
public static final String X_CACHE = "X-Cache";
public static final String X_CACHE_LOOKUP = "X-Cache-Lookup";
public static final String COOKIE = "Cookie";
public static final String IF_MODIFIED_SINCE = "If-Modified-Since";
public static final String IF_RANGE = "If-Range";
public static final String REFERER = "Referer"; // a misspelling of referrer that occurs as an HTTP header field. Its defined so in the http protocol, so please don't 'fix' it!
private static final long serialVersionUID = 0L;
public enum FileType {
HTML, JSON, XML
}
private final HttpServletRequest _request; // reference to the original request
private Date date_cache_IfModifiedSince = null;
public RequestHeader() {
super();
this._request = null;
}
public RequestHeader(HttpServletRequest request) {
super();
this._request = request;
}
public DigestURL referer() {
final String referer = get(REFERER);
if (referer == null) return null;
try {
return new DigestURL(referer);
} catch (final MalformedURLException e) {
return null;
}
}
public String refererHost() {
final MultiProtocolURL url = referer();
if (url == null) return null;
return url.getHost();
}
public Date ifModifiedSince() {
if (this.date_cache_IfModifiedSince != null) return date_cache_IfModifiedSince;
long time = this.getDateHeader(RequestHeader.IF_MODIFIED_SINCE);
if (time > 0) this.date_cache_IfModifiedSince = new Date(time);
return this.date_cache_IfModifiedSince;
}
public FileType fileType() {
String path = this.getPathInfo();
if (path == null) return FileType.HTML;
path = path.toLowerCase();
if (path.endsWith(".json")) return FileType.JSON;
if (path.endsWith(".xml")) return FileType.XML;
if (path.endsWith(".rdf")) return FileType.XML;
if (path.endsWith(".rss")) return FileType.XML;
return FileType.HTML;
}
public boolean accessFromLocalhost() {
// authorization for localhost, only if flag is set to grant localhost access as admin
final String clientIP = this.getRemoteAddr();
if ( !Domains.isLocalhost(clientIP) ) {
return false;
}
final String refererHost = this.refererHost();
if (refererHost == null || refererHost.isEmpty() || Domains.isLocalhost(refererHost)) return true;
return false;
}
/**
* Gets the header entry "Cookie" as on string containing all cookies
*
* @return String with cookies separated by ';'
* @see getCookies()
* @deprecated depreceated since 1.92, use getCookies()
*/
@Deprecated
public String getHeaderCookies() {
String cookiestring = this.get(COOKIE); // get from legacy or HttpServletRequest
if (cookiestring == null) {
return "";
} else {
return cookiestring;
}
}
// implementation of HttpServletRequest procedures
// the general approach is to prefer values in the YaCy legacy RequestHeader.map and if no value exists
// to use the httpservletrequest. This approach is used, because legacy requestheader allows to add or
// change header values. This makes sure a modified or added value is used.
// At this point of implementation a original request is not required, so test for _request != null is needed.
/**
* This overrides the legacy get() to make sure the original _request values
* are considered
* @param key header name
* @return value
*/
@Override
public String get(Object key) {
String value = super.get(key); // important to use super.get
if (value == null && _request != null) {
return _request.getHeader((String)key);
}
return value;
}
/**
* Override legacy containsKey to be sure original request headers are incl.
* in the check.
* Use of this legacy methode is discouraged
* @param key headername
* @return
*/
@Override
public boolean containsKey(Object key) {
boolean val = super.containsKey(key);
if (val) {
return val;
} else if (_request != null) {
return _request.getHeader((String) key) != null;
}
return val;
}
/**
* Override legacy mime()
* @return mime string or "application/octet-stream" if content type missing
* @see getContentType()
*/
@Override
public String mime() {
if (super.containsKey(HeaderFramework.CONTENT_TYPE)) {
return super.mime();
} else {
if (_request != null) {
return _request.getContentType();
}
}
return "application/octet-stream";
}
@Override
public String getAuthType() {
if (_request != null) {
return _request.getAuthType();
}
return null; // according to spec return only value if authenticated
}
@Override
public Cookie[] getCookies() {
if (_request != null) {
return _request.getCookies();
} else {
String cstr = super.get(COOKIE);
if (cstr != null) {
CookieCutter cc = new CookieCutter(); // reuse jetty cookie parser
cc.addCookieField(cstr);
return cc.getCookies();
}
return null;
}
}
@Override
public long getDateHeader(String name) {
Date d = super.headerDate(name);
if (d != null) {
return d.getTime();
} else {
if (_request != null) {
return _request.getDateHeader(name);
}
return -1;
}
}
@Override
public String getHeader(String name) {
return this.get(name);
}
@Override
public Enumeration<String> getHeaders(String name) {
if (_request != null) {
return _request.getHeaders(name);
}
return null;
}
@Override
public Enumeration<String> getHeaderNames() {
if (_request != null) {
return _request.getHeaderNames();
}
return null; // not supported in legacy RequestHeader, safe to return null
}
@Override
public int getIntHeader(String name) {
if (super.containsKey(name)) {
String val = super.get(name);
if (val != null) {
try {
return Integer.parseInt(val);
} catch (NumberFormatException ex) {}
}
} else if(_request != null) {
return _request.getIntHeader(name);
}
return -1;
}
@Override
public String getMethod() {
if (_request != null) {
return _request.getMethod();
} else {
return HeaderFramework.METHOD_POST;
}
}
@Override
public String getPathInfo() {
if (super.containsKey(HeaderFramework.CONNECTION_PROP_PATH)) {
return super.get(HeaderFramework.CONNECTION_PROP_PATH);
} else if (_request != null) {
return _request.getPathInfo();
}
return ""; // TODO: in difference to standard return empty string (instead null) as we not always check for null
}
@Override
public String getPathTranslated() {
if (_request != null) {
return _request.getPathTranslated();
} else {
throw new UnsupportedOperationException("Not supported yet.");
}
}
@Override
public String getContextPath() {
if (_request != null) {
return _request.getContextPath();
} else {
return "";
}
}
@Override
public String getQueryString() {
if (_request != null) {
return _request.getQueryString();
} else {
return null;
}
}
@Override
public String getRemoteUser() {
if (_request != null)
return _request.getRemoteUser();
else
return null;
}
@Override
public boolean isUserInRole(String role) {
if (_request != null) {
return _request.isUserInRole(role);
} else {
return false;
}
}
@Override
public Principal getUserPrincipal() {
if (_request != null) {
return _request.getUserPrincipal();
} else {
return null;
}
}
@Override
public String getRequestedSessionId() {
if (_request != null) {
return _request.getRequestedSessionId();
} else {
throw new UnsupportedOperationException("Not supported yet.");
}
}
@Override
public String getRequestURI() {
if (_request != null) {
return _request.getRequestURI();
} else {
return super.get(HeaderFramework.CONNECTION_PROP_PATH, "/"); // TODO: property as header discouraged (but currently used)
}
}
@Override
public StringBuffer getRequestURL() {
if (_request != null) {
return _request.getRequestURL();
} else {
StringBuffer sbuf = new StringBuffer(32);
URIUtil.appendSchemeHostPort(sbuf, this.getScheme(), this.getServerName(), this.getServerPort());
sbuf.append(this.getRequestURI());
return sbuf;
}
}
@Override
public String getServletPath() {
if (_request != null) {
return _request.getServletPath();
} else {
throw new UnsupportedOperationException("Not supported yet.");
}
}
@Override
public HttpSession getSession(boolean create) {
if (_request != null) {
return _request.getSession(create);
} else {
throw new UnsupportedOperationException("Not supported yet.");
}
}
@Override
public HttpSession getSession() {
if (_request != null) {
return _request.getSession();
} else {
throw new UnsupportedOperationException("Not supported yet.");
}
}
@Override
public String changeSessionId() {
if (_request != null) {
return _request.changeSessionId();
} else {
throw new UnsupportedOperationException("Not supported yet.");
}
}
@Override
public boolean isRequestedSessionIdValid() {
if (_request != null) {
return _request.isRequestedSessionIdValid();
} else {
return false;
}
}
@Override
public boolean isRequestedSessionIdFromCookie() {
if (_request != null) {
return _request.isRequestedSessionIdFromCookie();
} else {
return false;
}
}
@Override
public boolean isRequestedSessionIdFromURL() {
if (_request != null) {
return _request.isRequestedSessionIdFromURL();
} else {
return false;
}
}
@Deprecated // As of Version 2.1 of the Java Servlet API, use isRequestedSessionIdFromURL() instead.
@Override
public boolean isRequestedSessionIdFromUrl() {
if (_request != null) {
return _request.isRequestedSessionIdFromUrl();
} else {
return false;
}
}
@Override
public boolean authenticate(HttpServletResponse response) throws IOException, ServletException {
if (_request != null) {
return _request.authenticate(response);
} else {
throw new UnsupportedOperationException("Not supported yet.");
}
}
@Override
public void login(String username, String password) throws ServletException {
if (_request != null) {
_request.login(username, password);
} else {
throw new UnsupportedOperationException("Not supported yet.");
}
}
@Override
public void logout() throws ServletException {
if (_request != null) {
_request.logout();
}
super.remove(AUTHORIZATION);
// TODO: take care of legacy login cookie (and possibly cached UserDB login status)
}
@Override
public Collection<Part> getParts() throws IOException, ServletException {
if (_request != null) {
return _request.getParts();
}
throw new UnsupportedOperationException("Not supported yet.");
}
@Override
public Part getPart(String name) throws IOException, ServletException {
if (_request != null) {
return _request.getPart(name);
}
throw new UnsupportedOperationException("Not supported yet.");
}
@Override
public <T extends HttpUpgradeHandler> T upgrade(Class<T> handlerClass) throws IOException, ServletException {
if (_request != null) {
return _request.upgrade(handlerClass);
}
throw new UnsupportedOperationException("Not supported yet.");
}
@Override
public Object getAttribute(String name) {
if (_request != null) {
return _request.getAttribute(name);
} else {
throw new UnsupportedOperationException("Not supported yet.");
}
}
@Override
public Enumeration<String> getAttributeNames() {
if (_request != null) {
return _request.getAttributeNames();
} else {
throw new UnsupportedOperationException("Not supported yet.");
}
}
@Override
public String getCharacterEncoding() {
String enc = super.getCharacterEncoding();
if (enc == null && _request != null) return _request.getCharacterEncoding();
return enc;
}
@Override
public void setCharacterEncoding(String env) throws UnsupportedEncodingException {
if (_request != null) {
_request.setCharacterEncoding(env);
} else {
// charset part of Content-Type header
// Example: "Content-Type: text/html; charset=ISO-8859-4"
// see https://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.17
//
final String mime = mime();
super.put(CONTENT_TYPE, mime + "; charset=" + env);
}
}
@Override
public int getContentLength() {
int len = super.getContentLength();
if (len < 0 && _request != null) return _request.getContentLength();
return len;
}
@Override
public long getContentLengthLong() {
long len = super.getContentLengthLong();
if (len < 0 && _request != null) return _request.getContentLengthLong();
return len;
}
@Override
public String getContentType() {
if (super.containsKey(HeaderFramework.CONTENT_TYPE)) {
return super.mime();
} else {
if (_request != null) {
return _request.getContentType();
}
}
return null;
}
@Override
public ServletInputStream getInputStream() throws IOException {
if (_request != null) {
return _request.getInputStream();
} else {
throw new UnsupportedOperationException("Not supported yet.");
}
}
@Override
public String getParameter(String name) {
if (_request != null) {
return _request.getParameter(name);
} else {
throw new UnsupportedOperationException("Not supported yet.");
}
}
@Override
public Enumeration<String> getParameterNames() {
if (_request != null) {
return _request.getParameterNames();
} else {
throw new UnsupportedOperationException("Not supported yet.");
}
}
@Override
public String[] getParameterValues(String name) {
if (_request != null) {
return _request.getParameterValues(name);
} else {
throw new UnsupportedOperationException("Not supported yet.");
}
}
@Override
public Map<String, String[]> getParameterMap() {
if (_request != null) {
return _request.getParameterMap();
} else {
throw new UnsupportedOperationException("Not supported yet.");
}
}
@Override
public String getProtocol() {
// here we can directly check original request, as protocol is not expected to be modified
if (_request != null) {
return _request.getProtocol();
} else {
return super.get(HeaderFramework.CONNECTION_PROP_HTTP_VER, HeaderFramework.HTTP_VERSION_1_1);
}
}
@Override
public String getScheme() {
// here we can directly check original request first, as scheme is not expected to be changed
if (_request != null) {
return _request.getScheme();
} else {
if (super.containsKey(HeaderFramework.CONNECTION_PROP_PROTOCOL)) {
return super.get(HeaderFramework.CONNECTION_PROP_PROTOCOL);
} else {
return "http";
}
}
}
@Override
public String getServerName() {
if (super.containsKey(HeaderFramework.HOST)) {
final String hostport = super.get(HeaderFramework.HOST);
if (hostport.contains("[")) { // handle ipv6
final int pos = hostport.lastIndexOf(']');
if (pos > 0) {
return hostport.substring(0, pos + 1);
}
} else if (hostport.contains(":")) {
final int pos = hostport.indexOf(':');
if (pos > 0) {
return hostport.substring(0, pos);
}
}
return hostport;
} else if (_request != null) {
return _request.getServerName();
} else {
return Domains.LOCALHOST;
}
}
@Override
public int getServerPort() {
if (super.containsKey(HeaderFramework.HOST)) {
final String hostport = super.get(HeaderFramework.HOST);
int port = getScheme().equals("https") ? 443 : 80; // init with default ports
final int pos = hostport.lastIndexOf(':');
if (pos > 0 && hostport.lastIndexOf(']') < pos) { // check for ipv6
port = NumberTools.parseIntDecSubstring(hostport, pos + 1);
}
return port;
} else if (_request != null) {
return _request.getServerPort();
} else {
return 80;
}
}
@Override
public BufferedReader getReader() throws IOException {
if (_request != null) {
return _request.getReader();
}
throw new UnsupportedOperationException("Not supported yet.");
}
@Override
public String getRemoteAddr() {
if (this._request != null) {
return _request.getRemoteAddr();
} else {
return super.get(HeaderFramework.CONNECTION_PROP_CLIENTIP);
}
}
@Override
public String getRemoteHost() {
if (_request != null) {
return _request.getRemoteHost();
}
throw new UnsupportedOperationException("Not supported yet.");
}
@Override
public void setAttribute(String name, Object o) {
if (_request != null) {
_request.setAttribute(name, o);
} else {
throw new UnsupportedOperationException("Not supported yet.");
}
}
@Override
public void removeAttribute(String name) {
if (_request != null) {
_request.removeAttribute(name);
} else {
throw new UnsupportedOperationException("Not supported yet.");
}
}
@Override
public Locale getLocale() {
if (this._request != null) {
return _request.getLocale();
} else if (super.containsKey(HeaderFramework.ACCEPT_LANGUAGE)) {
final String lng = super.get(HeaderFramework.ACCEPT_LANGUAGE);
return new Locale(lng);
}
return Locale.getDefault(); // to avoid dependency on Switchboard just use system default
}
@Override
public Enumeration<Locale> getLocales() {
if (this._request != null) {
return _request.getLocales();
} else {
throw new UnsupportedOperationException("Not supported yet.");
}
}
@Override
public boolean isSecure() {
if (_request != null) {
return _request.isSecure();
}
return false;
}
@Override
public RequestDispatcher getRequestDispatcher(String path) {
if (_request != null) {
return _request.getRequestDispatcher(path);
}
throw new UnsupportedOperationException("Not supported yet.");
}
@Override
@Deprecated // Deprecated. As of Version 2.1 of the Java Servlet API, use ServletContext.getRealPath(java.lang.String) instead.
public String getRealPath(String path) {
if (_request != null) {
return _request.getRealPath(path);
}
throw new UnsupportedOperationException("Not supported yet.");
}
@Override
public int getRemotePort() {
if (_request != null) {
return _request.getRemotePort();
}
throw new UnsupportedOperationException("Not supported yet.");
}
@Override
public String getLocalName() {
if (_request != null) {
return _request.getLocalName();
}
throw new UnsupportedOperationException("Not supported yet.");
}
@Override
public String getLocalAddr() {
if (_request != null) {
return _request.getLocalAddr();
}
throw new UnsupportedOperationException("Not supported yet.");
}
@Override
public int getLocalPort() {
if (_request != null) {
return _request.getLocalPort();
}
throw new UnsupportedOperationException("Not supported yet.");
}
@Override
public ServletContext getServletContext() {
if (_request != null) {
return _request.getServletContext();
}
throw new UnsupportedOperationException("Not supported yet.");
}
@Override
public AsyncContext startAsync() throws IllegalStateException {
if (_request != null) {
return _request.startAsync();
}
throw new UnsupportedOperationException("Not supported yet.");
}
@Override
public AsyncContext startAsync(ServletRequest servletRequest, ServletResponse servletResponse) throws IllegalStateException {
if (_request != null) {
startAsync(servletRequest, servletResponse);
}
throw new UnsupportedOperationException("Not supported yet.");
}
@Override
public boolean isAsyncStarted() {
if (_request != null) {
return _request.isAsyncStarted();
}
return false;
}
@Override
public boolean isAsyncSupported() {
if (_request != null) {
return _request.isAsyncStarted();
}
return false;
}
@Override
public AsyncContext getAsyncContext() {
if (_request != null) {
return _request.getAsyncContext();
}
return null;
}
@Override
public DispatcherType getDispatcherType() {
if (_request != null) {
return _request.getDispatcherType();
}
return null;
}
}