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

evolvedbinary / elemental / 982

29 Apr 2025 08:34PM UTC coverage: 56.409% (+0.007%) from 56.402%
982

push

circleci

adamretter
[feature] Improve README.md badges

28451 of 55847 branches covered (50.94%)

Branch coverage included in aggregate %.

77468 of 131924 relevant lines covered (58.72%)

0.59 hits per line

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

74.7
/exist-core/src/main/java/org/exist/http/urlrewrite/RewriteConfig.java
1
/*
2
 * Elemental
3
 * Copyright (C) 2024, Evolved Binary Ltd
4
 *
5
 * admin@evolvedbinary.com
6
 * https://www.evolvedbinary.com | https://www.elemental.xyz
7
 *
8
 * Use of this software is governed by the Business Source License 1.1
9
 * included in the LICENSE file and at www.mariadb.com/bsl11.
10
 *
11
 * Change Date: 2028-04-27
12
 *
13
 * On the date above, in accordance with the Business Source License, use
14
 * of this software will be governed by the Apache License, Version 2.0.
15
 *
16
 * Additional Use Grant: Production use of the Licensed Work for a permitted
17
 * purpose. A Permitted Purpose is any purpose other than a Competing Use.
18
 * A Competing Use means making the Software available to others in a commercial
19
 * product or service that: substitutes for the Software; substitutes for any
20
 * other product or service we offer using the Software that exists as of the
21
 * date we make the Software available; or offers the same or substantially
22
 * similar functionality as the Software.
23
 *
24
 * NOTE: Parts of this file contain code from 'The eXist-db Authors'.
25
 *       The original license header is included below.
26
 *
27
 * =====================================================================
28
 *
29
 * eXist-db Open Source Native XML Database
30
 * Copyright (C) 2001 The eXist-db Authors
31
 *
32
 * info@exist-db.org
33
 * http://www.exist-db.org
34
 *
35
 * This library is free software; you can redistribute it and/or
36
 * modify it under the terms of the GNU Lesser General Public
37
 * License as published by the Free Software Foundation; either
38
 * version 2.1 of the License, or (at your option) any later version.
39
 *
40
 * This library is distributed in the hope that it will be useful,
41
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
42
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
43
 * Lesser General Public License for more details.
44
 *
45
 * You should have received a copy of the GNU Lesser General Public
46
 * License along with this library; if not, write to the Free Software
47
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
48
 */
49
package org.exist.http.urlrewrite;
50

51
import org.apache.logging.log4j.LogManager;
52
import org.apache.logging.log4j.Logger;
53
import org.exist.EXistException;
54
import org.exist.Namespaces;
55
import org.exist.dom.memtree.SAXAdapter;
56
import org.exist.dom.persistent.DocumentImpl;
57
import org.exist.dom.persistent.LockedDocument;
58
import org.exist.security.PermissionDeniedException;
59
import org.exist.storage.DBBroker;
60
import org.exist.storage.lock.Lock.LockMode;
61
import org.exist.thirdparty.net.sf.saxon.functions.regex.JDK15RegexTranslator;
62
import org.exist.thirdparty.net.sf.saxon.functions.regex.RegexSyntaxException;
63
import org.exist.thirdparty.net.sf.saxon.functions.regex.RegularExpression;
64
import org.exist.util.XMLReaderPool;
65
import org.exist.xmldb.XmldbURI;
66
import org.exist.xquery.Constants;
67
import org.exist.xquery.Expression;
68
import org.w3c.dom.Document;
69
import org.w3c.dom.Element;
70
import org.w3c.dom.Node;
71
import org.xml.sax.InputSource;
72
import org.xml.sax.SAXException;
73
import org.xml.sax.XMLReader;
74

75
import jakarta.servlet.ServletConfig;
76
import jakarta.servlet.ServletException;
77
import jakarta.servlet.http.HttpServletRequest;
78
import javax.xml.parsers.ParserConfigurationException;
79
import java.io.BufferedInputStream;
80
import java.io.IOException;
81
import java.io.InputStream;
82
import java.nio.file.Files;
83
import java.nio.file.Path;
84
import java.nio.file.Paths;
85
import java.util.ArrayList;
86
import java.util.List;
87
import java.util.Optional;
88
import java.util.regex.Matcher;
89
import java.util.regex.Pattern;
90

91
/**
92
 * Handles static mapping configuration for the @link XQueryURLRewrite filter,
93
 * defined in controller-config.xml. The static mapping is used to map
94
 * base paths to base controllers or servlets.
95
 */
