• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

openmrs / openmrs-core / 8603130860

08 Apr 2024 03:57PM UTC coverage: 64.68% (-0.01%) from 64.691%
8603130860

push

github

web-flow
TRUNK-6226: Fix potential zip-slips (#4613)

1 of 5 new or added lines in 2 files covered. (20.0%)

5 existing lines in 3 files now uncovered.

22761 of 35190 relevant lines covered (64.68%)

0.65 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

34.73
/web/src/main/java/org/openmrs/module/web/WebModuleUtil.java
1
/**
2
 * This Source Code Form is subject to the terms of the Mozilla Public License,
3
 * v. 2.0. If a copy of the MPL was not distributed with this file, You can
4
 * obtain one at http://mozilla.org/MPL/2.0/. OpenMRS is also distributed under
5
 * the terms of the Healthcare Disclaimer located at http://openmrs.org/license.
6
 *
7
 * Copyright (C) OpenMRS Inc. OpenMRS is a registered trademark and the OpenMRS
8
 * graphic logo is a trademark of OpenMRS Inc.
9
 */
10
package org.openmrs.module.web;
11

12
import java.io.File;
13
import java.io.FileInputStream;
14
import java.io.FileNotFoundException;
15
import java.io.FileOutputStream;
16
import java.io.IOException;
17
import java.io.InputStream;
18
import java.io.OutputStream;
19
import java.io.StringReader;
20
import java.nio.file.Path;
21
import java.nio.file.Paths;
22
import java.util.ArrayDeque;
23
import java.util.ArrayList;
24
import java.util.Collection;
25
import java.util.Deque;
26
import java.util.Enumeration;
27
import java.util.HashMap;
28
import java.util.Iterator;
29
import java.util.LinkedHashMap;
30
import java.util.List;
31
import java.util.Map;
32
import java.util.Properties;
33
import java.util.concurrent.locks.Lock;
34
import java.util.concurrent.locks.ReentrantLock;
35
import java.util.jar.JarEntry;
36
import java.util.jar.JarFile;
37
import java.util.regex.Pattern;
38

39
import javax.servlet.Filter;
40
import javax.servlet.ServletConfig;
41
import javax.servlet.ServletContext;
42
import javax.servlet.ServletException;
43
import javax.servlet.ServletRequest;
44
import javax.servlet.http.HttpServlet;
45
import javax.servlet.http.HttpServletRequest;
46
import javax.xml.parsers.DocumentBuilder;
47
import javax.xml.parsers.DocumentBuilderFactory;
48
import javax.xml.parsers.ParserConfigurationException;
49
import javax.xml.transform.Transformer;
50
import javax.xml.transform.TransformerException;
51
import javax.xml.transform.TransformerFactory;
52
import javax.xml.transform.dom.DOMSource;
53
import javax.xml.transform.stream.StreamResult;
54

55
import org.openmrs.api.APIException;
56
import org.openmrs.api.context.Context;
57
import org.openmrs.module.Module;
58
import org.openmrs.module.ModuleException;
59
import org.openmrs.module.ModuleFactory;
60
import org.openmrs.module.ModuleUtil;
61
import org.openmrs.module.web.filter.ModuleFilterConfig;
62
import org.openmrs.module.web.filter.ModuleFilterDefinition;
63
import org.openmrs.module.web.filter.ModuleFilterMapping;
64
import org.openmrs.scheduler.SchedulerException;
65
import org.openmrs.scheduler.SchedulerService;
66
import org.openmrs.scheduler.TaskDefinition;
67
import org.openmrs.util.OpenmrsUtil;
68
import org.openmrs.util.PrivilegeConstants;
69
import org.openmrs.web.DispatcherServlet;
70
import org.openmrs.web.StaticDispatcherServlet;
71
import org.slf4j.Logger;
72
import org.slf4j.LoggerFactory;
73
import org.springframework.web.context.support.WebApplicationContextUtils;
74
import org.springframework.web.context.support.XmlWebApplicationContext;
75
import org.w3c.dom.Document;
76
import org.w3c.dom.Element;
77
import org.w3c.dom.NamedNodeMap;
78
import org.w3c.dom.Node;
79
import org.w3c.dom.NodeList;
80
import org.xml.sax.InputSource;
81

82
public class WebModuleUtil {
83

84
        private WebModuleUtil() {
85
        }
86
        
87
        private static final Logger log = LoggerFactory.getLogger(WebModuleUtil.class);
1✔
88
        
89
        private static final Lock SERVLET_LOCK = new ReentrantLock();
1✔
90
        
91
        private static final Lock FILTERS_LOCK = new ReentrantLock();
1✔
92
        
93
        // caches all modules' mapped servlets
94
        private static final Map<String, HttpServlet> MODULE_SERVLETS = new HashMap<>();
1✔
95
        
96
        // caches all modules filters and filter-mappings
97
        private static final Map<Module, Collection<Filter>> MODULE_FILTERS = new HashMap<>();
1✔
98
        
99
        private static final Map<String, Filter> MODULE_FILTERS_BY_NAME = new HashMap<>();
1✔
100
        
101
        private static final Deque<ModuleFilterMapping> MODULE_FILTER_MAPPINGS = new ArrayDeque<>();
1✔
102
        
103
        private static DispatcherServlet dispatcherServlet = null;
1✔
104
        
105
        private static StaticDispatcherServlet staticDispatcherServlet = null;
1✔
106
        
107
        /**
108
         * Performs the webapp specific startup needs for modules Normal startup is done in
109
         * {@link ModuleFactory#startModule(Module)} If delayContextRefresh is true, the spring context
110
         * is not rerun. This will save a lot of time, but it also means that the calling method is
111
         * responsible for restarting the context if necessary (the calling method will also have to
112
         * call {@link #loadServlets(Module, ServletContext)} and
113
         * {@link #loadFilters(Module, ServletContext)}).<br>
114
         * <br>
115
         * If delayContextRefresh is true and this module should have caused a context refresh, a true
116
         * value is returned. Otherwise, false is returned
117
         *
118
         * @param mod Module to start
119
         * @param servletContext the current ServletContext
120
         * @param delayContextRefresh true/false whether or not to do the context refresh
121
         * @return boolean whether or not the spring context need to be refreshed
122
         */
123
        public static boolean startModule(Module mod, ServletContext servletContext, boolean delayContextRefresh) {
124
                
125
                log.debug("trying to start module {}", mod);
1✔
126
                
127
                // only try and start this module if the api started it without a
128
                // problem.
129
                if (ModuleFactory.isModuleStarted(mod) && !mod.hasStartupError()) {
1✔
130
                        
131
                        String realPath = getRealPath(servletContext);
1✔
132
                        
133
                        if (realPath == null) {
1✔
134
                                realPath = System.getProperty("user.dir");
1✔
135
                        }
136
                        
137
                        File webInf = new File(realPath + "/WEB-INF".replace("/", File.separator));
1✔
138
                        if (!webInf.exists()) {
1✔
139
                                webInf.mkdir();
1✔
140
                        }
141
                        
142
                        // flag to tell whether we added any xml/dwr/etc changes that necessitate a refresh
143
                        // of the web application context
144
                        boolean moduleNeedsContextRefresh = false;
1✔
145
                        
146
                        // copy the html files into the webapp (from /web/module/ in the module)
147
                        // also looks for a spring context file. If found, schedules spring to be restarted
148
                        JarFile jarFile = null;
1✔
149
                        OutputStream outStream = null;
1✔
150
                        InputStream inStream = null;
1✔
151
                        try {
152
                                File modFile = mod.getFile();
1✔
153
                                jarFile = new JarFile(modFile);
×
154
                                Enumeration<JarEntry> entries = jarFile.entries();
×
155
                                
156
                                while (entries.hasMoreElements()) {
×
157
                                        JarEntry entry = entries.nextElement();
×
158
                                        String name = entry.getName();
×
NEW
159
                                        if (Paths.get(name).startsWith("..")) {
×
NEW
160
                                                throw new UnsupportedOperationException("Attempted to write file '" + name + "' rejected as it attempts to write outside the chosen directory. This may be the result of a zip-slip style attack.");
×
161
                                        }
162
                                        
NEW
163
                                        log.debug("Entry name: {}", name);
×
UNCOV
164
                                        if (name.startsWith("web/module/")) {
×
165
                                                // trim out the starting path of "web/module/"
166
                                                String filepath = name.substring(11);
×
167
                                                
168
                                                StringBuilder absPath = new StringBuilder(realPath + "/WEB-INF");
×
169
                                                
170
                                                // If this is within the tag file directory, copy it into /WEB-INF/tags/module/moduleId/...
171
                                                if (filepath.startsWith("tags/")) {
×
172
                                                        filepath = filepath.substring(5);
×
173
                                                        absPath.append("/tags/module/");
×
174
                                                }
175
                                                // Otherwise, copy it into /WEB-INF/view/module/moduleId/...
176
                                                else {
177
                                                        absPath.append("/view/module/");
×
178
                                                }
179
                                                
180
                                                // if a module id has a . in it, we should treat that as a /, i.e. files in the module
181
                                                // ui.springmvc should go in folder names like .../ui/springmvc/...
182
                                                absPath.append(mod.getModuleIdAsPath()).append("/").append(filepath);
×
183
                                                log.debug("Moving file from: {} to {}", name, absPath);
×
184
                                                
185
                                                // get the output file
186
                                                File outFile = new File(absPath.toString().replace("/", File.separator));
×
187
                                                if (entry.isDirectory()) {
×
188
                                                        if (!outFile.exists()) {
×
189
                                                                outFile.mkdirs();
×
190
                                                        }
191
                                                } else {
192
                                                        // make the parent directories in case it doesn't exist
193
                                                        File parentDir = outFile.getParentFile();
×
194
                                                        if (!parentDir.exists()) {
×
195
                                                                parentDir.mkdirs();
×
196
                                                        }
197
                                                        
198
                                                        // copy the contents over to the webapp for non directories
199
                                                        outStream = new FileOutputStream(outFile, false);
×
200
                                                        inStream = jarFile.getInputStream(entry);
×
201
                                                        OpenmrsUtil.copyFile(inStream, outStream);
×
202
                                                }
203
                                        } else if ("moduleApplicationContext.xml".equals(name) || "webModuleApplicationContext.xml".equals(name)) {
×
204
                                                moduleNeedsContextRefresh = true;
×
205
                                        } else if (name.equals(mod.getModuleId() + "Context.xml")) {
×
206
                                                String msg = "DEPRECATED: '" + name
×
207
                                                        + "' should be named 'moduleApplicationContext.xml' now. Please update/upgrade. ";
208
                                                throw new ModuleException(msg, mod.getModuleId());
×
209
                                        }
210
                                }
×
211
                        }
212
                        catch (IOException io) {
1✔
213
                                log.warn("Unable to copy files from module " + mod.getModuleId() + " to the web layer", io);
1✔
214
                        }
215
                        finally {
216
                                if (jarFile != null) {
1✔
217
                                        try {
218
                                                jarFile.close();
×
219
                                        }
220
                                        catch (IOException io) {
×
221
                                                log.warn("Couldn't close jar file: " + jarFile.getName(), io);
×
222
                                        }
×
223
                                }
224
                                if (inStream != null) {
1✔
225
                                        try {
226
                                                inStream.close();
×
227
                                        }
228
                                        catch (IOException io) {
×
229
                                                log.warn("Couldn't close InputStream: " + io);
×
230
                                        }
×
231
                                }
232
                                if (outStream != null) {
1✔
233
                                        try {
234
                                                outStream.close();
×
235
                                        }
236
                                        catch (IOException io) {
×
237
                                                log.warn("Couldn't close OutputStream: " + io);
×
238
                                        }
×
239
                                }
240
                        }
241
                        
242
                        // find and add the dwr code to the dwr-modules.xml file (if defined)
243
                        InputStream inputStream = null;
1✔
244
                        try {
245
                                Document config = mod.getConfig();
1✔
246
                                Element root = config.getDocumentElement();
1✔
247
                                if (root.getElementsByTagName("dwr").getLength() > 0) {
1✔
248
                                        
249
                                        // get the dwr-module.xml file that we're appending our code to
250
                                        File f = new File(realPath + "/WEB-INF/dwr-modules.xml".replace("/", File.separator));
1✔
251
                                        
252
                                        // testing if file exists
253
                                        if (!f.exists()) {
1✔
254
                                                // if it does not -> needs to be created
255
                                                createDwrModulesXml(realPath);
1✔
256
                                        }
257
                                        
258
                                        inputStream = new FileInputStream(f);
1✔
259
                                        Document dwrmodulexml = getDWRModuleXML(inputStream, realPath);
1✔
260
                                        Element outputRoot = dwrmodulexml.getDocumentElement();
1✔
261
                                        
262
                                        // loop over all of the children of the "dwr" tag
263
                                        Node node = root.getElementsByTagName("dwr").item(0);
1✔
264
                                        Node current = node.getFirstChild();
1✔
265
                                        
266
                                        while (current != null) {
1✔
267
                                                if ("allow".equals(current.getNodeName()) || "signatures".equals(current.getNodeName())
1✔
268
                                                        || "init".equals(current.getNodeName())) {
1✔
269
                                                        ((Element) current).setAttribute("moduleId", mod.getModuleId());
1✔
270
                                                        outputRoot.appendChild(dwrmodulexml.importNode(current, true));
1✔
271
                                                }
272
                                                
273
                                                current = current.getNextSibling();
1✔
274
                                        }
275
                                        
276
                                        moduleNeedsContextRefresh = true;
1✔
277
                                        
278
                                        // save the dwr-modules.xml file.
279
                                        OpenmrsUtil.saveDocument(dwrmodulexml, f);
1✔
280
                                }
281
                        }
282
                        catch (FileNotFoundException e) {
×
283
                                throw new ModuleException(realPath + "/WEB-INF/dwr-modules.xml file doesn't exist.", e);
×
284
                        }
285
                        finally {
286
                                if (inputStream != null) {
1✔
287
                                        try {
288
                                                inputStream.close();
1✔
289
                                        }
290
                                        catch (IOException io) {
×
291
                                                log.error("Error while closing input stream", io);
×
292
                                        }
1✔
293
                                }
294
                        }
295
                        
296
                        // mark to delete the entire module web directory on exit
297
                        // this will usually only be used when an improper shutdown has occurred.
298
                        String folderPath = realPath + "/WEB-INF/view/module/" + mod.getModuleIdAsPath();
1✔
299
                        File outFile = new File(folderPath.replace("/", File.separator));
1✔
300
                        outFile.deleteOnExit();
1✔
301
                        
302
                        // additional checks on module needing a context refresh
303
                        if (!moduleNeedsContextRefresh && mod.getAdvicePoints() != null && !mod.getAdvicePoints().isEmpty()) {
1✔
304
                                
305
                                // AOP advice points are only loaded during the context refresh now.
306
                                // if the context hasn't been marked to be refreshed yet, mark it
307
                                // now if this module defines some advice
308
                                moduleNeedsContextRefresh = true;
×
309
                                
310
                        }
311
                        
312
                        // refresh the spring web context to get the just-created xml
313
                        // files into it (if we copied an xml file)
314
                        if (moduleNeedsContextRefresh && !delayContextRefresh) {
1✔
315
                                log.debug("Refreshing context for module {}", mod);
×
316
                                
317
                                try {
318
                                        refreshWAC(servletContext, false, mod);
×
319
                                        log.debug("Done Refreshing WAC");
×
320
                                }
321
                                catch (Exception e) {
×
322
                                        String msg = "Unable to refresh the WebApplicationContext";
×
323
                                        mod.setStartupErrorMessage(msg, e);
×
324
                                        
325
                                        if (log.isWarnEnabled()) {
×
326
                                                log.warn(msg + " for module: " + mod.getModuleId(), e);
×
327
                                        }
328
                                        
329
                                        try {
330
                                                stopModule(mod, servletContext, true);
×
331
                                                ModuleFactory.stopModule(mod, true, true); //remove jar from classloader play
×
332
                                        }
333
                                        catch (Exception e2) {
×
334
                                                // exception expected with most modules here
335
                                                if (log.isWarnEnabled()) {
×
336
                                                        log.warn("Error while stopping a module that had an error on refreshWAC", e2);
×
337
                                                }
338
                                        }
×
339
                                        
340
                                        // try starting the application context again
341
                                        refreshWAC(servletContext, false, mod);
×
342
                                        
343
                                        notifySuperUsersAboutModuleFailure(mod);
×
344
                                }
×
345
                                
346
                        }
347
                        
348
                        if (!delayContextRefresh && ModuleFactory.isModuleStarted(mod)) {
1✔
349
                                // only loading the servlets/filters if spring is refreshed because one
350
                                // might depend on files being available in spring
351
                                // if the caller wanted to delay the refresh then they are responsible for
352
                                // calling these two methods on the module
353
                                
354
                                // find and cache the module's servlets
355
                                //(only if the module started successfully previously)
356
                                log.debug("Loading servlets and filters for module {}", mod);
×
357
                                servletContext.setAttribute(OpenmrsJspServlet.OPENMRS_TLD_SCAN_NEEDED, true);
×
358
                                loadServlets(mod, servletContext);
×
359
                                loadFilters(mod, servletContext);
×
360
                        }
361
                        
362
                        // return true if the module needs a context refresh and we didn't do it here
363
                        return (moduleNeedsContextRefresh && delayContextRefresh);
1✔
364
                        
365
                }
366
                
367
                // we aren't processing this module, so a context refresh is not necessary
368
                return false;
×
369
        }
370
        
371
        /** Stops all tasks started by given module
372
         * @param mod
373
         */
374
        private static void stopTasks(Module mod) {
375
                SchedulerService schedulerService;
376
                try {
377
                        schedulerService = Context.getSchedulerService();
×
378
                } catch (NullPointerException | APIException e) {
×
379
                        // if we got here, the scheduler has already been shut down, so there's no work to do
380
                        return;
×
381
                }
×
382
                
383
                String modulePackageName = mod.getPackageName();
×
384
                for (TaskDefinition task : schedulerService.getRegisteredTasks()) {
×
385
                        
386
                        String taskClass = task.getTaskClass();
×
387
                        if (isModulePackageNameInTaskClass(modulePackageName, taskClass)) {
×
388
                                try {
389
                                        schedulerService.shutdownTask(task);
×
390
                                }
391
                                catch (SchedulerException e) {
×
392
                                        log.error("Couldn't stop task:" + task + " for module: " + mod);
×
393
                                }
×
394
                        }
395
                }
×
396
        }
×
397
        
398
        /**
399
         * Checks if module package name is in task class name
400
         * @param modulePackageName the package name of module
401
         * @param taskClass the class of given task
402
         * @return true if task and module are in the same package
403
         * <strong>Should</strong> return false for different package names
404
         * <strong>Should</strong> return false if module has longer package name
405
         * <strong>Should</strong> properly match subpackages
406
         * <strong>Should</strong> return false for empty package names
407
         */
408
        public static boolean isModulePackageNameInTaskClass(String modulePackageName, String taskClass) {
409
                return modulePackageName.length() <= taskClass.length()
1✔
410
                        && taskClass.matches(Pattern.quote(modulePackageName) + "(\\..*)+");
1✔
411
        }
412
        
413
        /**
414
         * Send an Alert to all super users that the given module did not start successfully.
415
         *
416
         * @param mod The Module that failed
417
         */
418
        private static void notifySuperUsersAboutModuleFailure(Module mod) {
419
                try {
420
                        // Add the privileges necessary for notifySuperUsers
421
                        Context.addProxyPrivilege(PrivilegeConstants.MANAGE_ALERTS);
×
422
                        Context.addProxyPrivilege(PrivilegeConstants.GET_USERS);
×
423
                        
424
                        // Send an alert to all administrators
425
                        Context.getAlertService().notifySuperUsers("Module.startupError.notification.message", null, mod.getName());
×
426
                }
427
                finally {
428
                        // Remove added privileges
429
                        Context.removeProxyPrivilege(PrivilegeConstants.GET_USERS);
×
430
                        Context.removeProxyPrivilege(PrivilegeConstants.MANAGE_ALERTS);
×
431
                }
432
        }
×
433
        
434
        /**
435
         * This method will find and cache this module's servlets (so that it doesn't have to look them
436
         * up every time)
437
         *
438
         * @param mod
439
         * @param servletContext the servlet context
440
         */
441
        public static void loadServlets(Module mod, ServletContext servletContext) {
442
                Element rootNode = mod.getConfig().getDocumentElement();
1✔
443
                NodeList servletTags = rootNode.getElementsByTagName("servlet");
1✔
444
                
445
                for (int i = 0; i < servletTags.getLength(); i++) {
1✔
446
                        Node node = servletTags.item(i);
1✔
447
                        NodeList childNodes = node.getChildNodes();
1✔
448
                        String name = "", className = "";
1✔
449

450
                        Map<String, String> initParams = new HashMap<>();
1✔
451
                        for (int j = 0; j < childNodes.getLength(); j++) {
1✔
452
                                Node childNode = childNodes.item(j);
1✔
453
                                if ("servlet-name".equals(childNode.getNodeName())) {
1✔
454
                                        if (childNode.getTextContent() != null) {
1✔
455
                                                name = childNode.getTextContent().trim();
1✔
456
                                        }
457
                                } else if ("servlet-class".equals(childNode.getNodeName()) && childNode.getTextContent() != null) {
1✔
458
                                        className = childNode.getTextContent().trim();
1✔
459
                                } else if ("init-param".equals(childNode.getNodeName())) {
1✔
460
                                        NodeList initParamChildren = childNode.getChildNodes();
1✔
461
                                        String paramName = null, paramValue = null;
1✔
462
                                        for (int k = 0; k < initParamChildren.getLength(); k++) {
1✔
463
                                                Node initParamChild = initParamChildren.item(k);
1✔
464
                                                if ("param-name".equals(initParamChild.getNodeName()) && initParamChild.getTextContent() != null) {
1✔
465
                                                        paramName = initParamChild.getTextContent().trim();
1✔
466
                                                } else if ("param-value".equals(initParamChild.getNodeName()) && initParamChild.getTextContent() != null) {
1✔
467
                                                        paramValue = initParamChild.getTextContent().trim();
1✔
468
                                                }
469
                                        }
470

471
                                        if (paramName != null && paramValue != null) {
1✔
472
                                                initParams.put(paramName, paramValue);
1✔
473
                                        }
474
                                }
475
                        }
476
                        if (name.length() == 0 || className.length() == 0) {
1✔
477
                                log.warn("both 'servlet-name' and 'servlet-class' are required for the 'servlet' tag. Given '" + name
×
478
                                        + "' and '" + className + "' for module " + mod.getName());
×
479
                                continue;
×
480
                        }
481
                        
482
                        HttpServlet httpServlet;
483
                        try {
484
                                httpServlet = (HttpServlet) ModuleFactory.getModuleClassLoader(mod).loadClass(className).newInstance();
1✔
485
                        }
486
                        catch (ClassCastException e) {
×
487
                                log.warn("Class {} from module {} is not a valid HttpServlet", className, mod, e);
×
488
                                continue;
×
489
                        }
490
                        catch (ClassNotFoundException e) {
×
491
                                log.warn("Class {} not found for servlet {} from module {}", className, name, mod, e);
×
492
                                continue;
×
493
                        }
494
                        catch (IllegalAccessException e) {
×
495
                                log.warn("Class {} cannot be accessed for servlet {} from module {}", className, name, mod, e);
×
496
                                continue;
×
497
                        }
498
                        catch (InstantiationException e) {
×
499
                                log.warn("Class {} cannot be instantiated for servlet {} from module {}", className, name, mod, e);
×
500
                                continue;
×
501
                        }
1✔
502
                        
503
                        try {
504
                                log.debug("Initializing {} servlet. - {}.", name, httpServlet);
1✔
505
                                ServletConfig servletConfig = new ModuleServlet.SimpleServletConfig(name, servletContext, initParams);
1✔
506
                                httpServlet.init(servletConfig);
1✔
507
                        }
508
                        catch (Exception e) {
×
509
                                log.warn("Unable to initialize servlet {}", name, e);
×
510
                                throw new ModuleException("Unable to initialize servlet " + name, mod.getModuleId(), e);
×
511
                        }
1✔
512
                        
513
                        // don't allow modules to overwrite servlets of other modules.
514
                        HttpServlet otherServletUsingSameName = MODULE_SERVLETS.get(name);
1✔
515
                        if (otherServletUsingSameName != null) {
1✔
516
                                String otherServletName = otherServletUsingSameName.getClass().getName();
×
517
                                throw new ModuleException("A servlet mapping with name " + name + " is already in use and pointing at: "
×
518
                                        + otherServletName + " from another installed module and this module is trying"
519
                                        + " to use that same name.  Either the module attempting to be installed (" + mod
520
                                        + ") will not work or the other one will not.  Please consult the developers of these two"
521
                                        + " modules to sort this out.");
522
                        }
523
                        
524
                        log.debug("Caching the {} servlet.", name);
1✔
525
                        
526
                        SERVLET_LOCK.lock();
1✔
527
                        try {
528
                                MODULE_SERVLETS.put(name, httpServlet);
1✔
529
                        } finally {
530
                                SERVLET_LOCK.unlock();
1✔
531
                        }
532
                }
533
        }
1✔
534
        
535
        /**
536
         * Remove the servlets defined for this module
537
         *
538
         * @param mod the module that is being stopped that needs its servlets removed
539
         */
540
        public static void unloadServlets(Module mod) {
541
                Element rootNode = mod.getConfig().getDocumentElement();
1✔
542
                NodeList servletTags = rootNode.getElementsByTagName("servlet");
1✔
543
                
544
                for (int i = 0; i < servletTags.getLength(); i++) {
1✔
545
                        Node node = servletTags.item(i);
1✔
546
                        NodeList childNodes = node.getChildNodes();
1✔
547
                        String name;
548
                        for (int j = 0; j < childNodes.getLength(); j++) {
1✔
549
                                Node childNode = childNodes.item(j);
1✔
550
                                if ("servlet-name".equals(childNode.getNodeName()) && childNode.getTextContent() != null) {
1✔
551
                                        name = childNode.getTextContent().trim();
1✔
552
                                        
553
                                        HttpServlet servlet;
554
                                        SERVLET_LOCK.lock();
1✔
555
                                        try {
556
                                                servlet = MODULE_SERVLETS.get(name);
1✔
557
                                        } finally {
558
                                                SERVLET_LOCK.unlock();
1✔
559
                                        }
560
                                        
561
                                        if (servlet != null) {
1✔
562
                                                // shut down the servlet
563
                                                servlet.destroy();
1✔
564
                                        }
565
                                        
566
                                        SERVLET_LOCK.lock();
1✔
567
                                        try {
568
                                                MODULE_SERVLETS.remove(name);
1✔
569
                                        } finally {
570
                                                SERVLET_LOCK.unlock();
1✔
571
                                        }
572
                                }
573
                        }
574
                }
575
        }
1✔
576
        
577
        /**
578
         * This method will initialize and store this module's filters
579
         *
580
         * @param module - The Module to load and register Filters
581
         * @param servletContext - The servletContext within which this method is called
582
         */
583
        public static void loadFilters(Module module, ServletContext servletContext) {
584
                
585
                // Load Filters
586
                Map<String, Filter> filters = new LinkedHashMap<>();
×
587
                
588
                Map<String, Filter> existingFilters;
589
                FILTERS_LOCK.lock();
×
590
                try {
591
                        existingFilters = new HashMap<>(MODULE_FILTERS_BY_NAME);
×
592
                } finally {
593
                        FILTERS_LOCK.unlock();
×
594
                }
595
                
596
                for (ModuleFilterDefinition def : ModuleFilterDefinition.retrieveFilterDefinitions(module)) {
×
597
                        String name = def.getFilterName();
×
598
                        String className = def.getFilterClass();
×
599
                        
600
                        if (existingFilters.containsKey(name)) {
×
601
                                throw new ModuleException("A filter with the name " + name + " is already in use and pointing at: "
×
602
                                        + existingFilters.get(name).getClass().getName()
×
603
                                        + " from another installed module and this module is trying"
604
                                        + " to use that same name.  Either the module attempting to be installed (" + module
605
                                        + ") will not work or the other one will not.  Please consult the developers of these two"
606
                                        + " modules to sort this out.");
607
                        }
608
                        
609
                        ModuleFilterConfig config = ModuleFilterConfig.getInstance(def, servletContext);
×
610
                        
611
                        Filter filter;
612
                        try {
613
                                filter = (Filter) ModuleFactory.getModuleClassLoader(module).loadClass(className).newInstance();
×
614
                        }
615
                        catch (ClassCastException e) {
×
616
                                log.warn("Class {} from module {} is not a valid Filter", className, module, e);
×
617
                                continue;
×
618
                        }
619
                        catch (ClassNotFoundException e) {
×
620
                                log.warn("Class {} not found for servlet {} from module {}", className, name, module, e);
×
621
                                continue;
×
622
                        }
623
                        catch (IllegalAccessException e) {
×
624
                                log.warn("Class {} cannot be accessed for servlet {} from module {}", className, name, module, e);
×
625
                                continue;
×
626
                        }
627
                        catch (InstantiationException e) {
×
628
                                log.warn("Class {} cannot be instantiated for servlet {} from module {}", className, name, module, e);
×
629
                                continue;
×
630
                        }
×
631
                        
632
                        try {
633
                                log.debug("Initializing {} filter. - {}.", name, filter);
×
634
                                filter.init(config);
×
635
                        }
636
                        catch (Exception e) {
×
637
                                log.warn("Unable to initialize servlet {}", name, e);
×
638
                                throw new ModuleException("Unable to initialize servlet " + name, module.getModuleId(), e);
×
639
                        }
×
640
                        
641
                        filters.put(name, filter);
×
642
                }
×
643

644
                FILTERS_LOCK.lock();
×
645
                try {
646
                        MODULE_FILTERS.put(module, filters.values());
×
647
                        MODULE_FILTERS_BY_NAME.putAll(filters);
×
648
                        log.debug("Module {} successfully loaded {} filters.", module, filters.size());
×
649
                        
650
                        // Load Filter Mappings
651
                        Deque<ModuleFilterMapping> modMappings = ModuleFilterMapping.retrieveFilterMappings(module);
×
652
                        
653
                        // IMPORTANT: Filter load order
654
                        // retrieveFilterMappings will return the list of filters in the order they occur in the config.xml file
655
                        // here we add them to the *front* of the filter mappings
656
                        modMappings.descendingIterator().forEachRemaining(MODULE_FILTER_MAPPINGS::addFirst);
×
657
                        
658
                        log.debug("Module {} successfully loaded {} filter mappings.", module, modMappings.size());
×
659
                } finally {
660
                        FILTERS_LOCK.unlock();
×
661
                }
662
        }
×
663
        
664
        /**
665
         * This method will destroy and remove all filters that were registered by the passed
666
         * {@link Module}
667
         *
668
         * @param module - The Module for which you want to remove and destroy filters.
669
         */
670
        public static void unloadFilters(Module module) {
671
                
672
                // Unload Filter Mappings
673
                for (Iterator<ModuleFilterMapping> mapIter = MODULE_FILTER_MAPPINGS.iterator(); mapIter.hasNext();) {
×
674
                        ModuleFilterMapping mapping = mapIter.next();
×
675
                        if (module.equals(mapping.getModule())) {
×
676
                                mapIter.remove();
×
677
                                log.debug("Removed ModuleFilterMapping: " + mapping);
×
678
                        }
679
                }
×
680
                
681
                // unload Filters
682
                Collection<Filter> filters = MODULE_FILTERS.get(module);
×
683
                if (filters != null) {
×
684
                        try {
685
                                for (Filter f : filters) {
×
686
                                        f.destroy();
×
687
                                }
×
688
                        }
689
                        catch (Exception e) {
×
690
                                log.warn("An error occurred while trying to destroy and remove module Filter.", e);
×
691
                        }
×
692
                        
693
                        log.debug("Module: " + module.getModuleId() + " successfully unloaded " + filters.size() + " filters.");
×
694
                        MODULE_FILTERS.remove(module);
×
695

696
                        MODULE_FILTERS_BY_NAME.values().removeIf(filters::contains);
×
697
                }
698
        }
×
699
        
700
        /**
701
         * This method will return all Filters that have been registered a module
702
         *
703
         * @return A Collection of {@link Filter}s that have been registered by a module
704
         */
705
        public static Collection<Filter> getFilters() {
706
                return MODULE_FILTERS_BY_NAME.values();
×
707
        }
708
        
709
        /**
710
         * This method will return all Filter Mappings that have been registered by a module
711
         *
712
         * @return A Collection of all {@link ModuleFilterMapping}s that have been registered by a
713
         *         Module
714
         */
715
        public static Collection<ModuleFilterMapping> getFilterMappings() {
716
                return new ArrayList<>(MODULE_FILTER_MAPPINGS);
×
717
        }
718
        
719
        /**
720
         * Return List of Filters that have been loaded through Modules that have mappings that pass for
721
         * the passed request
722
         *
723
         * @param request - The request to check for matching {@link Filter}s
724
         * @return List of all {@link Filter}s that have filter mappings that match the passed request
725
         */
726
        public static List<Filter> getFiltersForRequest(ServletRequest request) {
727
                
728
                List<Filter> filters = new ArrayList<>();
×
729
                if (request != null) {
×
730
                        HttpServletRequest httpRequest = (HttpServletRequest) request;
×
731
                        String requestPath = httpRequest.getRequestURI();
×
732
                        
733
                        if (requestPath != null) {
×
734
                                if (requestPath.startsWith(httpRequest.getContextPath())) {
×
735
                                        requestPath = requestPath.substring(httpRequest.getContextPath().length());
×
736
                                }
737
                                for (ModuleFilterMapping filterMapping : WebModuleUtil.getFilterMappings()) {
×
738
                                        if (ModuleFilterMapping.filterMappingPasses(filterMapping, requestPath)) {
×
739
                                                Filter passedFilter = MODULE_FILTERS_BY_NAME.get(filterMapping.getFilterName());
×
740
                                                if (passedFilter != null) {
×
741
                                                        filters.add(passedFilter);
×
742
                                                } else {
743
                                                        log.warn("Unable to retrieve filter that has a name of " + filterMapping.getFilterName()
×
744
                                                                + " in filter mapping.");
745
                                                }
746
                                        }
747
                                }
×
748
                        }
749
                }
750
                return filters;
×
751
        }
752
        
753
        /**
754
         * @param inputStream
755
         * @param realPath
756
         * @return
757
         */
758
        private static Document getDWRModuleXML(InputStream inputStream, String realPath) {
759
                Document dwrmodulexml;
760
                try {
761
                        DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
1✔
762
                        DocumentBuilder db = dbf.newDocumentBuilder();
1✔
763

764
                        // When asked to resolve external entities (such as a DTD) we return an InputSource
765
                        // with no data at the end, causing the parser to ignore the DTD.
766
                        db.setEntityResolver((publicId, systemId) -> new InputSource(new StringReader("")));
1✔
767
                        dwrmodulexml = db.parse(inputStream);
1✔
768
                }
769
                catch (Exception e) {
×
770
                        throw new ModuleException("Error parsing dwr-modules.xml file", e);
×
771
                }
1✔
772
                
773
                return dwrmodulexml;
1✔
774
        }
775
        
776
        /**
777
         * Reverses all activities done by startModule(org.openmrs.module.Module) Normal stop/shutdown
778
         * is done by ModuleFactory
779
         */
780
        public static void shutdownModules(ServletContext servletContext) {
781
                
782
                String realPath = getRealPath(servletContext);
×
783
                
784
                // clear the module messages
785
                String messagesPath = realPath + "/WEB-INF/";
×
786
                File folder = new File(messagesPath.replace("/", File.separator));
×
787
                
788
                File[] files = folder.listFiles();
×
789
                if (folder.exists() && files != null) {
×
790
                        Properties emptyProperties = new Properties();
×
791
                        for (File f : files) {
×
792
                                if (f.getName().startsWith("module_messages")) {
×
793
                                        OpenmrsUtil.storeProperties(emptyProperties, f, "");
×
794
                                }
795
                        }
796
                }
797
                
798
                // call web shutdown for each module
799
                for (Module mod : ModuleFactory.getLoadedModules()) {
×
800
                        stopModule(mod, servletContext, true);
×
801
                }
×
802
                
803
        }
×
804
        
805
        /**
806
         * Reverses all visible activities done by startModule(org.openmrs.module.Module)
807
         *
808
         * @param mod
809
         * @param servletContext
810
         */
811
        public static void stopModule(Module mod, ServletContext servletContext) {
812
                stopModule(mod, servletContext, false);
×
813
        }
×
814
        
815
        /**
816
         * Reverses all visible activities done by startModule(org.openmrs.module.Module)
817
         *
818
         * @param mod
819
         * @param servletContext
820
         * @param skipRefresh
821
         */
822
        public static void stopModule(Module mod, ServletContext servletContext, boolean skipRefresh) {
823
                
824
                String moduleId = mod.getModuleId();
×
825
                String modulePackage = mod.getPackageName();
×
826
                
827
                // stop all dependent modules
828
                for (Module dependentModule : ModuleFactory.getStartedModules()) {
×
829
                        if (!dependentModule.equals(mod) && dependentModule.getRequiredModules().contains(modulePackage)) {
×
830
                                stopModule(dependentModule, servletContext, skipRefresh);
×
831
                        }
832
                }
×
833
                
834
                String realPath = getRealPath(servletContext);
×
835
                
836
                // delete the web files from the webapp
837
                String absPath = realPath + "/WEB-INF/view/module/" + moduleId;
×
838
                File moduleWebFolder = new File(absPath.replace("/", File.separator));
×
839
                if (moduleWebFolder.exists()) {
×
840
                        try {
841
                                OpenmrsUtil.deleteDirectory(moduleWebFolder);
×
842
                        }
843
                        catch (IOException io) {
×
844
                                log.warn("Couldn't delete: " + moduleWebFolder.getAbsolutePath(), io);
×
845
                        }
×
846
                }
847
                
848
                // (not) deleting module message properties
849
                
850
                // remove the module's servlets
851
                unloadServlets(mod);
×
852
                
853
                // remove the module's filters and filter mappings
854
                unloadFilters(mod);
×
855
                
856
                // stop all tasks associated with mod
857
                stopTasks(mod);
×
858
                
859
                // remove this module's entries in the dwr xml file
860
                InputStream inputStream = null;
×
861
                try {
862
                        Document config = mod.getConfig();
×
863
                        Element root = config.getDocumentElement();
×
864
                        // if they defined any xml element
865
                        if (root.getElementsByTagName("dwr").getLength() > 0) {
×
866
                                
867
                                // get the dwr-module.xml file that we're appending our code to
868
                                File f = new File(realPath + "/WEB-INF/dwr-modules.xml".replace("/", File.separator));
×
869
                                
870
                                // testing if file exists
871
                                if (!f.exists()) {
×
872
                                        // if it does not -> needs to be created
873
                                        createDwrModulesXml(realPath);
×
874
                                }
875
                                
876
                                inputStream = new FileInputStream(f);
×
877
                                Document dwrmodulexml = getDWRModuleXML(inputStream, realPath);
×
878
                                Element outputRoot = dwrmodulexml.getDocumentElement();
×
879
                                
880
                                // loop over all of the children of the "dwr" tag
881
                                // and remove all "allow" and "signature" tags that have the
882
                                // same moduleId attr as the module being stopped
883
                                NodeList nodeList = outputRoot.getChildNodes();
×
884
                                int i = 0;
×
885
                                while (i < nodeList.getLength()) {
×
886
                                        Node current = nodeList.item(i);
×
887
                                        if ("allow".equals(current.getNodeName()) || "signatures".equals(current.getNodeName())) {
×
888
                                                NamedNodeMap attrs = current.getAttributes();
×
889
                                                Node attr = attrs.getNamedItem("moduleId");
×
890
                                                if (attr != null && moduleId.equals(attr.getNodeValue())) {
×
891
                                                        outputRoot.removeChild(current);
×
892
                                                } else {
893
                                                        i++;
×
894
                                                }
895
                                        } else {
×
896
                                                i++;
×
897
                                        }
898
                                }
×
899
                                
900
                                // save the dwr-modules.xml file.
901
                                OpenmrsUtil.saveDocument(dwrmodulexml, f);
×
902
                        }
903
                }
904
                catch (FileNotFoundException e) {
×
905
                        throw new ModuleException(realPath + "/WEB-INF/dwr-modules.xml file doesn't exist.", e);
×
906
                }
907
                finally {
908
                        if (inputStream != null) {
×
909
                                try {
910
                                        inputStream.close();
×
911
                                }
912
                                catch (IOException io) {
×
913
                                        log.error("Error while closing input stream", io);
×
914
                                }
×
915
                        }
916
                }
917
                
918
                if (!skipRefresh) {        
×
919
                        refreshWAC(servletContext, false, null);
×
920
                }
921
                
922
        }
×
923
        
924
        /**
925
         * Stops, closes, and refreshes the Spring context for the given <code>servletContext</code>
926
         *
927
         * @param servletContext
928
         * @param isOpenmrsStartup if this refresh is being done at application startup
929
         * @param startedModule the module that was just started and waiting on the context refresh
930
         * @return The newly refreshed webApplicationContext
931
         */
932
        public static XmlWebApplicationContext refreshWAC(ServletContext servletContext, boolean isOpenmrsStartup,
933
                Module startedModule) {
934
                XmlWebApplicationContext wac = (XmlWebApplicationContext) WebApplicationContextUtils
×
935
                        .getWebApplicationContext(servletContext);
×
936
                log.debug("Refreshing web application Context of class: {}", wac.getClass().getName());
×
937
                
938
                if (dispatcherServlet != null) {
×
939
                        dispatcherServlet.stopAndCloseApplicationContext();
×
940
                }
941
                
942
                if (staticDispatcherServlet != null) {
×
943
                        staticDispatcherServlet.stopAndCloseApplicationContext();
×
944
                }
945
                
946
                XmlWebApplicationContext newAppContext = (XmlWebApplicationContext) ModuleUtil.refreshApplicationContext(wac,
×
947
                    isOpenmrsStartup, startedModule);
948
                
949
                try {
950
                        // must "refresh" the spring dispatcherservlet as well to add in
951
                        //the new handlerMappings
952
                        if (dispatcherServlet != null) {
×
953
                                dispatcherServlet.reInitFrameworkServlet();
×
954
                        }
955
                        
956
                        if (staticDispatcherServlet != null) {
×
957
                                staticDispatcherServlet.refreshApplicationContext();
×
958
                        }
959
                }
960
                catch (ServletException se) {
×
961
                        log.warn("Caught a servlet exception while refreshing the dispatcher servlet", se);
×
962
                }
×
963
                
964
                return newAppContext;
×
965
        }
966
        
967
        /**
968
         * Save the dispatcher servlet for use later (reinitializing things)
969
         *
970
         * @param ds
971
         */
972
        public static void setDispatcherServlet(DispatcherServlet ds) {
973
                log.debug("Setting dispatcher servlet: " + ds);
1✔
974
                dispatcherServlet = ds;
1✔
975
        }
1✔
976
        
977
        /**
978
         * Save the static content dispatcher servlet for use later when refreshing spring
979
         *
980
         * @param ds
981
         */
982
        public static void setStaticDispatcherServlet(StaticDispatcherServlet ds) {
983
                log.debug("Setting dispatcher servlet for static content: " + ds);
×
984
                staticDispatcherServlet = ds;
×
985
        }
×
986
        
987
        /**
988
         * Finds the servlet defined by the servlet name
989
         *
990
         * @param servletName the name of the servlet out of the path
991
         * @return the current servlet or null if none defined
992
         */
993
        public static HttpServlet getServlet(String servletName) {
994
                return MODULE_SERVLETS.get(servletName);
1✔
995
        }
996
        
997
        /**
998
         * Retrieves a path to a folder that stores web files of a module. <br>
999
         * (path-to-openmrs/WEB-INF/view/module/moduleid)
1000
         *
1001
         * @param moduleId module id (e.g., "basicmodule")
1002
         * @return a path to a folder that stores web files or null if not in a web environment
1003
         * <strong>Should</strong> return the correct module folder
1004
         * <strong>Should</strong> return null if the dispatcher servlet is not yet set
1005
         * <strong>Should</strong> return the correct module folder if real path has a trailing slash
1006
         */
1007
        public static String getModuleWebFolder(String moduleId) {
1008
                if (dispatcherServlet == null) {
1✔
1009
                        throw new ModuleException("Dispatcher servlet must be present in the web environment");
1✔
1010
                }
1011
                
1012
                String moduleFolder = "WEB-INF/view/module/";
1✔
1013
                String realPath = dispatcherServlet.getServletContext().getRealPath("");
1✔
1014
                String moduleWebFolder;
1015
                
1016
                //RealPath may contain '/' on Windows when running tests with the mocked servlet context
1017
                if (realPath.endsWith(File.separator) || realPath.endsWith("/")) {
1✔
1018
                        moduleWebFolder = realPath + moduleFolder;
1✔
1019
                } else {
1020
                        moduleWebFolder = realPath + "/" + moduleFolder;
1✔
1021
                }
1022
                
1023
                moduleWebFolder += moduleId;
1✔
1024
                
1025
                return moduleWebFolder.replace("/", File.separator);
1✔
1026
        }
1027
        
1028
        public static void createDwrModulesXml(String realPath) {
1029
                
1030
                try {
1031
                        
1032
                        DocumentBuilderFactory docFactory = DocumentBuilderFactory.newInstance();
1✔
1033
                        DocumentBuilder docBuilder = docFactory.newDocumentBuilder();
1✔
1034
                        
1035
                        // root elements
1036
                        Document doc = docBuilder.newDocument();
1✔
1037
                        Element rootElement = doc.createElement("dwr");
1✔
1038
                        doc.appendChild(rootElement);
1✔
1039
                        
1040
                        // write the content into xml file
1041
                        TransformerFactory transformerFactory = TransformerFactory.newInstance();
1✔
1042
                        Transformer transformer = transformerFactory.newTransformer();
1✔
1043
                        DOMSource source = new DOMSource(doc);
1✔
1044
                        StreamResult result = new StreamResult(new File(realPath
1✔
1045
                                + "/WEB-INF/dwr-modules.xml".replace("/", File.separator)));
1✔
1046
                        
1047
                        transformer.transform(source, result);
1✔
1048
                        
1049
                }
1050
                catch (ParserConfigurationException pce) {
×
1051
                        log.error("Failed to parse document", pce);
×
1052
                }
1053
                catch (TransformerException tfe) {
×
1054
                        log.error("Failed to transorm xml source", tfe);
×
1055
                }
1✔
1056
        }
1✔
1057

1058
        public static String getRealPath(ServletContext servletContext) {
1059
                return servletContext.getRealPath("");
1✔
1060
        }
1061
        
1062
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc