1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.mycore.frontend;
20
21 import java.io.File;
22 import java.net.InetAddress;
23 import java.net.MalformedURLException;
24 import java.net.URI;
25 import java.net.URL;
26 import java.net.UnknownHostException;
27 import java.util.Date;
28 import java.util.Enumeration;
29 import java.util.Map;
30 import java.util.Objects;
31 import java.util.Optional;
32 import java.util.Set;
33 import java.util.StringTokenizer;
34 import java.util.TreeSet;
35 import java.util.function.Supplier;
36 import java.util.stream.Collectors;
37 import java.util.stream.Stream;
38
39 import org.apache.logging.log4j.LogManager;
40 import org.apache.logging.log4j.Logger;
41 import org.mycore.common.MCRException;
42 import org.mycore.common.MCRSession;
43 import org.mycore.common.MCRSessionMgr;
44 import org.mycore.common.config.MCRConfiguration2;
45 import org.mycore.common.config.MCRConfigurationException;
46 import org.mycore.frontend.servlets.MCRServletJob;
47 import org.mycore.services.i18n.MCRTranslation;
48
49 import jakarta.servlet.ServletContext;
50 import jakarta.servlet.ServletRequest;
51 import jakarta.servlet.http.HttpServletRequest;
52 import jakarta.servlet.http.HttpServletResponse;
53
54
55
56
57 public class MCRFrontendUtil {
58
59 private static final String PROXY_HEADER_HOST = "X-Forwarded-Host";
60
61 private static final String PROXY_HEADER_SCHEME = "X-Forwarded-Proto";
62
63 private static final String PROXY_HEADER_PORT = "X-Forwarded-Port";
64
65 private static final String PROXY_HEADER_PATH = "X-Forwarded-Path";
66
67 private static final String PROXY_HEADER_REMOTE_IP = "X-Forwarded-For";
68
69 public static final String BASE_URL_ATTRIBUTE = "org.mycore.base.url";
70
71 public static final String SESSION_NETMASK_IPV4_STRING = MCRConfiguration2
72 .getString("MCR.Servlet.Session.NetMask.IPv4").orElse("255.255.255.255");
73
74 public static final String SESSION_NETMASK_IPV6_STRING = MCRConfiguration2
75 .getString("MCR.Servlet.Session.NetMask.IPv6").orElse("FFFF:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF");
76
77 private static String BASE_URL;
78
79 private static String BASE_HOST_IP;
80
81 private static Logger LOGGER = LogManager.getLogger();
82
83 public static byte[] SESSION_NETMASK_IPV4;
84
85 public static byte[] SESSION_NETMASK_IPV6;
86
87 private static final ThreadLocal<Map.Entry<String, MCRServletJob>> CURRENT_SERVLET_JOB = new ThreadLocal<>();
88
89 static {
90 try {
91 SESSION_NETMASK_IPV4 = InetAddress.getByName(MCRFrontendUtil.SESSION_NETMASK_IPV4_STRING).getAddress();
92 } catch (UnknownHostException e) {
93 throw new MCRConfigurationException("MCR.Servlet.Session.NetMask.IPv4 is not a correct IPv4 network mask.",
94 e);
95 }
96 try {
97 SESSION_NETMASK_IPV6 = InetAddress.getByName(MCRFrontendUtil.SESSION_NETMASK_IPV6_STRING).getAddress();
98 } catch (UnknownHostException e) {
99 throw new MCRConfigurationException("MCR.Servlet.Session.NetMask.IPv6 is not a correct IPv6 network mask.",
100 e);
101 }
102 prepareBaseURLs("");
103 addSessionListener();
104 }
105
106
107 protected static final Set<String> TRUSTED_PROXIES = getTrustedProxies();
108
109
110 public static String getBaseURL() {
111 if (MCRSessionMgr.hasCurrentSession()) {
112 MCRSession session = MCRSessionMgr.getCurrentSession();
113 Object value = session.get(BASE_URL_ATTRIBUTE);
114 if (value != null) {
115 LOGGER.debug("Returning BaseURL {} from user session.", value);
116 return value.toString();
117 }
118 }
119 return BASE_URL;
120 }
121
122 public static String getHostIP() {
123 return BASE_HOST_IP;
124 }
125
126
127
128
129
130 public static String getBaseURL(ServletRequest req) {
131 HttpServletRequest request = (HttpServletRequest) req;
132 String scheme = req.getScheme();
133 String host = req.getServerName();
134 int serverPort = req.getServerPort();
135 String path = request.getContextPath() + "/";
136
137 if (TRUSTED_PROXIES.contains(req.getRemoteAddr())) {
138 scheme = Optional.ofNullable(request.getHeader(PROXY_HEADER_SCHEME)).orElse(scheme);
139 host = Optional.ofNullable(request.getHeader(PROXY_HEADER_HOST)).orElse(host);
140 serverPort = Optional.ofNullable(request.getHeader(PROXY_HEADER_PORT))
141 .map(Integer::parseInt)
142 .orElse(serverPort);
143 path = Optional.ofNullable(request.getHeader(PROXY_HEADER_PATH)).orElse(path);
144 if (!path.endsWith("/")) {
145 path += "/";
146 }
147 }
148 StringBuilder webappBase = new StringBuilder(scheme);
149 webappBase.append("://");
150 webappBase.append(host);
151 if (!("http".equals(scheme) && serverPort == 80 || "https".equals(scheme) && serverPort == 443)) {
152 webappBase.append(':').append(serverPort);
153 }
154 webappBase.append(path);
155 return webappBase.toString();
156 }
157
158 public static synchronized void prepareBaseURLs(String baseURL) {
159 BASE_URL = MCRConfiguration2.getString("MCR.baseurl").orElse(baseURL);
160 if (!BASE_URL.endsWith("/")) {
161 BASE_URL = BASE_URL + "/";
162 }
163 try {
164 URL url = new URL(BASE_URL);
165 InetAddress baseHost = InetAddress.getByName(url.getHost());
166 BASE_HOST_IP = baseHost.getHostAddress();
167 } catch (MalformedURLException e) {
168 LOGGER.error("Can't create URL from String {}", BASE_URL);
169 } catch (UnknownHostException e) {
170 LOGGER.error("Can't find host IP for URL {}", BASE_URL);
171 }
172 }
173
174 public static void configureSession(MCRSession session, HttpServletRequest request, HttpServletResponse response) {
175 final MCRServletJob servletJob = new MCRServletJob(request, response);
176 setAsCurrent(session, servletJob);
177
178 getProperty(request, "lang")
179 .filter(MCRTranslation.getAvailableLanguages()::contains)
180 .ifPresent(session::setCurrentLanguage);
181
182
183 if (session.getCurrentIP().length() == 0) {
184 session.setCurrentIP(getRemoteAddr(request));
185 }
186
187
188 if (request.getAttribute(BASE_URL_ATTRIBUTE) != null) {
189 session.put(BASE_URL_ATTRIBUTE, request.getAttribute(BASE_URL_ATTRIBUTE));
190 }
191
192
193 putParamsToSession(request);
194 }
195
196
197
198
199
200
201
202
203 public static Optional<String> getProperty(HttpServletRequest request, String name) {
204 return Stream.<Supplier<Object>>of(
205 () -> request.getAttribute(name),
206 () -> request.getParameter(name))
207 .map(Supplier::get)
208 .filter(Objects::nonNull)
209 .map(Object::toString)
210 .map(String::trim)
211 .filter(s -> !s.isEmpty())
212 .findFirst();
213 }
214
215
216
217
218
219
220
221 public static String getRemoteAddr(HttpServletRequest req) {
222 String remoteAddress = req.getRemoteAddr();
223 if (TRUSTED_PROXIES.contains(remoteAddress)) {
224 String xff = getXForwardedFor(req);
225 if (xff != null) {
226 remoteAddress = xff;
227 }
228 }
229 return remoteAddress;
230 }
231
232
233
234
235
236
237
238 private static void setAsCurrent(MCRSession session, MCRServletJob job) throws IllegalStateException {
239 session.setFirstURI(() -> URI.create(job.getRequest().getRequestURI()));
240 CURRENT_SERVLET_JOB.set(Map.entry(session.getID(), job));
241 }
242
243
244
245
246
247
248 public static Optional<MCRServletJob> getCurrentServletJob() {
249 final Map.Entry<String, MCRServletJob> servletJob = CURRENT_SERVLET_JOB.get();
250 final Optional<MCRServletJob> rv = Optional.ofNullable(servletJob)
251 .filter(job -> MCRSessionMgr.hasCurrentSession())
252 .filter(job -> MCRSessionMgr.getCurrentSession().getID().equals(job.getKey()))
253 .map(Map.Entry::getValue);
254 if (rv.isEmpty()) {
255 CURRENT_SERVLET_JOB.remove();
256 }
257 return rv;
258 }
259
260
261
262
263 private static String getXForwardedFor(HttpServletRequest req) {
264 String xff = req.getHeader(PROXY_HEADER_REMOTE_IP);
265 if ((xff == null) || xff.trim().isEmpty()) {
266 xff = req.getHeader(PROXY_HEADER_REMOTE_IP);
267 }
268 if ((xff == null) || xff.trim().isEmpty()) {
269 return null;
270 }
271
272
273
274
275
276 LOGGER.debug("{} complete: {}", PROXY_HEADER_REMOTE_IP, xff);
277 StringTokenizer st = new StringTokenizer(xff, " ,;");
278 while (st.hasMoreTokens()) {
279 xff = st.nextToken().trim();
280 }
281 LOGGER.debug("{} last: {}", PROXY_HEADER_REMOTE_IP, xff);
282 return xff;
283 }
284
285 private static void putParamsToSession(HttpServletRequest request) {
286 MCRSession mcrSession = MCRSessionMgr.getCurrentSession();
287
288 for (Enumeration<String> e = request.getParameterNames(); e.hasMoreElements();) {
289 String name = e.nextElement();
290 if (name.startsWith("XSL.") && name.endsWith(".SESSION")) {
291 String key = name.substring(0, name.length() - 8);
292
293 if (!request.getParameter(name).trim().equals("")) {
294 mcrSession.put(key, request.getParameter(name));
295 LOGGER.debug("Found HTTP-Req.-Parameter {}={} that should be saved in session, safed {}={}", name,
296 request.getParameter(name), key, request.getParameter(name));
297 } else {
298
299
300 if (mcrSession.get(key) != null) {
301 mcrSession.deleteObject(key);
302 }
303 }
304 }
305 }
306 for (Enumeration<String> e = request.getAttributeNames(); e.hasMoreElements();) {
307 String name = e.nextElement();
308 if (name.startsWith("XSL.") && name.endsWith(".SESSION")) {
309 String key = name.substring(0, name.length() - 8);
310
311 if (!request.getAttribute(name).toString().trim().equals("")) {
312 mcrSession.put(key, request.getAttribute(name));
313 LOGGER.debug("Found HTTP-Req.-Attribute {}={} that should be saved in session, safed {}={}", name,
314 request.getParameter(name), key, request.getParameter(name));
315 } else {
316
317
318 if (mcrSession.get(key) != null) {
319 mcrSession.deleteObject(key);
320 }
321 }
322 }
323 }
324 }
325
326
327
328
329
330
331
332 private static TreeSet<String> getTrustedProxies() {
333
334 return Stream
335 .concat(Stream.of("localhost", URI.create(getBaseURL()).getHost()), MCRConfiguration2
336 .getString("MCR.Request.TrustedProxies").map(MCRConfiguration2::splitValue).orElse(Stream.empty()))
337 .distinct()
338 .peek(proxy -> LOGGER.debug("Trusted proxy: {}", proxy))
339 .map(host -> {
340 try {
341 return InetAddress.getAllByName(host);
342 } catch (UnknownHostException e) {
343 LOGGER.warn("Unknown host: {}", host);
344 return null;
345 }
346 }).filter(Objects::nonNull)
347 .flatMap(Stream::of)
348 .map(InetAddress::getHostAddress)
349 .collect(Collectors.toCollection(TreeSet::new));
350 }
351
352
353
354
355
356
357
358
359
360
361 public static void writeCacheHeaders(HttpServletResponse response, long cacheTime, long lastModified,
362 boolean useExpire) {
363 response.setHeader("Cache-Control", "public, max-age=" + cacheTime);
364 response.setDateHeader("Last-Modified", lastModified);
365 if (useExpire) {
366 Date expires = new Date(System.currentTimeMillis() + cacheTime * 1000);
367 LOGGER.debug("Last-Modified: {}, expire on: {}", new Date(lastModified), expires);
368 response.setDateHeader("Expires", expires.getTime());
369 }
370 }
371
372 public static Optional<File> getWebAppBaseDir(ServletContext ctx) {
373 return Optional.ofNullable(ctx.getRealPath("/")).map(File::new);
374 }
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392 public static boolean isIPAddrAllowed(String lastIP, String newIP) throws UnknownHostException {
393 InetAddress lastIPAddress = InetAddress.getByName(lastIP);
394 InetAddress newIPAddress = InetAddress.getByName(newIP);
395 byte[] lastIPMask = decideNetmask(lastIPAddress);
396 byte[] newIPMask = decideNetmask(newIPAddress);
397 lastIPAddress = InetAddress.getByAddress(filterIPByNetmask(lastIPAddress.getAddress(), lastIPMask));
398 newIPAddress = InetAddress.getByAddress(filterIPByNetmask(newIPAddress.getAddress(), newIPMask));
399 if (lastIPAddress.equals(newIPAddress)) {
400 return true;
401 }
402 String hostIP = getHostIP();
403 InetAddress hostIPAddress = InetAddress.getByName(hostIP);
404 byte[] hostIPMask = decideNetmask(hostIPAddress);
405 hostIPAddress = InetAddress.getByAddress(filterIPByNetmask(hostIPAddress.getAddress(), hostIPMask));
406 return newIPAddress.equals(hostIPAddress);
407 }
408
409 private static byte[] filterIPByNetmask(final byte[] ip, final byte[] mask) {
410 for (int i = 0; i < ip.length; i++) {
411 ip[i] = (byte) (ip[i] & mask[i]);
412 }
413 return ip;
414 }
415
416 private static byte[] decideNetmask(InetAddress ip) throws MCRException {
417 if (hasIPVersion(ip, 4)) {
418 return SESSION_NETMASK_IPV4;
419 } else if (hasIPVersion(ip, 6)) {
420 return SESSION_NETMASK_IPV6;
421 } else {
422 throw new MCRException("Unknown or unidentifiable version of ip: " + ip);
423 }
424 }
425
426 private static Boolean hasIPVersion(InetAddress ip, int version) {
427 int byteLength;
428 switch (version) {
429 case 4:
430 byteLength = 4;
431 break;
432 case 6:
433 byteLength = 16;
434 break;
435 default:
436 throw new IndexOutOfBoundsException("Unknown ip version: " + version);
437 }
438 return ip.getAddress().length == byteLength;
439 }
440
441 private static void addSessionListener() {
442 MCRSessionMgr.addSessionListener(event -> {
443 switch (event.getType()) {
444 case passivated:
445 case destroyed:
446 CURRENT_SERVLET_JOB.remove();
447 break;
448 default:
449 break;
450 }
451 });
452 }
453
454 }