96
public class RewriteConfig {
97
    private static final Logger LOG = LogManager.getLogger(RewriteConfig.class);
1✔
98

99
    public static final String CONFIG_FILE = "controller-config.xml";
100
    public static final String PATTERN_ATTRIBUTE = "pattern";
101
    /**
102
     * Adding server-name="www.example.com" to a root tag in the controller-config.xml file.
103
     *
104
     * i.e.
105
     *
106
     * <root server-name="example1.com" pattern="/*" path="xmldb:exist:///db/org/example1/"/>
107
     * <root server-name="example2.com" pattern="/*" path="xmldb:exist:///db/org/example2/"/>
108
     *
109
     * Will redirect http://example1.com to /db/org/example1/
110
     * and http://example2.com to /db/org/example2/
111
     *
112
     * If there is no server-name attribute on the root tag, then the server name is ignored while performing the URL rewriting.
113
     */
114
    public static final String SERVER_NAME_ATTRIBUTE = "server-name";
1✔
115

116
    // the list of established mappings
117
    private final List<Mapping> mappings = new ArrayList<>();
1✔
118

119
    // parent XQueryURLRewrite
120
    private final XQueryURLRewrite urlRewrite;
121

122
    public RewriteConfig(final XQueryURLRewrite urlRewrite) throws ServletException {
1✔
123
        this.urlRewrite = urlRewrite;
1✔
124
        String controllerConfig = urlRewrite.getConfig().getInitParameter("config");
1✔
125
        if (controllerConfig == null) {
1!
126
            controllerConfig = CONFIG_FILE;
×
127
        }
128

129
        configure(controllerConfig);
1✔
130
    }
1✔
131

132
    /**
133
     * Lookup the given path in the static mappings table.
134
     *
135
     * @param request use the path from this request
136
     * @return the URLRewrite instance for the mapping or null if none was found
137
     */
138
    public synchronized URLRewrite lookup(final HttpServletRequest request) {
139
        final String path = request.getRequestURI().substring(request.getContextPath().length());
1✔
140
        return lookup(path, request.getServerName(), false, null);
1✔
141
    }
142

143
    /**
144
     * Lookup the given path in the static mappings table.
145
     *
146
     * @param path the path to look up
147
     * @param serverName the servers name
148
     * @param staticMapping don't return redirects to other controllers, just static mappings to servlets.
149
     * @param copyFrom the urlrewrite rule to copy from or null
150
     *
151
     * @return the URLRewrite instance for the mapping or null if none was found
152
     */
153
    public synchronized URLRewrite lookup(String path, final String serverName, final boolean staticMapping, final URLRewrite copyFrom) {
154
        final int p = path.lastIndexOf(';');
1✔
155
        if (p != Constants.STRING_NOT_FOUND) {
1✔
156
            path = path.substring(0, p);
1✔
157
        }
158
        for (final Mapping mapping : mappings) {
1!
159
            final String matchedString = mapping.match(path);
1✔
160
            if (matchedString != null) {
1✔
161
                final URLRewrite action = mapping.action.copy();
1✔
162
                if (copyFrom != null) {
1✔
163
                    action.copyFrom(copyFrom);
1✔
164
                }
165

166
                /*
167
                 * If the URLRewrite is a ControllerForward, then test to see if there is a condition
168
                 * on the server name.  If there is a condition on the server name and the names do not
169
                 * match, then ignore this ControllerForward.
170
                 */
171
                if (action instanceof ControllerForward) {
1✔
172
                    if (serverName != null) {
1!
173
                        final String controllerServerName = ((ControllerForward) action).getServerName();
1✔
174
                        if (controllerServerName != null) {
1!
175
                            if (!serverName.equalsIgnoreCase(controllerServerName)) {
×
176
                                continue;
×
177
                            }
178
                        }
179
                    }
180

181
                }
182
                // if the mapping matches a part of the URI only, set the prefix to the
183
                // matched string. This will later be stripped from the URI.
184
                if (matchedString.length() != path.length() && !"/".equals(matchedString)) {
1!
185
                    action.setPrefix(matchedString);
1✔
186
                }
187
                action.setURI(path);
1✔
188
                if (!staticMapping || !(action instanceof ControllerForward)) {
1!
189
                    return action;
1✔
190
                }
191
            }
192
        }
193
        return null;
×
194
    }
195

196
    private void configure(final String controllerConfig) throws ServletException {
197
        if (LOG.isDebugEnabled()) {
1!
198
            LOG.debug("Loading XQueryURLRewrite configuration from {}", controllerConfig);
×
199
        }
200

201
        if (controllerConfig.startsWith(XmldbURI.XMLDB_URI_PREFIX)) {
1!
202

203
            try (final DBBroker broker = urlRewrite.getBrokerPool().get(Optional.ofNullable(urlRewrite.getDefaultUser()))) {
×
204
                try (final LockedDocument lockedDocument = broker.getXMLResource(XmldbURI.create(controllerConfig), LockMode.READ_LOCK)) {
×
205
                    final DocumentImpl doc = lockedDocument == null ? null : lockedDocument.getDocument();
×
206
                    if (doc != null) {
×
207
                        parse(doc);
×
208
                    }
209
                }
210
            } catch (final EXistException | PermissionDeniedException e) {
×
211
                throw new ServletException("Failed to parse controller.xml: " + e.getMessage(), e);
×
212
            }
213
        } else {
214
            try {
215
                final Path d = Paths.get(urlRewrite.getConfig().getServletContext().getRealPath("/")).normalize();
1✔
216
                final Path configFile = d.resolve(controllerConfig);
1✔
217
                if (Files.isReadable(configFile)) {
1!
218
                    final Document doc = parseConfig(configFile);
1✔
219
                    parse(doc);
1✔
220
                }
221
            } catch (final ParserConfigurationException | IOException | SAXException e) {
1✔
222
                throw new ServletException("Failed to parse controller.xml: " + e.getMessage(), e);
×
223
            }
224
        }
225
        urlRewrite.clearCaches();
1✔
226
    }
1✔
227

228
    private void parse(final Document doc) throws ServletException {
229
        final Element root = doc.getDocumentElement();
1✔
230
        Node child = root.getFirstChild();
1✔
231
        while (child != null) {
1✔
232
            final String ns = child.getNamespaceURI();
1✔
233
            if (child.getNodeType() == Node.ELEMENT_NODE && Namespaces.EXIST_NS.equals(ns)) {
1!
234
                final Element elem = (Element) child;
1✔
235
                final String pattern = elem.getAttribute(PATTERN_ATTRIBUTE);
1✔
236
                if (pattern.isEmpty()) {
1!
237
                    throw new ServletException("Action in controller-config.xml has no pattern: " + elem.toString());
×
238
                }
239
                final URLRewrite urw = parseAction(urlRewrite.getConfig(), pattern, elem);
1✔
240
                if (urw == null) {
1!
241
                    throw new ServletException("Unknown action in controller-config.xml: " + elem.getNodeName());
×
242
                }
243
                mappings.add(new Mapping(pattern, urw));
1✔
244
            }
245
            child = child.getNextSibling();
1✔
246
        }
247
    }
1✔
248

249
    private URLRewrite parseAction(final ServletConfig config, final String pattern, final Element action) throws ServletException {
250
        final URLRewrite rewrite;
251
        if ("forward".equals(action.getLocalName())) {
1✔
252
            rewrite = new PathForward(config, action, pattern);
1✔
253
        } else if ("redirect".equals(action.getLocalName())) {
1!
254
            rewrite = new Redirect(action, pattern);
×
255
        } else if ("root".equals(action.getLocalName())) {
1!
256
            final ControllerForward cf = new ControllerForward(action, pattern);
1✔
257

258
            /*
259
             * If there is a server-name attribute on the root tag, then add that
260
             * as an attribute on the ControllerForward object.
261
             */
262
            final String serverName = action.getAttribute(SERVER_NAME_ATTRIBUTE);
1✔
263
            if (!serverName.isEmpty()) {
1!
264
                cf.setServerName(serverName);
×
265
            }
266
            rewrite = cf;
1✔
267
        } else {
1✔
268
            rewrite = null;
×
269
        }
270
        return rewrite;
1✔
271
    }
272

273
    private Document parseConfig(final Path file) throws ParserConfigurationException, SAXException, IOException {
274
        try (final InputStream is = new BufferedInputStream(Files.newInputStream(file))) {
1✔
275
            final InputSource src = new InputSource(is);
1✔
276
            final XMLReaderPool parserPool = urlRewrite.getBrokerPool().getParserPool();
1✔
277
            XMLReader xr = null;
1✔
278
            try {
279
                xr = parserPool.borrowXMLReader();
1✔
280
                final SAXAdapter adapter = new SAXAdapter((Expression) null);
1✔
281
                xr.setContentHandler(adapter);
1✔
282
                xr.setProperty(Namespaces.SAX_LEXICAL_HANDLER, adapter);
1✔
283
                xr.parse(src);
1✔
284
                return adapter.getDocument();
1✔
285
            } finally {
286
                if (xr != null) {
1!
287
                    parserPool.returnXMLReader(xr);
1✔
288
                }
289
            }
290
        }
291
    }
292

293
    /**
294
     * Maps a regular expression to an URLRewrite instance
295
     */
296
    private static final class Mapping {
297
        private final Pattern pattern;
298
        private final URLRewrite action;
299
        private Matcher matcher;
300

301
        private Mapping(String regex, final URLRewrite action) throws ServletException {
1✔
302
            try {
303
                final int options = RegularExpression.XML11 | RegularExpression.XPATH30;
1✔
304
                int flagbits = 0;
1✔
305

306
                final List<RegexSyntaxException> warnings = new ArrayList<>();
1✔
307
                regex = JDK15RegexTranslator.translate(regex, options, flagbits, warnings);
1✔
308

309
                this.pattern = Pattern.compile(regex, 0);
1✔
310
                this.action = action;
1✔
311
                this.matcher = pattern.matcher("");
1✔
312
            } catch (final RegexSyntaxException e) {
1✔
313
                throw new ServletException("Syntax error in regular expression specified for path. " +
×
314
                        e.getMessage(), e);
×
315
            }
316
        }
1✔
317

318
        public String match(final String path) {
319
            matcher.reset(path);
1✔
320
            if (matcher.lookingAt()) {
1✔
321
                return path.substring(matcher.start(), matcher.end());
1✔
322
            }
323
            return null;
1✔
324
        }
325
    }
326
}
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