• 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

51.33
/exist-core/src/main/java/org/exist/http/RESTServer.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;
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.collections.Collection;
56
import org.exist.collections.triggers.TriggerException;
57
import org.exist.debuggee.DebuggeeFactory;
58
import org.exist.dom.QName;
59
import org.exist.dom.memtree.ElementImpl;
60
import org.exist.dom.memtree.NodeImpl;
61
import org.exist.dom.memtree.SAXAdapter;
62
import org.exist.dom.persistent.*;
63
import org.exist.http.servlets.EXistServlet;
64
import org.exist.http.servlets.HttpRequestWrapper;
65
import org.exist.http.servlets.HttpResponseWrapper;
66
import org.exist.http.servlets.ResponseWrapper;
67
import org.exist.http.urlrewrite.XQueryURLRewrite;
68
import org.exist.security.Permission;
69
import org.exist.security.PermissionDeniedException;
70
import org.exist.security.Subject;
71
import org.exist.security.internal.RealmImpl;
72
import org.exist.source.DBSource;
73
import org.exist.source.Source;
74
import org.exist.source.StringSource;
75
import org.exist.source.URLSource;
76
import org.exist.storage.BrokerPool;
77
import org.exist.storage.DBBroker;
78
import org.exist.storage.XQueryPool;
79
import org.exist.storage.lock.Lock.LockMode;
80
import org.exist.storage.lock.ManagedCollectionLock;
81
import org.exist.storage.serializers.EXistOutputKeys;
82
import org.exist.storage.serializers.Serializer;
83
import org.exist.storage.txn.Txn;
84
import org.exist.util.*;
85
import org.exist.util.io.CachingFilterInputStream;
86
import org.exist.util.io.FilterInputStreamCache;
87
import org.exist.util.io.FilterInputStreamCacheFactory;
88
import org.exist.util.io.FilterInputStreamCacheFactory.FilterInputStreamCacheConfiguration;
89
import org.exist.util.serializer.SAXSerializer;
90
import org.exist.util.serializer.SerializerPool;
91
import org.exist.util.serializer.XQuerySerializer;
92
import org.exist.util.serializer.json.JSONNode;
93
import org.exist.util.serializer.json.JSONObject;
94
import org.exist.util.serializer.json.JSONSimpleProperty;
95
import org.exist.util.serializer.json.JSONValue;
96
import org.exist.xmldb.XmldbURI;
97
import org.exist.xqj.Marshaller;
98
import org.exist.xquery.*;
99
import org.exist.xquery.value.*;
100
import org.exist.xupdate.Modification;
101
import org.exist.xupdate.XUpdateProcessor;
102
import org.exquery.http.HttpRequest;
103
import org.w3c.dom.Document;
104
import org.w3c.dom.Element;
105
import org.w3c.dom.Node;
106
import org.w3c.dom.NodeList;
107
import org.xml.sax.InputSource;
108
import org.xml.sax.SAXException;
109
import org.xml.sax.SAXParseException;
110
import org.xml.sax.XMLReader;
111
import org.xml.sax.helpers.AttributesImpl;
112
import org.xml.sax.helpers.XMLFilterImpl;
113

114
import jakarta.servlet.http.HttpServletRequest;
115
import jakarta.servlet.http.HttpServletResponse;
116
import javax.xml.XMLConstants;
117
import javax.xml.parsers.ParserConfigurationException;
118
import javax.xml.stream.XMLStreamException;
119
import javax.xml.transform.OutputKeys;
120
import javax.xml.transform.TransformerConfigurationException;
121
import java.io.*;
122
import java.lang.invoke.LambdaMetafactory;
123
import java.lang.invoke.MethodHandle;
124
import java.lang.invoke.MethodHandles;
125
import java.lang.reflect.Field;
126
import java.util.Properties;
127
import java.util.*;
128
import java.util.function.BiFunction;
129

130
import static java.lang.invoke.MethodType.methodType;
131
import static java.nio.charset.StandardCharsets.UTF_8;
132
import static org.exist.http.RESTServerParameter.*;
133

134
/**
135
 *
136
 * @author wolf
137
 * @author ljo
138
 * @author adam
139
 * @author gev
140
 */
141
public class RESTServer {
142

143
    protected final static Logger LOG = LogManager.getLogger(RESTServer.class);
1✔
144
    public final static String SERIALIZATION_METHOD_PROPERTY = "output-as";
145
    // Should we not obey the instance's defaults? /ljo
146
    protected final static Properties defaultProperties = new Properties();
1✔
147

148
    static {
149
        defaultProperties.setProperty(OutputKeys.INDENT, "yes");
1✔
150
        defaultProperties.setProperty(OutputKeys.MEDIA_TYPE, MimeType.XML_TYPE.getName());
1✔
151
        defaultProperties.setProperty(EXistOutputKeys.EXPAND_XINCLUDES, "yes");
1✔
152
        defaultProperties.setProperty(EXistOutputKeys.HIGHLIGHT_MATCHES, "elements");
1✔
153
        defaultProperties.setProperty(EXistOutputKeys.PROCESS_XSL_PI, "yes");
1✔
154
    }
155
    public final static Properties defaultOutputKeysProperties = new Properties();
1✔
156

157
    static {
158
        defaultOutputKeysProperties.setProperty(EXistOutputKeys.OMIT_ORIGINAL_XML_DECLARATION, "no");
1✔
159
        defaultOutputKeysProperties.setProperty(OutputKeys.OMIT_XML_DECLARATION, "yes");
1✔
160
        defaultOutputKeysProperties.setProperty(OutputKeys.INDENT, "yes");
1✔
161
        defaultOutputKeysProperties.setProperty(OutputKeys.MEDIA_TYPE,
1✔
162
                MimeType.XML_TYPE.getName());
1✔
163
    }
164
    private final static String QUERY_ERROR_HEAD = "<html>" + "<head>"
165
            + "<title>Query Error</title>" + "<style type=\"text/css\">"
166
            + ".errmsg {" + "  border: 1px solid black;" + "  padding: 15px;"
167
            + "  margin-left: 20px;" + "  margin-right: 20px;" + "}"
168
            + "h1 { color: #C0C0C0; }" + ".path {" + "  padding-bottom: 10px;"
169
            + "}" + ".high { " + "  color: #666699; " + "  font-weight: bold;"
170
            + "}" + "</style>" + "</head>" + "<body>" + "<h1>XQuery Error</h1>";
171

172
    private static final String DEFAULT_ENCODING = UTF_8.name();
1✔
173

174
    private final String formEncoding; // TODO: we may be able to remove this
175
    // eventually, in favour of
176
    // HttpServletRequestWrapper being setup in
177
    // EXistServlet, currently used for doPost()
178
    // but perhaps could be used for other
179
    // Request Methods? - deliriumsky
180
    private final String containerEncoding;
181
    private final boolean useDynamicContentType;
182
    private final boolean safeMode;
183
    private final SessionManager sessionManager;
184
    private final EXistServlet.FeatureEnabled xquerySubmission;
185
    private final EXistServlet.FeatureEnabled xupdateSubmission;
186

187
    //EXQuery Request Module details
188
    private String xqueryContextExqueryRequestAttribute = null;
1✔
189
    private BiFunction<HttpServletRequest, FilterInputStreamCacheConfiguration, HttpRequest> cstrHttpServletRequestAdapter = null;
1✔
190

191
    // Constructor
192
    public RESTServer(final BrokerPool pool, final String formEncoding,
1✔
193
                      final String containerEncoding, final boolean useDynamicContentType, final boolean safeMode, final EXistServlet.FeatureEnabled xquerySubmission, final EXistServlet.FeatureEnabled xupdateSubmission) {
194
        this.formEncoding = formEncoding;
1✔
195
        this.containerEncoding = containerEncoding;
1✔
196
        this.useDynamicContentType = useDynamicContentType;
1✔
197
        this.safeMode = safeMode;
1✔
198
        this.sessionManager = new SessionManager();
1✔
199
        this.xquerySubmission = xquerySubmission;
1✔
200
        this.xupdateSubmission = xupdateSubmission;
1✔
201

202
        //get (optiona) EXQuery Request Module details
203
        try {
204
            Class clazz = Class.forName("org.exist.extensions.exquery.modules.request.RequestModule");
×
205
            if(clazz != null) {
×
206
                final Field fldExqRequestAttr = clazz.getDeclaredField("EXQ_REQUEST_ATTR");
×
207
                if(fldExqRequestAttr != null) {
×
208
                    this.xqueryContextExqueryRequestAttribute = (String)fldExqRequestAttr.get(null);
×
209

210
                    if(this.xqueryContextExqueryRequestAttribute != null) {
×
211
                        clazz = Class.forName("org.exist.extensions.exquery.restxq.impl.adapters.HttpServletRequestAdapter");
×
212
                        if(clazz != null) {
×
213
                            final MethodHandles.Lookup lookup = MethodHandles.lookup();
×
214
                            final MethodHandle methodHandle = lookup.findConstructor(clazz, methodType(void.class, HttpServletRequest.class, FilterInputStreamCacheConfiguration.class));
×
215

216
                            this.cstrHttpServletRequestAdapter =
×
217
                                    (BiFunction<HttpServletRequest, FilterInputStreamCacheConfiguration, HttpRequest>)
218
                                            LambdaMetafactory.metafactory(
×
219
                                                    lookup, "apply", methodType(BiFunction.class),
×
220
                                                    methodHandle.type().erase(), methodHandle, methodHandle.type()).getTarget().invokeExact();
×
221
                        }
222
                    }
223

224
                }
225
            }
226
        } catch(final Throwable e) {
1✔
227
            if (e instanceof InterruptedException) {
1!
228
                // NOTE: must set interrupted flag
229
                Thread.currentThread().interrupt();
×
230
            }
231

232
            if(LOG.isDebugEnabled()) {
1!
233
                LOG.debug("EXQuery Request Module is not present: {}", e.getMessage(), e);
×
234
            }
235
        }
236
    }
1✔
237

238
    /**
239
     * Retrieves a parameter from the Query String of the request
240
     */
241
    private String getParameter(final HttpServletRequest request, final RESTServerParameter parameter) {
242
        return request.getParameter(parameter.queryStringKey());
1✔
243
    }
244

245
    /**
246
     * Handle GET request. In the simplest case just returns the document or
247
     * binary resource specified in the path. If the path leads to a collection,
248
     * a listing of the collection contents is returned. If it resolves to a
249
     * binary resource with mime-type "application/xquery", this resource will
250
     * be loaded and executed by the XQuery engine.
251
     *
252
     * The method also recognizes a number of predefined parameters:
253
     *
254
     * <ul> <li>_xpath or _query: if specified, the given query is executed on
255
     * the current resource or collection.</li>
256
     *
257
     * <li>_howmany: defines how many items from the query result will be
258
     * returned.</li>
259
     *
260
     * <li>_start: a start offset into the result set.</li>
261
     *
262
     * <li>_wrap: if set to "yes", the query results will be wrapped into a
263
     * exist:result element.</li>
264
     *
265
     * <li>_indent: if set to "yes", the returned XML will be pretty-printed.
266
     * </li>
267
     *
268
     * <li>_source: if set to "yes" and a resource with mime-type
269
     * "application/xquery" is requested then the xquery will not be executed,
270
     * instead the source of the document will be returned. Must be enabled in
271
     * descriptor.xml with the following syntax
272
     * <pre>{@code
273
     *     <xquery-app>
274
     *         <allow-source>
275
     *             <xquery path="/db/mycollection/myquery.xql"/>
276
     *         </allow-source>
277
     *     </xquery-app>
278
     * }</pre>
279
     * </li>
280
     *
281
     * <li>_xsl: an URI pointing to an XSL stylesheet that will be applied to
282
     * the returned XML.</li>
283
     *
284
     * <li>_output-doctype: if set to "yes", the returned XML will include
285
     * a Document Type Declaration if one is present, if "no" the Document Type Declaration will be omitted.</li>
286
     * </ul>
287
     *
288
     * @param broker the database broker
289
     * @param transaction the database transaction
290
     * @param request the request
291
     * @param response the response
292
     * @param path the path of the request
293
     *
294
     * @throws BadRequestException if a bad request is made
295
     * @throws PermissionDeniedException if the request has insufficient permissions
296
     * @throws NotFoundException if the request resource cannot be found
297
     * @throws IOException if an I/O error occurs
298
     */
299
    public void doGet(final DBBroker broker, final Txn transaction, final HttpServletRequest request,
300
            final HttpServletResponse response, final String path)
301
            throws BadRequestException, PermissionDeniedException,
302
            NotFoundException, IOException {
303

304
        // if required, set character encoding
305
        if (request.getCharacterEncoding() == null) {
1!
306
            request.setCharacterEncoding(formEncoding);
1✔
307
        }
308

309
        String option;
310
        if ((option = getParameter(request, Release)) != null) {
1!
311
            final int sessionId = Integer.parseInt(option);
×
312
            sessionManager.release(sessionId);
×
313
            if (LOG.isDebugEnabled()) {
×
314
                LOG.debug("Released session {}", sessionId);
×
315
            }
316
            response.setStatus(HttpServletResponse.SC_OK);
×
317
            return;
×
318
        }
319

320
        // Process special parameters
321

322
        int howmany = 10;
1✔
323
        int start = 1;
1✔
324
        boolean typed = false;
1✔
325
        boolean wrap = true;
1✔
326
        boolean source = false;
1✔
327
        boolean cache = false;
1✔
328
        final Properties outputProperties = new Properties(defaultOutputKeysProperties);
1✔
329

330
        String query = null;
1✔
331
        if (!safeMode) {
1!
332
            query = getParameter(request, XPath);
1✔
333
            if (query == null) {
1!
334
                query = getParameter(request, Query);
1✔
335
            }
336
        }
337
        final String _var = getParameter(request, Variables);
1✔
338
        List /*<Namespace>*/ namespaces = null;
1✔
339
        ElementImpl variables = null;
1✔
340
        try {
341
            if (_var != null) {
1!
342
                final NamespaceExtractor nsExtractor = new NamespaceExtractor();
×
343
                variables = parseXML(broker.getBrokerPool(), _var, nsExtractor);
×
344
                namespaces = nsExtractor.getNamespaces();
×
345
            }
346
        } catch (final SAXException e) {
×
347
            final XPathException x = new XPathException(variables != null ? variables.getExpression() : null, e.toString());
×
348
            writeXPathException(response, HttpServletResponse.SC_BAD_REQUEST, DEFAULT_ENCODING, query, path, x);
×
349
        }
350

351
        if ((option = getParameter(request, HowMany)) != null) {
1!
352
            try {
353
                howmany = Integer.parseInt(option);
×
354
            } catch (final NumberFormatException nfe) {
×
355
                throw new BadRequestException(
×
356
                        "Parameter _howmany should be an int");
×
357
            }
358
        }
359
        if ((option = getParameter(request, Start)) != null) {
1!
360
            try {
361
                start = Integer.parseInt(option);
×
362
            } catch (final NumberFormatException nfe) {
×
363
                throw new BadRequestException(
×
364
                        "Parameter _start should be an int");
×
365
            }
366
        }
367
        if ((option = getParameter(request, Typed)) != null) {
1!
368
            if ("yes".equals(option.toLowerCase())) {
×
369
                typed = true;
×
370
            }
371
        }
372
        if ((option = getParameter(request, Wrap)) != null) {
1✔
373
            wrap = "yes".equals(option);
1✔
374
            outputProperties.setProperty("_wrap", option);
1✔
375
        }
376
        if ((option = getParameter(request, Cache)) != null) {
1!
377
            cache = "yes".equals(option);
×
378
        }
379
        if ((option = getParameter(request, Indent)) != null) {
1✔
380
            outputProperties.setProperty(OutputKeys.INDENT, option);
1✔
381
        }
382
        if ((option = getParameter(request, Output_Doctype)) != null) {
1✔
383
            // take user query-string specified output-doctype setting
384
            outputProperties.setProperty(EXistOutputKeys.OUTPUT_DOCTYPE, option);
1✔
385
        } else {
1✔
386
            // set output-doctype by configuration
387
            final String outputDocType = broker.getConfiguration().getProperty(Serializer.PROPERTY_OUTPUT_DOCTYPE, "yes");
1✔
388
            outputProperties.setProperty(EXistOutputKeys.OUTPUT_DOCTYPE, outputDocType);
1✔
389
        }
390
        if ((option = getParameter(request, Omit_Xml_Declaration)) != null) {
1!
391
            // take user query-string specified omit-xml-declaration setting
392
            outputProperties.setProperty(OutputKeys.OMIT_XML_DECLARATION, option);
×
393
        } else {
×
394
            // set omit-xml-declaration by configuration
395
            final String omitXmlDeclaration = broker.getConfiguration().getProperty(Serializer.PROPERTY_OMIT_XML_DECLARATION, "yes");
1✔
396
            outputProperties.setProperty(OutputKeys.OMIT_XML_DECLARATION, omitXmlDeclaration);
1✔
397
        }
398
        if ((option = getParameter(request, Omit_Original_Xml_Declaration)) != null) {
1✔
399
            // take user query-string specified omit-original-xml-declaration setting
400
            outputProperties.setProperty(EXistOutputKeys.OMIT_ORIGINAL_XML_DECLARATION, option);
1✔
401
        } else {
1✔
402
            // set omit-original-xml-declaration by configuration
403
            final String omitOriginalXmlDeclaration = broker.getConfiguration().getProperty(Serializer.PROPERTY_OMIT_ORIGINAL_XML_DECLARATION, "no");
1✔
404
            outputProperties.setProperty(EXistOutputKeys.OMIT_ORIGINAL_XML_DECLARATION, omitOriginalXmlDeclaration);
1✔
405
        }
406
        if ((option = getParameter(request, Source)) != null && !safeMode) {
1!
407
            source = "yes".equals(option);
×
408
        }
409
        if ((option = getParameter(request, Session)) != null) {
1!
410
            outputProperties.setProperty(Serializer.PROPERTY_SESSION_ID, option);
×
411
        }
412
        String stylesheet;
413
        if ((stylesheet = getParameter(request, XSL)) != null) {
1!
414
            if ("no".equals(stylesheet)) {
×
415
                outputProperties.setProperty(EXistOutputKeys.PROCESS_XSL_PI, "no");
×
416
                outputProperties.remove(EXistOutputKeys.STYLESHEET);
×
417
                stylesheet = null;
×
418
            } else {
×
419
                outputProperties.setProperty(EXistOutputKeys.STYLESHEET, stylesheet);
×
420
            }
421
        } else {
×
422
            outputProperties.setProperty(EXistOutputKeys.PROCESS_XSL_PI, "yes");
1✔
423
        }
424
        LOG.debug("stylesheet = {}", stylesheet);
1✔
425
        LOG.debug("query = {}", query);
1✔
426
        String encoding;
427
        if ((encoding = getParameter(request, Encoding)) != null) {
1!
428
            outputProperties.setProperty(OutputKeys.ENCODING, encoding);
×
429
        } else {
×
430
            encoding = DEFAULT_ENCODING;
1✔
431
        }
432

433
        final String mimeType = outputProperties.getProperty(OutputKeys.MEDIA_TYPE);
1✔
434

435
        if (query != null) {
1✔
436
            // query parameter specified, search method does all the rest of the work
437
            try {
438
                search(broker, transaction, query, path, namespaces, variables, howmany, start, typed, outputProperties,
1✔
439
                        wrap, cache, request, response);
1✔
440

441
            } catch (final XPathException e) {
1✔
442
                if (MimeType.XML_TYPE.getName().equals(mimeType)) {
1!
443
                    writeXPathException(response, HttpServletResponse.SC_BAD_REQUEST, encoding, query, path, e);
1✔
444
                } else {
1✔
445
                    writeXPathExceptionHtml(response, HttpServletResponse.SC_BAD_REQUEST, encoding, query, path, e);
×
446
                }
447
            }
448
            return;
1✔
449
        }
450
        // Process the request
451
        LockedDocument lockedDocument = null;
1✔
452
        DocumentImpl resource = null;
1✔
453
        final XmldbURI pathUri = XmldbURI.create(path);
1✔
454
        try {
455
            // check if path leads to an XQuery resource
456
            final String xquery_mime_type = MimeType.XQUERY_TYPE.getName();
1✔
457
            final String xproc_mime_type = MimeType.XPROC_TYPE.getName();
1✔
458
            lockedDocument = broker.getXMLResource(pathUri, LockMode.READ_LOCK);
1✔
459
            resource = lockedDocument == null ? null : lockedDocument.getDocument();
1✔
460

461
            if (null != resource && !isExecutableType(resource)) {
1✔
462
                // return regular resource that is not an xquery and not is xproc
463
                writeResourceAs(resource, broker, transaction, stylesheet, encoding, null,
1✔
464
                        outputProperties, request, response);
1✔
465
                return;
1✔
466
            }
467
            if (resource == null) { // could be request for a Collection
1✔
468

469
                // no document: check if path points to a collection
470
                try(final Collection collection = broker.openCollection(pathUri, LockMode.READ_LOCK)) {
1✔
471
                    if (collection != null) {
1!
472
                        if (safeMode || !collection.getPermissionsNoLock().validate(broker.getCurrentSubject(), Permission.READ)) {
×
473
                            throw new PermissionDeniedException("Not allowed to read collection");
×
474
                        }
475
                        // return a listing of the collection contents
476
                        try {
477
                            writeCollection(response, encoding, broker, collection);
×
478
                            return;
×
479
                        } catch (final LockException le) {
×
480
                            if (MimeType.XML_TYPE.getName().equals(mimeType)) {
×
481
                                writeXPathException(response, HttpServletResponse.SC_BAD_REQUEST, encoding, query, path, new XPathException((Expression) null, le.getMessage(), le));
×
482
                            } else {
×
483
                                writeXPathExceptionHtml(response, HttpServletResponse.SC_BAD_REQUEST, encoding, query, path, new XPathException((Expression) null, le.getMessage(), le));
×
484
                            }
485
                        }
486

487
                    } else if (source) {
1!
488
                        // didn't find regular resource, or user wants source
489
                        // on a possible xquery resource that was not found
490
                        throw new NotFoundException("Document " + path + " not found");
×
491
                    }
492
                }
493
            }
494

495
            XmldbURI servletPath = pathUri;
1✔
496

497
            // if resource is still null, work up the url path to find an
498
            // xquery or xproc resource
499
            while (null == resource) {
1✔
500
                // traverse up the path looking for xquery objects
501
                servletPath = servletPath.removeLastSegment();
1✔
502
                if (servletPath == XmldbURI.EMPTY_URI) {
1✔
503
                    break;
1✔
504
                }
505

506
                lockedDocument = broker.getXMLResource(servletPath, LockMode.READ_LOCK);
1✔
507
                resource = lockedDocument == null ? null : lockedDocument.getDocument();
1✔
508
                if (null != resource && isExecutableType(resource)) {
1✔
509
                    break;
1✔
510

511
                } else if (null != resource) {
1✔
512
                    //unlocked at finally block
513

514
                    // not an xquery resource. This means we have a path
515
                    // that cannot contain an xquery object even if we keep
516
                    // moving up the path, so bail out now
517
                    throw new NotFoundException("Document " + path + " not found");
1✔
518
                }
519
            }
520

521
            if (null == resource) { // path search failed
1✔
522
                throw new NotFoundException("Document " + path + " not found");
1✔
523
            }
524

525
            // found an XQuery or XProc resource, fixup request values
526
            final String pathInfo = pathUri.trimFromBeginning(servletPath).toString();
1✔
527

528
            // reset any output-doctype, omit-xml-declaration, or omit-original-xml-declaration properties, as these can conflict with others set via XQuery Serialization settings
529
            outputProperties.setProperty(EXistOutputKeys.OUTPUT_DOCTYPE, "no");
1✔
530
            outputProperties.setProperty(OutputKeys.OMIT_XML_DECLARATION, "yes");
1✔
531
            outputProperties.setProperty(EXistOutputKeys.OMIT_ORIGINAL_XML_DECLARATION, "yes");
1✔
532

533
            // Should we display the source of the XQuery or XProc or execute it
534
            final Descriptor descriptor = Descriptor.getDescriptorSingleton();
1✔
535
            if (source) {
1!
536
                // show the source
537

538
                // check are we allowed to show the xquery source -
539
                // descriptor.xml
540
                if ((null != descriptor)
×
541
                        && descriptor.allowSource(path)
×
542
                        && resource.getPermissions().validate(
×
543
                        broker.getCurrentSubject(), Permission.READ)) {
×
544

545
                    // TODO: change writeResourceAs to use a serializer
546
                    // that will serialize xquery to syntax coloured
547
                    // xhtml, replace the asMimeType parameter with a
548
                    // method for specifying the serializer, or split
549
                    // the code into two methods. - deliriumsky
550

551
                    if (xquery_mime_type.equals(resource.getMimeType())) {
×
552
                        // Show the source of the XQuery
553
                        writeResourceAs(resource, broker, transaction, stylesheet, encoding,
×
554
                                MimeType.TEXT_TYPE.getName(), outputProperties,
×
555
                                request, response);
×
556
                    } else if (xproc_mime_type.equals(resource.getMimeType())) {
×
557
                        // Show the source of the XProc
558
                        writeResourceAs(resource, broker, transaction, stylesheet, encoding,
×
559
                                MimeType.XML_TYPE.getName(), outputProperties,
×
560
                                request, response);
×
561
                    }
562
                } else {
×
563
                    // we are not allowed to show the source - query not
564
                    // allowed in descriptor.xml
565
                    // or descriptor not found, so assume source view not
566
                    // allowed
567
                    response
×
568
                            .sendError(
×
569
                            HttpServletResponse.SC_FORBIDDEN,
×
570
                            "Permission to view XQuery source for: "
×
571
                            + path
×
572
                            + " denied. Must be explicitly defined in descriptor.xml");
573
                    return;
×
574
                }
575
            } else {
576
                try {
577
                    if (xquery_mime_type.equals(resource.getMimeType())) {
1!
578
                        // Execute the XQuery
579
                        executeXQuery(broker, transaction, resource, request, response,
1✔
580
                                outputProperties, servletPath.toString(), pathInfo);
1✔
581
                    } else if (xproc_mime_type.equals(resource.getMimeType())) {
1!
582
                        // Execute the XProc
583
                        executeXProc(broker, transaction, resource, request, response,
×
584
                                outputProperties, servletPath.toString(), pathInfo);
×
585
                    }
586
                } catch (final XPathException e) {
×
587
                    if (LOG.isDebugEnabled()) {
×
588
                        LOG.debug(e.getMessage(), e);
×
589
                    }
590
                    if (MimeType.XML_TYPE.getName().equals(mimeType)) {
×
591
                        writeXPathException(response, HttpServletResponse.SC_INTERNAL_SERVER_ERROR, encoding, query, path, e);
×
592
                    } else {
×
593
                        writeXPathExceptionHtml(response, HttpServletResponse.SC_INTERNAL_SERVER_ERROR, encoding, query,
×
594
                                path, e);
×
595
                    }
596
                }
597
            }
598
        } finally {
×
599
            if (lockedDocument != null) {
1✔
600
                lockedDocument.close();
1✔
601
            }
602
        }
603
    }
1✔
604

605
    public void doHead(final DBBroker broker, final Txn transaction, final HttpServletRequest request,
606
            final HttpServletResponse response, final String path)
607
            throws BadRequestException, PermissionDeniedException,
608
            NotFoundException, IOException {
609

610
        final XmldbURI pathUri = XmldbURI.create(path);
1✔
611
        if (checkForXQueryTarget(broker, transaction, pathUri, request, response)) {
1!
612
            return;
1✔
613
        }
614

615
        final Properties outputProperties = new Properties(defaultOutputKeysProperties);
×
616

617
        String encoding;
618
        if ((encoding = getParameter(request, Encoding)) != null) {
×
619
            outputProperties.setProperty(OutputKeys.ENCODING, encoding);
×
620
        } else {
×
621
            encoding = DEFAULT_ENCODING;
×
622
        }
623

624
        try(final LockedDocument lockedDocument = broker.getXMLResource(pathUri, LockMode.READ_LOCK)) {
×
625
            final DocumentImpl resource = lockedDocument == null ? null : lockedDocument.getDocument();
×
626

627
            if (resource != null) {
×
628
                if (!resource.getPermissions().validate(broker.getCurrentSubject(), Permission.READ)) {
×
629
                    throw new PermissionDeniedException(
×
630
                            "Permission to read resource " + path + " denied");
×
631
                }
632
                response.setContentType(resource.getMimeType());
×
633
                // As HttpServletResponse.setContentLength is limited to integers,
634
                // (see http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4187336)
635
                // next sentence:
636
                //        response.setContentLength(resource.getContentLength());
637
                // must be set so
638
                response.addHeader("Content-Length", Long.toString(resource.getContentLength()));
×
639
                setCreatedAndLastModifiedHeaders(response, resource.getCreated(), resource.getLastModified());
×
640
            } else {
×
641
                try(final Collection col = broker.openCollection(pathUri, LockMode.READ_LOCK)) {
×
642
                    //no resource or collection
643
                    if (col == null) {
×
644
                        response.sendError(HttpServletResponse.SC_NOT_FOUND, "No resource at location: " + path);
×
645

646
                        return;
×
647
                    }
648

649
                    if (!col.getPermissionsNoLock().validate(broker.getCurrentSubject(), Permission.READ)) {
×
650
                        throw new PermissionDeniedException(
×
651
                                "Permission to read resource " + path + " denied");
×
652
                    }
653
                    response.setContentType(MimeType.XML_TYPE.getName() + "; charset=" + encoding);
×
654
                    setCreatedAndLastModifiedHeaders(response, col.getCreated(), col.getCreated());
×
655
                }
656
            }
657
        }
658
    }
×
659

660
    /**
661
     * Handles POST requests. If the path leads to a binary resource with
662
     * mime-type "application/xquery", that resource will be read and executed
663
     * by the XQuery engine. Otherwise, the request content is loaded and parsed
664
     * as XML. It may either contain an XUpdate or a query request.
665
     *
666
     * @param broker the database broker
667
     * @param transaction the database transaction
668
     * @param request the request
669
     * @param response the response
670
     * @param path the path of the request
671
     *
672
     * @throws BadRequestException if a bad request is made
673
     * @throws PermissionDeniedException if the request has insufficient permissions
674
     * @throws NotFoundException if the request resource cannot be found
675
     * @throws IOException if an I/O error occurs
676
     */
677
    public void doPost(final DBBroker broker, final Txn transaction, final HttpServletRequest request,
678
            final HttpServletResponse response, final String path)
679
            throws BadRequestException, PermissionDeniedException, IOException,
680
            NotFoundException {
681

682
        // if required, set character encoding
683
        if (request.getCharacterEncoding() == null) {
1✔
684
            request.setCharacterEncoding(formEncoding);
1✔
685
        }
686

687
        final Properties outputProperties = new Properties(defaultOutputKeysProperties);
1✔
688
        final XmldbURI pathUri = XmldbURI.create(path);
1✔
689
        LockedDocument lockedDocument = null;
1✔
690
        DocumentImpl resource = null;
1✔
691

692
        final String encoding = getEncoding(outputProperties);
1✔
693
        String mimeType = outputProperties.getProperty(OutputKeys.MEDIA_TYPE);
1✔
694
        try {
695
            // check if path leads to an XQuery resource.
696
            // if yes, the resource is loaded and the XQuery executed.
697
            final String xquery_mime_type = MimeType.XQUERY_TYPE.getName();
1✔
698
            final String xproc_mime_type = MimeType.XPROC_TYPE.getName();
1✔
699
            lockedDocument = broker.getXMLResource(pathUri, LockMode.READ_LOCK);
1✔
700
            resource = lockedDocument == null ? null : lockedDocument.getDocument();
1✔
701

702
            XmldbURI servletPath = pathUri;
1✔
703

704
            // if resource is still null, work up the url path to find an
705
            // xquery resource
706
            while (null == resource) {
1✔
707
                // traverse up the path looking for xquery objects
708
                servletPath = servletPath.removeLastSegment();
1✔
709
                if (servletPath == XmldbURI.EMPTY_URI) {
1✔
710
                    break;
1✔
711
                }
712

713
                lockedDocument = broker.getXMLResource(servletPath, LockMode.READ_LOCK);
1✔
714
                resource = lockedDocument == null ? null : lockedDocument.getDocument();
1✔
715
                if (null != resource
1✔
716
                        && (resource.getResourceType() == DocumentImpl.BINARY_FILE
1!
717
                        && xquery_mime_type.equals(resource.getMimeType())
1!
718
                        || resource.getResourceType() == DocumentImpl.XML_FILE
×
719
                        && xproc_mime_type.equals(resource.getMimeType()))) {
×
720
                    break; // found a binary file with mime-type xquery or XML file with mime-type xproc
×
721

722
                } else if (null != resource) {
1!
723

724
                    // not an xquery or xproc resource. This means we have a path
725
                    // that cannot contain an xquery or xproc object even if we keep
726
                    // moving up the path, so bail out now
727
                    lockedDocument.close();
×
728
                    lockedDocument = null;
×
729
                    resource = null;
×
730
                    break;
×
731
                }
732
            }
733

734
            // either xquery binary file or xproc xml file
735
            if (resource != null) {
1✔
736
                if (resource.getResourceType() == DocumentImpl.BINARY_FILE
1✔
737
                        && xquery_mime_type.equals(resource.getMimeType())
1!
738
                        || resource.getResourceType() == DocumentImpl.XML_FILE
1!
739
                        && xproc_mime_type.equals(resource.getMimeType())) {
1!
740

741
                    // found an XQuery resource, fixup request values
742
                    final String pathInfo = pathUri.trimFromBeginning(servletPath).toString();
1✔
743
                    try {
744
                        if (xquery_mime_type.equals(resource.getMimeType())) {
1!
745
                            // Execute the XQuery
746
                            executeXQuery(broker, transaction, resource, request, response,
1✔
747
                                    outputProperties, servletPath.toString(), pathInfo);
1✔
748
                        } else {
1✔
749
                            // Execute the XProc
750
                            executeXProc(broker, transaction, resource, request, response,
×
751
                                    outputProperties, servletPath.toString(), pathInfo);
×
752
                        }
753

754
                    } catch (final XPathException e) {
×
755
                        if (MimeType.XML_TYPE.getName().equals(mimeType)) {
×
756
                            writeXPathException(response, HttpServletResponse.SC_INTERNAL_SERVER_ERROR, encoding, null, path, e);
×
757

758
                        } else {
×
759
                            writeXPathExceptionHtml(response, HttpServletResponse.SC_INTERNAL_SERVER_ERROR, encoding, null, path, e);
×
760
                        }
761
                    }
762
                    return;
1✔
763
                }
764
            }
765

766
        } finally {
767
            if (lockedDocument != null) {
1✔
768
                lockedDocument.close();
1✔
769
            }
770
        }
771

772
        // check the content type to see if its XML or a parameter string
773
        String requestType = request.getContentType();
1✔
774
        if (requestType != null) {
1!
775
            final int semicolon = requestType.indexOf(';');
1✔
776
            if (semicolon > 0) {
1✔
777
                requestType = requestType.substring(0, semicolon).trim();
1✔
778
            }
779
        }
780

781
        // content type != application/x-www-form-urlencoded
782
        if (requestType == null || !requestType.equals(MimeType.URL_ENCODED_TYPE.getName())) {
1!
783
            // third, normal POST: read the request content and check if
784
            // it is an XUpdate or a query request.
785
            int howmany = 10;
1✔
786
            int start = 1;
1✔
787
            boolean typed = false;
1✔
788
            ElementImpl variables = null;
1✔
789
            boolean enclose = true;
1✔
790
            boolean cache = false;
1✔
791
            String query = null;
1✔
792

793
            try {
794
                final String content = getRequestContent(request);
1✔
795
                final NamespaceExtractor nsExtractor = new NamespaceExtractor();
1✔
796
                final ElementImpl root = parseXML(broker.getBrokerPool(), content, nsExtractor);
1✔
797
                final String rootNS = root.getNamespaceURI();
1✔
798

799
                if (rootNS != null && rootNS.equals(Namespaces.EXIST_NS)) {
1!
800

801
                    if (Query.xmlKey().equals(root.getLocalName())) {
1!
802
                        // process <query>xpathQuery</query>
803
                        String option = root.getAttribute(Start.xmlKey());
1✔
804
                        if (!option.isEmpty()) {
1!
805
                            try {
806
                                start = Integer.parseInt(option);
×
807
                            } catch (final NumberFormatException e) {
×
808
                                //
809
                            }
810
                        }
811

812
                        option = root.getAttribute(Max.xmlKey());
1✔
813
                        if (!option.isEmpty()) {
1!
814
                            try {
815
                                howmany = Integer.parseInt(option);
×
816
                            } catch (final NumberFormatException e) {
×
817
                                //
818
                            }
819
                        }
820

821
                        option = root.getAttribute(Enclose.xmlKey());
1✔
822
                        if ("no".equals(option)) {
1!
823
                            enclose = false;
×
824
                        } else {
×
825
                            option = root.getAttribute(Wrap.xmlKey());
1✔
826
                            if ("no".equals(option)) {
1!
827
                                enclose = false;
×
828
                            }
829
                        }
830

831
                        option = root.getAttribute(Method.xmlKey());
1✔
832
                        if (!option.isEmpty()) {
1!
833
                            outputProperties.setProperty(SERIALIZATION_METHOD_PROPERTY, option);
×
834
                        }
835

836
                        option = root.getAttribute(Typed.xmlKey());
1✔
837
                        if ("yes".equals(option)) {
1!
838
                            typed = true;
×
839
                        }
840

841
                        option = root.getAttribute(Mime.xmlKey());
1✔
842
                        if (!option.isEmpty()) {
1!
843
                            mimeType = option;
×
844
                        }
845

846
                        option = root.getAttribute(Cache.xmlKey());
1✔
847
                        cache = "yes".equals(option);
1✔
848

849
                        option = root.getAttribute(Session.xmlKey());
1✔
850
                        if (!option.isEmpty()) {
1!
851
                            outputProperties.setProperty(
×
852
                                    Serializer.PROPERTY_SESSION_ID, option);
×
853
                        }
854

855
                        final NodeList children = root.getChildNodes();
1✔
856
                        for (int i = 0; i < children.getLength(); i++) {
1✔
857

858
                            final Node child = children.item(i);
1✔
859
                            if (child.getNodeType() == Node.ELEMENT_NODE
1✔
860
                                    && child.getNamespaceURI().equals(Namespaces.EXIST_NS)) {
1!
861

862
                                if (Text.xmlKey().equals(child.getLocalName())) {
1✔
863
                                    final StringBuilder buf = new StringBuilder();
1✔
864
                                    Node next = child.getFirstChild();
1✔
865
                                    while (next != null) {
1✔
866
                                        if (next.getNodeType() == Node.TEXT_NODE
1!
867
                                                || next.getNodeType() == Node.CDATA_SECTION_NODE) {
×
868
                                            buf.append(next.getNodeValue());
1✔
869
                                        }
870
                                        next = next.getNextSibling();
1✔
871
                                    }
872
                                    query = buf.toString();
1✔
873

874
                                } else if (Variables.xmlKey().equals(child.getLocalName())) {
1!
875
                                    variables = (ElementImpl) child;
×
876

877
                                } else if (Properties.xmlKey().equals(child.getLocalName())) {
1!
878
                                    Node node = child.getFirstChild();
1✔
879
                                    while (node != null) {
1✔
880
                                        if (node.getNodeType() == Node.ELEMENT_NODE
1!
881
                                                && node.getNamespaceURI().equals(Namespaces.EXIST_NS)
1!
882
                                                && Property.xmlKey().equals(node.getLocalName())) {
1!
883

884
                                            final Element property = (Element) node;
1✔
885
                                            final String key = property.getAttribute("name");
1✔
886
                                            final String value = property.getAttribute("value");
1✔
887
                                            LOG.debug("{} = {}", key, value);
1✔
888

889
                                            if ((!key.isEmpty()) && (!value.isEmpty())) {
1!
890
                                                outputProperties.setProperty(key, value);
1✔
891
                                            }
892
                                        }
893
                                        node = node.getNextSibling();
1✔
894
                                    }
895
                                }
896
                            }
897
                        }
898
                    }
899

900
                    // execute query
901
                    if (query != null) {
1!
902

903
                        try {
904
                            search(broker, transaction, query, path, nsExtractor.getNamespaces(), variables,
1✔
905
                                    howmany, start, typed, outputProperties,
1✔
906
                                    enclose, cache, request, response);
1✔
907
                        } catch (final XPathException e) {
1✔
908
                            if (MimeType.XML_TYPE.getName().equals(mimeType)) {
1!
909
                                writeXPathException(response, HttpServletResponse.SC_BAD_REQUEST,
1✔
910
                                        encoding, null, path, e);
1✔
911
                            } else {
1✔
912
                                writeXPathExceptionHtml(response, HttpServletResponse.SC_BAD_REQUEST,
×
913
                                        encoding, null, path, e);
×
914
                            }
915
                        }
916

917
                    } else {
×
918
                        throw new BadRequestException("No query specified");
×
919
                    }
920

921
                } else if (rootNS != null && rootNS.equals(XUpdateProcessor.XUPDATE_NS)) {
1!
922
                    if(LOG.isDebugEnabled()) {
1!
923
                        LOG.debug("Got xupdate request: {}", content);
×
924
                    }
925

926
                    if(xupdateSubmission == EXistServlet.FeatureEnabled.FALSE) {
1!
927
                        response.setStatus(HttpServletResponse.SC_FORBIDDEN);
×
928
                        return;
×
929
                    } else if(xupdateSubmission == EXistServlet.FeatureEnabled.AUTHENTICATED_USERS_ONLY) {
1!
930
                        final Subject currentSubject = broker.getCurrentSubject();
×
931
                        if(!currentSubject.isAuthenticated() || currentSubject.getId() == RealmImpl.GUEST_GROUP_ID) {
×
932
                            response.setStatus(HttpServletResponse.SC_FORBIDDEN);
×
933
                            return;
×
934
                        }
935
                    }
936

937
                    final MutableDocumentSet docs = new DefaultDocumentSet();
1✔
938

939
                    final boolean isCollection;
940
                    try(final Collection collection = broker.openCollection(pathUri, LockMode.READ_LOCK)) {
1✔
941
                        if (collection != null) {
1!
942
                            isCollection = true;
×
943
                            collection.allDocs(broker, docs, true);
×
944
                        } else {
×
945
                            isCollection = false;
1✔
946
                        }
947
                    }
948

949
                    if(!isCollection) {
1!
950
                        final DocumentImpl xupdateDoc = broker.getResource(pathUri, Permission.READ);
1✔
951
                        if (xupdateDoc != null) {
1!
952
                            docs.add(xupdateDoc);
1✔
953
                        } else {
1✔
954
                            broker.getAllXMLResources(docs);
×
955
                        }
956
                    }
957

958
                    final XUpdateProcessor processor = new XUpdateProcessor(broker, docs);
1✔
959
                    long mods = 0;
1✔
960
                    try(final Reader reader = new StringReader(content)) {
1✔
961
                        final Modification modifications[] = processor.parse(new InputSource(reader));
1✔
962
                        for (Modification modification : modifications) {
1✔
963
                            mods += modification.process(transaction);
1✔
964
                            broker.flush();
1✔
965
                        }
966
                    }
967

968
                    // FD : Returns an XML doc
969
                    writeXUpdateResult(response, encoding, mods);
1✔
970
                    // END FD
971

972
                } else {
1✔
973
                    throw new BadRequestException("Unknown XML root element: " + root.getNodeName());
×
974
                }
975

976
            } catch (final SAXException e) {
×
977
                Exception cause = e;
×
978
                if (e.getException() != null) {
×
979
                    cause = e.getException();
×
980
                }
981
                LOG.debug("SAX exception while parsing request: {}", cause.getMessage(), cause);
×
982
                throw new BadRequestException("SAX exception while parsing request: " + cause.getMessage());
×
983

984
            } catch (final ParserConfigurationException e) {
×
985
                throw new BadRequestException("Parser exception while parsing request: " + e.getMessage());
×
986
            } catch (final XPathException e) {
×
987
                throw new BadRequestException("Query exception while parsing request: " + e.getMessage());
×
988
            } catch (final IOException e) {
×
989
                throw new BadRequestException("IO exception while parsing request: " + e.getMessage());
×
990
            } catch (final EXistException e) {
×
991
                throw new BadRequestException(e.getMessage());
×
992
            } catch (final LockException e) {
×
993
                throw new PermissionDeniedException(e.getMessage());
×
994
            }
995

996
            // content type = application/x-www-form-urlencoded
997
        } else {
998
            doGet(broker, transaction, request, response, path);
×
999
        }
1000
    }
1✔
1001

1002
    private ElementImpl parseXML(final BrokerPool pool, final String content,
1003
            final NamespaceExtractor nsExtractor)
1004
            throws SAXException, IOException {
1005
        final InputSource src = new InputSource(new StringReader(content));
1✔
1006
        final XMLReaderPool parserPool = pool.getParserPool();
1✔
1007
        XMLReader reader = null;
1✔
1008
        try {
1009
            reader = parserPool.borrowXMLReader();
1✔
1010
            final SAXAdapter adapter = new SAXAdapter((Expression) null);
1✔
1011
            nsExtractor.setContentHandler(adapter);
1✔
1012
            reader.setProperty(Namespaces.SAX_LEXICAL_HANDLER, adapter);
1✔
1013
            nsExtractor.setParent(reader);
1✔
1014
            nsExtractor.parse(src);
1✔
1015

1016
            final Document doc = adapter.getDocument();
1✔
1017

1018
            return (ElementImpl) doc.getDocumentElement();
1✔
1019
        } finally {
1020
            if (reader != null) {
1!
1021
                parserPool.returnXMLReader(reader);
1✔
1022
            }
1023
        }
1024
    }
1025

1026
    private class NamespaceExtractor extends XMLFilterImpl {
1✔
1027

1028
        final List<Namespace> namespaces = new ArrayList<>();
1✔
1029

1030
        @Override
1031
        public void startPrefixMapping(final String prefix, final String uri)
1032
            throws SAXException {
1033
            if (!Namespaces.EXIST_NS.equals(uri)) {
1✔
1034
                final Namespace ns = new Namespace(prefix, uri);
1✔
1035
                namespaces.add(ns);
1✔
1036
            }
1037
            super.startPrefixMapping(prefix, uri);
1✔
1038
        }
1✔
1039

1040
        public List<Namespace> getNamespaces() {
1041
            return namespaces;
1✔
1042
        }
1043
    }
1044

1045
    public static class Namespace {
1046

1047
        private final String prefix;
1048
        private final String uri;
1049

1050
        public Namespace(final String prefix, final String uri) {
1✔
1051
            this.prefix = prefix;
1✔
1052
            this.uri = uri;
1✔
1053
        }
1✔
1054

1055
        public String getPrefix() {
1056
            return prefix;
1✔
1057
        }
1058

1059
        public String getUri() {
1060
            return uri;
1✔
1061
        }
1062
    }
1063

1064
    /**
1065
     * Handles PUT requests. The request content is stored as a new resource at
1066
     * the specified location. If the resource already exists, it is overwritten
1067
     * if the user has write permissions.
1068
     *
1069
     * The resource type depends on the content type specified in the HTTP
1070
     * header. The content type will be looked up in the global mime table. If
1071
     * the corresponding mime type is not a know XML mime type, the resource
1072
     * will be stored as a binary resource.
1073
     *
1074
     * @param broker the database broker
1075
     * @param transaction the database transaction
1076
     * @param request the request
1077
     * @param response the response
1078
     * @param path the path of the request
1079
     *
1080
     * @throws BadRequestException if a bad request is made
1081
     * @throws PermissionDeniedException if the request has insufficient permissions
1082
     * @throws NotFoundException if the request resource cannot be found
1083
     * @throws IOException if an I/O error occurs
1084
     */
1085
    public void doPut(final DBBroker broker, final Txn transaction, final XmldbURI path,
1086
            final HttpServletRequest request, final HttpServletResponse response)
1087
            throws BadRequestException, PermissionDeniedException, IOException,
1088
            NotFoundException {
1089

1090
        if (checkForXQueryTarget(broker, transaction, path, request, response)) {
1✔
1091
            return;
1✔
1092
        }
1093

1094
        // fourth, process the request
1095

1096
        final XmldbURI docUri = path.lastSegment();
1✔
1097
        final XmldbURI collUri = path.removeLastSegment();
1✔
1098

1099
        if (docUri == null || collUri == null) {
1!
1100
            throw new BadRequestException("Bad path: " + path);
×
1101
        }
1102
        // TODO : use getOrCreateCollection() right now ?
1103
        try(final ManagedCollectionLock managedCollectionLock = broker.getBrokerPool().getLockManager().acquireCollectionWriteLock(collUri)) {
1✔
1104
            final Collection collection = broker.getOrCreateCollection(transaction, collUri);
1✔
1105

1106
            final MimeType mime;
1107
            String contentType = request.getContentType();
1✔
1108
            if (contentType != null) {
1✔
1109
                final int semicolon = contentType.indexOf(';');
1✔
1110
                if (semicolon > 0) {
1✔
1111
                    contentType = contentType.substring(0, semicolon).trim();
1✔
1112
                }
1113
                mime = MimeTable.getInstance().getContentType(contentType);
1✔
1114
            } else {
1✔
1115
                mime = MimeTable.getInstance().getContentTypeFor(docUri);
1✔
1116
            }
1117

1118
            // TODO(AR) in storeDocument need to handle mime == null and use MimeType.BINARY_TYPE
1119
            // TODO(AR) in storeDocument, if the input source has an InputStream (but is not a subclass: FileInputSource or ByteArrayInputSource), need to handle caching and reusing the input stream between validate and store
1120
            try (final FilterInputStreamCache cache = FilterInputStreamCacheFactory.getCacheInstance(()
1✔
1121
                    -> (String) broker.getConfiguration().getProperty(Configuration.BINARY_CACHE_CLASS_PROPERTY), request.getInputStream());
1✔
1122
                final CachingFilterInputStream cfis = new CachingFilterInputStream(cache)) {
1✔
1123
                broker.storeDocument(transaction, docUri, new CachingFilterInputStreamInputSource(cfis), mime, collection);
1✔
1124
            }
1125
            response.setStatus(HttpServletResponse.SC_CREATED);
1✔
1126

1127
//            try(final FilterInputStreamCache cache = FilterInputStreamCacheFactory.getCacheInstance(() -> (String) broker.getConfiguration().getProperty(Configuration.BINARY_CACHE_CLASS_PROPERTY), request.getInputStream());
1128
//                final InputStream cfis = new CachingFilterInputStream(cache)) {
1129
//
1130
//                if (mime.isXMLType()) {
1131
//                    cfis.mark(Integer.MAX_VALUE);
1132
//                    final IndexInfo info = collection.validateXMLResource(transaction, broker, docUri, new InputSource(cfis));
1133
//                    info.getDocument().setMimeType(contentType);
1134
//                    cfis.reset();
1135
//                    collection.store(transaction, broker, info, new InputSource(cfis));
1136
//                    response.setStatus(HttpServletResponse.SC_CREATED);
1137
//                } else {
1138
//                    collection.addBinaryResource(transaction, broker, docUri, cfis, contentType, request.getContentLength());
1139
//                    response.setStatus(HttpServletResponse.SC_CREATED);
1140
//                }
1141
//            }
1142

1143
        } catch (final SAXParseException e) {
×
1144
            throw new BadRequestException("Parsing exception at "
×
1145
                    + e.getLineNumber() + "/" + e.getColumnNumber() + ": "
×
1146
                    + e.toString());
×
1147
        } catch (final TriggerException | LockException e) {
×
1148
            throw new PermissionDeniedException(e.getMessage());
×
1149
        } catch (final SAXException e) {
×
1150
            Exception o = e.getException();
×
1151
            if (o == null) {
×
1152
                o = e;
×
1153
            }
1154
            throw new BadRequestException("Parsing exception: " + o.getMessage());
×
1155
        } catch (final EXistException e) {
×
1156
            throw new BadRequestException("Internal error: " + e.getMessage());
×
1157
        }
1158
    }
1✔
1159

1160

1161
    /**
1162
     * Handles PATCH requests. Only XQuery modules are allowed as targets
1163
     * otherwise it is unclear how to handle the request and a method not allowed
1164
     * is returned.
1165
     *
1166
     * @param broker the database broker
1167
     * @param transaction the database transaction
1168
     * @param request the request
1169
     * @param response the response
1170
     * @param path the path of the request
1171
     *
1172
     * @throws BadRequestException if a bad request is made
1173
     * @throws PermissionDeniedException if the request has insufficient permissions
1174
     * @throws NotFoundException if the request resource cannot be found
1175
     * @throws IOException if an I/O error occurs
1176
     * @throws MethodNotAllowedException if the patch request is not permitted for the resource indicated
1177
     */
1178
    public void doPatch(final DBBroker broker, final Txn transaction, final XmldbURI path,
1179
                      final HttpServletRequest request, final HttpServletResponse response)
1180
            throws BadRequestException, PermissionDeniedException, IOException,
1181
            NotFoundException, MethodNotAllowedException {
1182

1183
        if (checkForXQueryTarget(broker, transaction, path, request, response)) {
1✔
1184
            return;
1✔
1185
        }
1186

1187
        throw new MethodNotAllowedException("No xquery found to handle patch request: " + path);
1✔
1188
    }
1189

1190
    public void doDelete(final DBBroker broker, final Txn transaction, final String path, final HttpServletRequest request, final HttpServletResponse response)
1191
            throws PermissionDeniedException, NotFoundException, IOException, BadRequestException {
1192
        final XmldbURI pathURI = XmldbURI.create(path);
1✔
1193
        if (checkForXQueryTarget(broker, transaction, pathURI, request, response)) {
1✔
1194
            return;
1✔
1195
        }
1196

1197
        try {
1198
            try(final Collection collection = broker.openCollection(pathURI, LockMode.WRITE_LOCK)) {
1✔
1199
                if (collection != null) {
1✔
1200
                    // remove the collection
1201
                    LOG.debug("removing collection {}", path);
1✔
1202

1203
                    broker.removeCollection(transaction, collection);
1✔
1204

1205
                    response.setStatus(HttpServletResponse.SC_OK);
1✔
1206

1207
                } else {
1✔
1208
                    try(final LockedDocument lockedDocument = broker.getXMLResource(pathURI, LockMode.WRITE_LOCK)) {
1✔
1209
                        final DocumentImpl doc = lockedDocument == null ? null : lockedDocument.getDocument();
1!
1210
                        if (doc == null) {
1!
1211
                            throw new NotFoundException("No document or collection found for path: " + path);
×
1212
                        } else {
1213
                            if (!doc.getPermissions().validate(broker.getCurrentSubject(), Permission.WRITE)) {
1!
1214
                                throw new PermissionDeniedException("Account '" + broker.getCurrentSubject().getName() + "' not allowed requested access to document '" + pathURI + "'");
×
1215
                            }
1216

1217
                            // remove the document
1218
                            if(LOG.isDebugEnabled()) {
1!
1219
                                LOG.debug("removing document {}", path);
×
1220
                            }
1221

1222
                            if (doc.getResourceType() == DocumentImpl.BINARY_FILE) {
1!
1223
                                doc.getCollection().removeBinaryResource(transaction, broker, pathURI.lastSegment());
×
1224
                            } else {
×
1225
                                doc.getCollection().removeXMLResource(transaction, broker, pathURI.lastSegment());
1✔
1226
                            }
1227

1228
                            response.setStatus(HttpServletResponse.SC_OK);
1✔
1229
                        }
1230
                    }
1231
                }
1232
            }
1233

1234
        } catch (final TriggerException e) {
×
1235
            throw new PermissionDeniedException("Trigger failed: " + e.getMessage());
×
1236
        } catch (final LockException e) {
×
1237
            throw new PermissionDeniedException("Could not acquire lock: " + e.getMessage());
×
1238
        }
1239
    }
1✔
1240

1241
    private boolean checkForXQueryTarget(final DBBroker broker, final Txn transaction,
1242
        final XmldbURI path, final HttpServletRequest request,
1243
        final HttpServletResponse response) throws PermissionDeniedException, IOException, BadRequestException {
1244

1245
        if (request.getAttribute(XQueryURLRewrite.RQ_ATTR) == null) {
1✔
1246
            return false;
1✔
1247
        }
1248
        final String xqueryType = MimeType.XQUERY_TYPE.getName();
1✔
1249

1250
        final Collection collection = broker.getCollection(path);
1✔
1251
        // a collection is not executable
1252
        if (collection != null) {
1!
1253
            return false;
×
1254
        }
1255

1256
        XmldbURI servletPath = path;
1✔
1257
        LockedDocument lockedDocument = null;
1✔
1258
        DocumentImpl resource = null;
1✔
1259
        // work up the url path to find an
1260
        // xquery resource
1261
        while (resource == null) {
1!
1262
            // traverse up the path looking for xquery objects
1263
            lockedDocument = broker.getXMLResource(servletPath, LockMode.READ_LOCK);
1✔
1264
            resource = lockedDocument == null ? null : lockedDocument.getDocument();
1✔
1265
            if (resource != null
1✔
1266
                    && (resource.getResourceType() == DocumentImpl.BINARY_FILE
1✔
1267
                    && xqueryType.equals(resource.getMimeType()))) {
1!
1268
                break; // found a binary file with mime-type xquery or XML file with mime-type xproc
1✔
1269
            } else if (resource != null) {
1✔
1270
                // not an xquery or xproc resource. This means we have a path
1271
                // that cannot contain an xquery or xproc object even if we keep
1272
                // moving up the path, so bail out now
1273
                lockedDocument.close();
1✔
1274
                return false;
1✔
1275
            }
1276
            servletPath = servletPath.removeLastSegment();
1✔
1277
            if (servletPath == XmldbURI.EMPTY_URI) {
1✔
1278
                // no resource and no path segments left
1279
                return false;
1✔
1280
            }
1281
        }
1282

1283
        // found an XQuery resource, fixup request values
1284
        final String pathInfo = path.trimFromBeginning(servletPath).toString();
1✔
1285
        final Properties outputProperties = new Properties(defaultOutputKeysProperties);
1✔
1286
        try {
1287
            // Execute the XQuery
1288
            executeXQuery(broker, transaction, resource, request, response,
1✔
1289
                    outputProperties, servletPath.toString(), pathInfo);
1✔
1290
        } catch (final XPathException e) {
1✔
1291
            writeXPathExceptionHtml(response, HttpServletResponse.SC_BAD_REQUEST, DEFAULT_ENCODING, null, path.toString(), e);
×
1292
        } finally {
1293
            lockedDocument.close();
1✔
1294
        }
1295
        return true;
1✔
1296
    }
1297

1298
    private String getRequestContent(final HttpServletRequest request) throws IOException {
1299

1300
        String encoding = request.getCharacterEncoding();
1✔
1301
        if (encoding == null) {
1!
1302
            encoding = DEFAULT_ENCODING;
×
1303
        }
1304

1305
        final InputStream is = request.getInputStream();
1✔
1306
        final Reader reader = new InputStreamReader(is, encoding);
1✔
1307
        final StringWriter content = new StringWriter();
1✔
1308
        final char ch[] = new char[4096];
1✔
1309
        int len = 0;
1✔
1310
        while ((len = reader.read(ch)) > -1) {
1✔
1311
            content.write(ch, 0, len);
1✔
1312
        }
1313

1314
        final String xml = content.toString();
1✔
1315
        return xml;
1✔
1316
    }
1317

1318
    /**
1319
     * TODO: pass request and response objects to XQuery.
1320
     *
1321
     * @param broker the database broker
1322
     * @param transaction the database transaction
1323
     * @param query the XQuery
1324
     * @param path the path of the request
1325
     * @param namespaces any XQuery namespace bindings
1326
     * @param variables any XQuery variable bindings
1327
     * @param howmany the number of items in the results to return
1328
     * @param start the start position in the results to return
1329
     * @param typed whether the result nodes should be typed
1330
     * @param outputProperties the serialization properties
1331
     * @param wrap true to wrap the result of the XQuery in an exist:result
1332
     * @param cache whether to cache the results
1333
     * @param request the request
1334
     * @param response the response
1335
     *
1336
     * @throws BadRequestException if a bad request is made
1337
     * @throws PermissionDeniedException if the request has insufficient permissions
1338
     * @throws XPathException if the XQuery raises an error
1339
     */
1340
    protected void search(final DBBroker broker, final Txn transaction, final String query,
1341
        final String path, final List<Namespace> namespaces,
1342
        final ElementImpl variables, final int howmany, final int start,
1343
        final boolean typed, final Properties outputProperties,
1344
        final boolean wrap, final boolean cache,
1345
        final HttpServletRequest request,
1346
        final HttpServletResponse response) throws BadRequestException,
1347
        PermissionDeniedException, XPathException {
1348

1349
        if(xquerySubmission == EXistServlet.FeatureEnabled.FALSE) {
1!
1350
            response.setStatus(HttpServletResponse.SC_FORBIDDEN);
×
1351
            return;
×
1352
        } else if(xquerySubmission == EXistServlet.FeatureEnabled.AUTHENTICATED_USERS_ONLY) {
1!
1353
            final Subject currentSubject = broker.getCurrentSubject();
×
1354
            if(!currentSubject.isAuthenticated() || currentSubject.getId() == RealmImpl.GUEST_GROUP_ID) {
×
1355
                response.setStatus(HttpServletResponse.SC_FORBIDDEN);
×
1356
                return;
×
1357
            }
1358
        }
1359

1360
        final String sessionIdParam = outputProperties.getProperty(Serializer.PROPERTY_SESSION_ID);
1✔
1361
        if (sessionIdParam != null) {
1!
1362
            try {
1363
                final int sessionId = Integer.parseInt(sessionIdParam);
×
1364
                if (sessionId > -1) {
×
1365
                    final Sequence cached = sessionManager.get(query, sessionId);
×
1366
                    if (cached != null) {
×
1367
                        LOG.debug("Returning cached query result");
×
1368
                        writeResults(response, broker, transaction, cached, howmany, start, typed, outputProperties, wrap, 0, 0);
×
1369

1370
                    } else {
×
1371
                        LOG.debug("Cached query result not found. Probably timed out. Repeating query.");
×
1372
                    }
1373
                }
1374

1375
            } catch (final NumberFormatException e) {
×
1376
                throw new BadRequestException("Invalid session id passed in query request: " + sessionIdParam);
×
1377
            }
1378
        }
1379

1380
        final XmldbURI pathUri = XmldbURI.create(path);
1✔
1381
        final Source source = new StringSource(query);
1✔
1382
        final XQueryPool pool = broker.getBrokerPool().getXQueryPool();
1✔
1383
        CompiledXQuery compiled = null;
1✔
1384
        try {
1385
            final XQuery xquery = broker.getBrokerPool().getXQueryService();
1✔
1386
            compiled = pool.borrowCompiledXQuery(broker, source);
1✔
1387

1388
            XQueryContext context;
1389
            if (compiled == null) {
1✔
1390
                context = new XQueryContext(broker.getBrokerPool());
1✔
1391
            } else {
1✔
1392
                context = compiled.getContext();
1✔
1393
                context.prepareForReuse();
1✔
1394
            }
1395

1396
            context.setStaticallyKnownDocuments(new XmldbURI[]{pathUri});
1✔
1397
            context.setBaseURI(new AnyURIValue(pathUri.toString()));
1✔
1398

1399
            declareNamespaces(context, namespaces);
1✔
1400
            declareVariables(context, variables, request, response);
1✔
1401

1402
            final long compilationTime;
1403
            if (compiled == null) {
1✔
1404
                final long compilationStart = System.currentTimeMillis();
1✔
1405
                compiled = xquery.compile(context, source);
1✔
1406
                compilationTime = System.currentTimeMillis() - compilationStart;
1✔
1407
            } else {
1✔
1408
                compiled.getContext().updateContext(context);
1✔
1409
                context.getWatchDog().reset();
1✔
1410
                compilationTime = 0;
1✔
1411
            }
1412

1413
            try {
1414
                final long executeStart = System.currentTimeMillis();
1✔
1415
                final Sequence resultSequence = xquery.execute(broker, compiled, null, outputProperties);
1✔
1416
                final long executionTime = System.currentTimeMillis() - executeStart;
1✔
1417

1418
                if (LOG.isDebugEnabled()) {
1!
1419
                    LOG.debug("Found {} in {}ms.", resultSequence.getItemCount(), executionTime);
×
1420
                }
1421

1422
                if (cache) {
1!
1423
                    final int sessionId = sessionManager.add(query, resultSequence);
×
1424
                    outputProperties.setProperty(Serializer.PROPERTY_SESSION_ID, Integer.toString(sessionId));
×
1425
                    if (!response.isCommitted()) {
×
1426
                        response.setIntHeader("X-Session-Id", sessionId);
×
1427
                    }
1428
                }
1429

1430
                writeResults(response, broker, transaction, resultSequence, howmany, start, typed, outputProperties, wrap, compilationTime, executionTime);
1✔
1431

1432
            } finally {
1✔
1433
                context.runCleanupTasks();
1✔
1434
            }
1435

1436
        } catch (final IOException e) {
×
1437
            throw new BadRequestException(e.getMessage(), e);
×
1438
        } finally {
1439
            if (compiled != null) {
1✔
1440
                pool.returnCompiledXQuery(source, compiled);
1✔
1441
            }
1442
        }
1443
    }
1✔
1444

1445
    private void declareNamespaces(final XQueryContext context,
1446
        final List<Namespace> namespaces) throws XPathException {
1447

1448
        if (namespaces == null) {
1✔
1449
            return;
1✔
1450
        }
1451

1452
        for (final Namespace ns : namespaces) {
1✔
1453
            context.declareNamespace(ns.getPrefix(), ns.getUri());
1✔
1454
        }
1455
    }
1✔
1456

1457
    /**
1458
     * Pass the request, response and session objects to the XQuery context.
1459
     *
1460
     * @param context
1461
     * @param request
1462
     * @param response
1463
     * @throws XPathException
1464
     */
1465
    private HttpRequestWrapper declareVariables(final XQueryContext context,
1466
        final ElementImpl variables, final HttpServletRequest request,
1467
        final HttpServletResponse response) throws XPathException {
1468

1469
        final HttpRequestWrapper reqw = new HttpRequestWrapper(request, formEncoding, containerEncoding);
1✔
1470
        final ResponseWrapper respw = new HttpResponseWrapper(response);
1✔
1471
        context.setHttpContext(new XQueryContext.HttpContext(reqw, respw));
1✔
1472

1473
        //enable EXQuery Request Module (if present)
1474
        try {
1475
            if(xqueryContextExqueryRequestAttribute != null && cstrHttpServletRequestAdapter != null) {
1!
1476
                final HttpRequest exqueryRequestAdapter = cstrHttpServletRequestAdapter.apply(request, () -> (String)context.getBroker().getConfiguration().getProperty(Configuration.BINARY_CACHE_CLASS_PROPERTY));
×
1477

1478
                if(exqueryRequestAdapter != null) {
×
1479
                    context.setAttribute(xqueryContextExqueryRequestAttribute, exqueryRequestAdapter);
×
1480
                }
1481
            }
1482
        } catch(final Exception e) {
×
1483
            if(LOG.isDebugEnabled()) {
×
1484
                LOG.debug("EXQuery Request Module is not present: {}", e.getMessage(), e);
×
1485
            }
1486
        }
1487

1488
        if (variables != null) {
1!
1489
            declareExternalAndXQJVariables(context, variables);
×
1490
        }
1491

1492
        return reqw;
1✔
1493
    }
1494

1495
    private void declareExternalAndXQJVariables(final XQueryContext context,
1496
        final ElementImpl variables) throws XPathException {
1497

1498
        final ValueSequence varSeq = new ValueSequence();
×
1499
        variables.selectChildren(new NameTest(Type.ELEMENT, new QName(Variable.xmlKey(), Namespaces.EXIST_NS)), varSeq);
×
1500
        for (final SequenceIterator i = varSeq.iterate(); i.hasNext();) {
×
1501
            final ElementImpl variable = (ElementImpl) i.nextItem();
×
1502
            // get the QName of the variable
1503
            final ElementImpl qname = (ElementImpl) variable.getFirstChild(new NameTest(Type.ELEMENT, new QName("qname", Namespaces.EXIST_NS)));
×
1504
            String localname = null, prefix = null, uri = null;
×
1505
            NodeImpl child = (NodeImpl) qname.getFirstChild();
×
1506
            while (child != null) {
×
1507
                if ("localname".equals(child.getLocalName())) {
×
1508
                    localname = child.getStringValue();
×
1509

1510
                } else if ("namespace".equals(child.getLocalName())) {
×
1511
                    uri = child.getStringValue();
×
1512

1513
                } else if ("prefix".equals(child.getLocalName())) {
×
1514
                    prefix = child.getStringValue();
×
1515

1516
                }
1517
                child = (NodeImpl) child.getNextSibling();
×
1518
            }
1519

1520
            if (uri != null && prefix != null) {
×
1521
                context.declareNamespace(prefix, uri);
×
1522
            }
1523

1524
            if (localname == null) {
×
1525
                continue;
×
1526
            }
1527

1528
            final QName q;
1529
            if (prefix != null && localname != null) {
×
1530
                q = new QName(localname, uri, prefix);
×
1531
            } else {
×
1532
                q = new QName(localname, uri, XMLConstants.DEFAULT_NS_PREFIX);
×
1533
            }
1534

1535
            // get serialized sequence
1536
            final NodeImpl value = variable.getFirstChild(new NameTest(Type.ELEMENT, Marshaller.ROOT_ELEMENT_QNAME));
×
1537
            final Sequence sequence;
1538
            try {
1539
                sequence = value == null ? Sequence.EMPTY_SEQUENCE : Marshaller.demarshall(value);
×
1540
            } catch (final XMLStreamException xe) {
×
1541
                throw new XPathException((Expression) null, xe.toString());
×
1542
            }
1543

1544
            // now declare variable
1545
            if (prefix != null) {
×
1546
                context.declareVariable(q.getPrefix() + ":" + q.getLocalPart(), sequence);
×
1547
            } else {
×
1548
                context.declareVariable(q.getLocalPart(), sequence);
×
1549
            }
1550
        }
1551
    }
×
1552

1553
    /**
1554
     * Directly execute an XQuery stored as a binary document in the database.
1555
     *
1556
     * @throws PermissionDeniedException
1557
     */
1558
    private void executeXQuery(final DBBroker broker, final Txn transaction, final DocumentImpl resource,
1559
            final HttpServletRequest request, final HttpServletResponse response,
1560
            final Properties outputProperties, final String servletPath, final String pathInfo)
1561
            throws XPathException, BadRequestException, PermissionDeniedException {
1562

1563
        final Source source = new DBSource(broker.getBrokerPool(), (BinaryDocument) resource, true);
1✔
1564
        final XQueryPool pool = broker.getBrokerPool().getXQueryPool();
1✔
1565
        CompiledXQuery compiled = null;
1✔
1566
        try {
1567
            final XQuery xquery = broker.getBrokerPool().getXQueryService();
1✔
1568
            compiled = pool.borrowCompiledXQuery(broker, source);
1✔
1569

1570
            XQueryContext context;
1571
            if (compiled == null) {
1✔
1572
                // special header to indicate that the query is not returned from
1573
                // cache
1574
                response.setHeader("X-XQuery-Cached", "false");
1✔
1575
                context = new XQueryContext(broker.getBrokerPool());
1✔
1576
            } else {
1✔
1577
                response.setHeader("X-XQuery-Cached", "true");
1✔
1578
                context = compiled.getContext();
1✔
1579
                context.prepareForReuse();
1✔
1580
            }
1581

1582
            // TODO: don't hardcode this?
1583
            context.setModuleLoadPath(
1✔
1584
                    XmldbURI.EMBEDDED_SERVER_URI.append(
1✔
1585
                            resource.getCollection().getURI()).toString());
1✔
1586

1587
            context.setStaticallyKnownDocuments(
1✔
1588
                    new XmldbURI[]{resource.getCollection().getURI()});
1✔
1589

1590
            final HttpRequestWrapper reqw = declareVariables(context, null, request, response);
1✔
1591
            reqw.setServletPath(servletPath);
1✔
1592
            reqw.setPathInfo(pathInfo);
1✔
1593

1594
            final long compilationTime;
1595
            if (compiled == null) {
1✔
1596
                try {
1597
                    final long compilationStart = System.currentTimeMillis();
1✔
1598
                    compiled = xquery.compile(context, source);
1✔
1599
                    compilationTime = System.currentTimeMillis() - compilationStart;
1✔
1600
                } catch (final IOException e) {
1✔
1601
                    throw new BadRequestException("Failed to read query from " + resource.getURI(), e);
×
1602
                }
1603
            } else {
1604
                compilationTime = 0;
1✔
1605
            }
1606

1607
            DebuggeeFactory.checkForDebugRequest(request, context);
1✔
1608

1609
            boolean wrap = outputProperties.getProperty("_wrap") != null
1✔
1610
                    && "yes".equals(outputProperties.getProperty("_wrap"));
1!
1611

1612
            try {
1613
                final long executeStart = System.currentTimeMillis();
1✔
1614
                final Sequence result = xquery.execute(broker, compiled, null, outputProperties);
1✔
1615
                writeResults(response, broker, transaction, result, -1, 1, false, outputProperties, wrap, compilationTime, System.currentTimeMillis() - executeStart);
1✔
1616

1617
            } finally {
1✔
1618
                context.runCleanupTasks();
1✔
1619
            }
1620
        } finally {
1621
            if (compiled != null) {
1!
1622
                pool.returnCompiledXQuery(source, compiled);
1✔
1623
            }
1624
        }
1625
    }
1✔
1626

1627
    /**
1628
     * Directly execute an XProc stored as a XML document in the database.
1629
     *
1630
     * @throws PermissionDeniedException
1631
     */
1632
    private void executeXProc(final DBBroker broker, final Txn transaction, final DocumentImpl resource,
1633
            final HttpServletRequest request, final HttpServletResponse response,
1634
            final Properties outputProperties, final String servletPath, final String pathInfo)
1635
            throws XPathException, BadRequestException, PermissionDeniedException {
1636

1637
        final URLSource source = new URLSource(this.getClass().getResource("run-xproc.xq"));
×
1638
        final XQueryPool pool = broker.getBrokerPool().getXQueryPool();
×
1639
        CompiledXQuery compiled = null;
×
1640

1641
        try {
1642
            final XQuery xquery = broker.getBrokerPool().getXQueryService();
×
1643
            compiled = pool.borrowCompiledXQuery(broker, source);
×
1644

1645
            XQueryContext context;
1646
            if (compiled == null) {
×
1647
                context = new XQueryContext(broker.getBrokerPool());
×
1648
            } else {
×
1649
                context = compiled.getContext();
×
1650
                context.prepareForReuse();
×
1651
            }
1652

1653
            context.declareVariable("pipeline", resource.getURI().toString());
×
1654

1655
            final String stdin = request.getParameter("stdin");
×
1656
            context.declareVariable("stdin", stdin == null ? "" : stdin);
×
1657

1658
            final String debug = request.getParameter("debug");
×
1659
            context.declareVariable("debug", debug == null ? "0" : "1");
×
1660

1661
            final String bindings = request.getParameter("bindings");
×
1662
            context.declareVariable("bindings", bindings == null ? "<bindings/>" : bindings);
×
1663

1664
            final String autobind = request.getParameter("autobind");
×
1665
            context.declareVariable("autobind", autobind == null ? "0" : "1");
×
1666

1667
            final String options = request.getParameter("options");
×
1668
            context.declareVariable("options", options == null ? "<options/>" : options);
×
1669

1670
            // TODO: don't hardcode this?
1671
            context.setModuleLoadPath(
×
1672
                    XmldbURI.EMBEDDED_SERVER_URI.append(
×
1673
                            resource.getCollection().getURI()).toString());
×
1674

1675
            context.setStaticallyKnownDocuments(
×
1676
                    new XmldbURI[]{resource.getCollection().getURI()});
×
1677

1678
            final HttpRequestWrapper reqw = declareVariables(context, null, request, response);
×
1679
            reqw.setServletPath(servletPath);
×
1680
            reqw.setPathInfo(pathInfo);
×
1681

1682
            final long compilationTime;
1683
            if (compiled == null) {
×
1684
                try {
1685
                    final long compilationStart = System.currentTimeMillis();
×
1686
                    compiled = xquery.compile(context, source);
×
1687
                    compilationTime = System.currentTimeMillis() - compilationStart;
×
1688
                } catch (final IOException e) {
×
1689
                    throw new BadRequestException("Failed to read query from "
×
1690
                            + source.getURL(), e);
×
1691
                }
1692
            } else {
1693
                compilationTime = 0;
×
1694
            }
1695

1696
            try {
1697
                final long executeStart = System.currentTimeMillis();
×
1698
                final Sequence result = xquery.execute(broker, compiled, null, outputProperties);
×
1699
                writeResults(response, broker, transaction, result, -1, 1, false, outputProperties, false, compilationTime, System.currentTimeMillis() - executeStart);
×
1700
            } finally {
×
1701
                context.runCleanupTasks();
×
1702

1703
            }
1704
        } finally {
1705
            if (compiled != null) {
×
1706
                pool.returnCompiledXQuery(source, compiled);
×
1707
            }
1708
        }
1709
    }
×
1710

1711
    public void setCreatedAndLastModifiedHeaders(
1712
        final HttpServletResponse response, long created, long lastModified) {
1713

1714
        /**
1715
         * Jetty ignores the milliseconds component -
1716
         * https://bugs.eclipse.org/bugs/show_bug.cgi?id=342712 So lets work
1717
         * around this by rounding up to the nearest whole second
1718
         */
1719
        final long lastModifiedMillisComp = lastModified % 1000;
1✔
1720
        if (lastModifiedMillisComp > 0) {
1!
1721
            lastModified += 1000 - lastModifiedMillisComp;
1✔
1722
        }
1723
        final long createdMillisComp = created % 1000;
1✔
1724
        if (createdMillisComp > 0) {
1!
1725
            created += 1000 - createdMillisComp;
1✔
1726
        }
1727

1728
        response.addDateHeader("Last-Modified", lastModified);
1✔
1729
        response.addDateHeader("Created", created);
1✔
1730
    }
1✔
1731

1732
    // writes out a resource, uses asMimeType as the specified mime-type or if
1733
    // null uses the type of the resource
1734
    private void writeResourceAs(final DocumentImpl resource, final DBBroker broker, final Txn transaction,
1735
        final String stylesheet, final String encoding, String asMimeType,
1736
        final Properties outputProperties, final HttpServletRequest request,
1737
        final HttpServletResponse response) throws BadRequestException,
1738
        PermissionDeniedException, IOException {
1739

1740
        // Do we have permission to read the resource
1741
        if (!resource.getPermissions().validate(broker.getCurrentSubject(), Permission.READ)) {
1!
1742
            throw new PermissionDeniedException("Not allowed to read resource");
×
1743
        }
1744

1745
        //get the document metadata
1746
        final long lastModified = resource.getLastModified();
1✔
1747
        setCreatedAndLastModifiedHeaders(response, resource.getCreated(), lastModified);
1✔
1748

1749

1750
        /**
1751
         * HTTP 1.1 RFC 2616 Section 14.25 *
1752
         */
1753
        //handle If-Modified-Since request header
1754
        try {
1755
            final long ifModifiedSince = request.getDateHeader("If-Modified-Since");
1✔
1756
            if (ifModifiedSince > -1) {
1!
1757

1758
                /*
1759
                 a) A date which is later than the server's
1760
                 current time is invalid.
1761
                 */
1762
                if (ifModifiedSince <= System.currentTimeMillis()) {
×
1763

1764
                    /*
1765
                     b) If the variant has been modified since the If-Modified-Since
1766
                     date, the response is exactly the same as for a normal GET.
1767
                     */
1768
                    if (lastModified <= ifModifiedSince) {
×
1769

1770
                        /*
1771
                         c) If the variant has not been modified since a valid If-
1772
                         Modified-Since date, the server SHOULD return a 304 (Not
1773
                         Modified) response.
1774
                         */
1775
                        response.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
×
1776
                        return;
×
1777
                    }
1778
                }
1779
            }
1780
        } catch (final IllegalArgumentException iae) {
×
1781
            LOG.warn("Illegal If-Modified-Since HTTP Header sent on request, ignoring. {}", iae.getMessage(), iae);
×
1782
        }
1783

1784
        if (resource.getResourceType() == DocumentImpl.BINARY_FILE) {
1!
1785
            // binary resource
1786

1787
            if (asMimeType == null) { // wasn't a mime-type specified?
×
1788
                asMimeType = resource.getMimeType();
×
1789
            }
1790

1791
            if (asMimeType.startsWith("text/")) {
×
1792
                response.setContentType(asMimeType + "; charset=" + encoding);
×
1793
            } else {
×
1794
                response.setContentType(asMimeType);
×
1795
            }
1796

1797
            // As HttpServletResponse.setContentLength is limited to integers,
1798
            // (see http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4187336)
1799
            // next sentence:
1800
            //        response.setContentLength(resource.getContentLength());
1801
            // must be set so
1802
            response.addHeader("Content-Length", Long.toString(resource.getContentLength()));
×
1803
            final OutputStream os = response.getOutputStream();
×
1804
            broker.readBinaryResource((BinaryDocument) resource, os);
×
1805
            os.flush();
×
1806
        } else {
×
1807
            // xml resource
1808

1809
            SAXSerializer sax = null;
1✔
1810
            final Serializer serializer = broker.borrowSerializer();
1✔
1811

1812
            //setup the http context
1813
            final HttpRequestWrapper reqw = new HttpRequestWrapper(request, formEncoding, containerEncoding);
1✔
1814
            final HttpResponseWrapper resw = new HttpResponseWrapper(response);
1✔
1815
            serializer.setHttpContext(new XQueryContext.HttpContext(reqw, resw));
1✔
1816

1817
            // Serialize the document
1818
            try {
1819
                sax = (SAXSerializer) SerializerPool.getInstance().borrowObject(SAXSerializer.class);
1✔
1820

1821
                // use a stylesheet if specified in query parameters
1822
                if (stylesheet != null) {
1!
1823
                    serializer.setStylesheet(resource, stylesheet);
×
1824
                }
1825
                serializer.setProperties(outputProperties);
1✔
1826

1827
                if (asMimeType != null) { // was a mime-type specified?
1!
1828
                    response.setContentType(asMimeType + "; charset=" + encoding);
×
1829
                } else {
×
1830
                    if (serializer.isStylesheetApplied()
1!
1831
                            || serializer.hasXSLPi(resource) != null) {
1✔
1832

1833
                        asMimeType = serializer.getStylesheetProperty(OutputKeys.MEDIA_TYPE);
1✔
1834
                        if (!useDynamicContentType || asMimeType == null) {
1!
1835
                            asMimeType = MimeType.HTML_TYPE.getName();
1✔
1836
                        }
1837

1838
                        if (LOG.isDebugEnabled()) {
1!
1839
                            LOG.debug("media-type: {}", asMimeType);
×
1840
                        }
1841

1842
                        response.setContentType(asMimeType + "; charset=" + encoding);
1✔
1843
                    } else {
1✔
1844
                        asMimeType = resource.getMimeType();
1✔
1845
                        response.setContentType(asMimeType + "; charset=" + encoding);
1✔
1846
                    }
1847
                }
1848
                if (asMimeType.equals(MimeType.HTML_TYPE.getName())) {
1✔
1849
                    outputProperties.setProperty("method", "xhtml");
1✔
1850
                    outputProperties.setProperty("media-type", "text/html; charset=" + encoding);
1✔
1851
                    outputProperties.setProperty("indent", "yes");
1✔
1852
                    outputProperties.setProperty("omit-xml-declaration", "no");
1✔
1853
                }
1854

1855
                final OutputStreamWriter writer = new OutputStreamWriter(response.getOutputStream(), encoding);
1✔
1856
                sax.setOutput(writer, outputProperties);
1✔
1857
                serializer.setSAXHandlers(sax, sax);
1✔
1858

1859
                serializer.toSAX(resource);
1✔
1860

1861
                writer.flush();
1✔
1862
                writer.close(); // DO NOT use in try-write-resources, otherwise ther response stream is always closed, and we can't report the errors
1✔
1863
            } catch (final SAXException saxe) {
1✔
1864
                LOG.warn(saxe);
1✔
1865
                throw new BadRequestException("Error while serializing XML: " + saxe.getMessage());
1✔
1866
            } catch (final TransformerConfigurationException e) {
×
1867
                LOG.warn(e);
×
1868
                throw new BadRequestException(e.getMessageAndLocation());
×
1869
            } finally {
1870
                if (sax != null) {
1!
1871
                    SerializerPool.getInstance().returnObject(sax);
1✔
1872
                }
1873
                broker.returnSerializer(serializer);
1✔
1874
            }
1875
        }
1876
    }
1✔
1877

1878
    /**
1879
     * @param response
1880
     * @param encoding
1881
     * @param query
1882
     * @param path
1883
     * @param e
1884
     *
1885
     */
1886
    private void writeXPathExceptionHtml(final HttpServletResponse response,
1887
        final int httpStatusCode, final String encoding, final String query,
1888
        final String path, final XPathException e) throws IOException {
1889

1890
        if (!response.isCommitted()) {
×
1891
            response.reset();
×
1892
        }
1893

1894
        response.setStatus(httpStatusCode);
×
1895

1896
        response.setContentType(MimeType.HTML_TYPE.getName() + "; charset=" + encoding);
×
1897

1898
        final OutputStreamWriter writer = new OutputStreamWriter(response.getOutputStream(), encoding);
×
1899
        writer.write(QUERY_ERROR_HEAD);
×
1900
        writer.write("<p class=\"path\"><span class=\"high\">Path</span>: ");
×
1901
        writer.write("<a href=\"");
×
1902
        writer.write(path);
×
1903
        writer.write("\">");
×
1904
        writer.write(path);
×
1905
        writer.write("</a>");
×
1906

1907
        writer.write("<p class=\"errmsg\">");
×
1908
        final String message = e.getMessage() == null ? e.toString() : e.getMessage();
×
1909
        writer.write(XMLUtil.encodeAttrMarkup(message));
×
1910
        writer.write("");
×
1911
        if (query != null) {
×
1912
            writer.write("<span class=\"high\">Query</span>:<pre>");
×
1913
            writer.write(XMLUtil.encodeAttrMarkup(query));
×
1914
            writer.write("</pre>");
×
1915
        }
1916
        writer.write("</body></html>");
×
1917

1918
        writer.flush();
×
1919
        writer.close();
×
1920
    }
×
1921

1922
    /**
1923
     * @param response
1924
     * @param encoding
1925
     * @param query
1926
     * @param path
1927
     * @param e
1928
     */
1929
    private void writeXPathException(final HttpServletResponse response,
1930
        final int httpStatusCode, final String encoding, final String query,
1931
        final String path, final XPathException e) throws IOException {
1932

1933
        if (!response.isCommitted()) {
1!
1934
            response.reset();
1✔
1935
        }
1936

1937
        response.setStatus(httpStatusCode);
1✔
1938

1939
        response.setContentType(MimeType.XML_TYPE.getName() + "; charset=" + encoding);
1✔
1940

1941
        try(final OutputStreamWriter writer =
1✔
1942
                new OutputStreamWriter(response.getOutputStream(), encoding)) {
1✔
1943

1944
            writer.write("<?xml version=\"1.0\" ?>");
1✔
1945
            writer.write("<exception><path>");
1✔
1946
            writer.write(path);
1✔
1947
            writer.write("</path>");
1✔
1948
            writer.write("<message>");
1✔
1949
            final String message = e.getMessage() == null ? e.toString() : e.getMessage();
1!
1950
            writer.write(XMLUtil.encodeAttrMarkup(message));
1✔
1951
            writer.write("</message>");
1✔
1952
            if (query != null) {
1✔
1953
                writer.write("<query>");
1✔
1954
                writer.write(XMLUtil.encodeAttrMarkup(query));
1✔
1955
                writer.write("</query>");
1✔
1956
            }
1957
            writer.write("</exception>");
1✔
1958
        }
1959
    }
1✔
1960

1961
    /**
1962
     * Writes the XUpdate results to the http response.
1963
     *
1964
     * @param response the http response to write the result to
1965
     * @param encoding the character encoding
1966
     * @param updateCount the number of updates performed
1967
     *
1968
     * @throws IOException if an I/O error occurs
1969
     */
1970
    private void writeXUpdateResult(final HttpServletResponse response,
1971
        final String encoding, final long updateCount) throws IOException {
1972

1973
        response.setContentType(MimeType.XML_TYPE.getName() + "; charset=" + encoding);
1✔
1974

1975
        final OutputStreamWriter writer =
1✔
1976
                new OutputStreamWriter(response.getOutputStream(), encoding);
1✔
1977

1978
        writer.write("<?xml version=\"1.0\" ?>");
1✔
1979
        writer.write("<exist:modifications xmlns:exist=\""
1✔
1980
                + Namespaces.EXIST_NS + "\" count=\"" + updateCount + "\">");
1✔
1981
        writer.write(updateCount + " modifications processed.");
1✔
1982
        writer.write("</exist:modifications>");
1✔
1983

1984
        writer.flush();
1✔
1985
        writer.close();
1✔
1986
    }
1✔
1987

1988
    /**
1989
     * Write the details of a Collection to the http response.
1990
     *
1991
     * @param response the http response to write the result to
1992
     * @param encoding the character encoding
1993
     * @param broker the database broker
1994
     * @param collection the collection to write
1995
     *
1996
     * @throws IOException if an I/O error occurs
1997
     * @throws PermissionDeniedException if there are insufficient privildged for the caller
1998
     * @throws LockException if a lock error occurs
1999
     */
2000
    protected void writeCollection(final HttpServletResponse response,
2001
        final String encoding, final DBBroker broker, final Collection collection)
2002
            throws IOException, PermissionDeniedException, LockException {
2003

2004
        response.setContentType(MimeType.XML_TYPE.getName() + "; charset=" + encoding);
×
2005

2006
        setCreatedAndLastModifiedHeaders(response, collection.getCreated(), collection.getCreated());
×
2007

2008
        final OutputStreamWriter writer =
×
2009
                new OutputStreamWriter(response.getOutputStream(), encoding);
×
2010

2011
        SAXSerializer serializer = null;
×
2012

2013
        try {
2014
            serializer = (SAXSerializer) SerializerPool.getInstance().borrowObject(SAXSerializer.class);
×
2015

2016
            serializer.setOutput(writer, defaultProperties);
×
2017
            final AttributesImpl attrs = new AttributesImpl();
×
2018

2019
            serializer.startDocument();
×
2020
            serializer.startPrefixMapping("exist", Namespaces.EXIST_NS);
×
2021
            serializer.startElement(Namespaces.EXIST_NS, "result",
×
2022
                    "exist:result", attrs);
×
2023

2024
            attrs.addAttribute("", "name", "name", "CDATA", collection.getURI()
×
2025
                    .toString());
×
2026
            // add an attribute for the creation date as an xs:dateTime
2027
            try {
2028
                final DateTimeValue dtCreated =
×
2029
                        new DateTimeValue(new Date(collection.getCreated()));
×
2030
                attrs.addAttribute("", "created", "created", "CDATA",
×
2031
                        dtCreated.getStringValue());
×
2032
            } catch (final XPathException e) {
×
2033
                // fallback to long value
2034
                attrs.addAttribute("", "created", "created", "CDATA",
×
2035
                        String.valueOf(collection.getCreated()));
×
2036
            }
2037

2038
            addPermissionAttributes(attrs, collection.getPermissionsNoLock());
×
2039

2040
            serializer.startElement(Namespaces.EXIST_NS, "collection",
×
2041
                    "exist:collection", attrs);
×
2042

2043
            for (final Iterator<XmldbURI> i = collection.collectionIterator(broker); i.hasNext();) {
×
2044
                final XmldbURI child = i.next();
×
2045
                final Collection childCollection = broker.getCollection(collection
×
2046
                        .getURI().append(child));
×
2047
                if (childCollection != null
×
2048
                        && childCollection.getPermissionsNoLock().validate(broker.getCurrentSubject(), Permission.READ)) {
×
2049
                    attrs.clear();
×
2050
                    attrs.addAttribute("", "name", "name", "CDATA", child.toString());
×
2051

2052
                    // add an attribute for the creation date as an xs:dateTime
2053
                    try {
2054
                        final DateTimeValue dtCreated =
×
2055
                                new DateTimeValue(new Date(childCollection.getCreated()));
×
2056
                        attrs.addAttribute("", "created", "created", "CDATA", dtCreated.getStringValue());
×
2057
                    } catch (final XPathException e) {
×
2058
                        // fallback to long value
2059
                        attrs.addAttribute("", "created", "created", "CDATA",
×
2060
                                String.valueOf(childCollection.getCreated()));
×
2061
                    }
2062

2063
                    addPermissionAttributes(attrs, childCollection.getPermissionsNoLock());
×
2064
                    serializer.startElement(Namespaces.EXIST_NS, "collection", "exist:collection", attrs);
×
2065
                    serializer.endElement(Namespaces.EXIST_NS, "collection", "exist:collection");
×
2066
                }
2067
            }
2068

2069
            for (final Iterator<DocumentImpl> i = collection.iterator(broker); i.hasNext();) {
×
2070
                final DocumentImpl doc = i.next();
×
2071
                if (doc.getPermissions().validate(broker.getCurrentSubject(), Permission.READ)) {
×
2072
                    final XmldbURI resource = doc.getFileURI();
×
2073
                    attrs.clear();
×
2074
                    attrs.addAttribute("", "name", "name", "CDATA", resource.toString());
×
2075

2076
                    // add an attribute for the creation date as an xs:dateTime
2077
                    try {
2078
                        final DateTimeValue dtCreated =
×
2079
                                new DateTimeValue(new Date(doc.getCreated()));
×
2080
                        attrs.addAttribute("", "created", "created", "CDATA",
×
2081
                                dtCreated.getStringValue());
×
2082
                    } catch (final XPathException e) {
×
2083
                        // fallback to long value
2084
                        attrs.addAttribute("", "created", "created", "CDATA",
×
2085
                                String.valueOf(doc.getCreated()));
×
2086
                    }
2087

2088
                    // add an attribute for the last modified date as an
2089
                    // xs:dateTime
2090
                    try {
2091
                        final DateTimeValue dtLastModified = new DateTimeValue(null,
×
2092
                                new Date(doc.getLastModified()));
×
2093
                        attrs.addAttribute("", "last-modified",
×
2094
                                "last-modified", "CDATA", dtLastModified.getStringValue());
×
2095
                    } catch (final XPathException e) {
×
2096
                        // fallback to long value
2097
                        attrs.addAttribute("", "last-modified",
×
2098
                                "last-modified", "CDATA", String.valueOf(doc.getLastModified()));
×
2099
                    }
2100

2101
                    addPermissionAttributes(attrs, doc.getPermissions());
×
2102
                    serializer.startElement(Namespaces.EXIST_NS, "resource", "exist:resource", attrs);
×
2103
                    serializer.endElement(Namespaces.EXIST_NS, "resource", "exist:resource");
×
2104
                }
2105
            }
2106

2107
            serializer.endElement(Namespaces.EXIST_NS, "collection", "exist:collection");
×
2108
            serializer.endElement(Namespaces.EXIST_NS, "result", "exist:result");
×
2109

2110
            serializer.endDocument();
×
2111

2112
            writer.flush();
×
2113
            writer.close();
×
2114

2115
        } catch (final SAXException e) {
×
2116
            // should never happen
2117
            LOG.warn("Error while serializing collection contents: {}", e.getMessage(), e);
×
2118
        } finally {
2119
            if (serializer != null) {
×
2120
                SerializerPool.getInstance().returnObject(serializer);
×
2121
            }
2122
        }
2123
    }
×
2124

2125
    protected void addPermissionAttributes(final AttributesImpl attrs, final Permission perm) {
2126
        attrs.addAttribute("", "owner", "owner", "CDATA", perm.getOwner().getName());
×
2127
        attrs.addAttribute("", "group", "group", "CDATA", perm.getGroup().getName());
×
2128
        attrs.addAttribute("", "permissions", "permissions", "CDATA", perm.toString());
×
2129
    }
×
2130

2131
    protected void writeResults(final HttpServletResponse response, final DBBroker broker, final Txn transaction,
2132
            final Sequence results, int howmany, final int start, final boolean typed,
2133
            final Properties outputProperties, final boolean wrap, final long compilationTime, final long executionTime)
2134
            throws BadRequestException {
2135

2136
        // some xquery functions can write directly to the output stream
2137
        // (response:stream-binary() etc...)
2138
        // so if output is already written then dont overwrite here
2139
        if (response.isCommitted()) {
1✔
2140
            return;
1✔
2141
        }
2142

2143
        // calculate number of results to return
2144
        if (!results.isEmpty()) {
1✔
2145
            final int rlen = results.getItemCount();
1✔
2146
            if ((start < 1) || (start > rlen)) {
1!
2147
                throw new BadRequestException("Start parameter out of range");
×
2148
            }
2149
            // FD : correct bound evaluation
2150
            if (((howmany + start) > rlen) || (howmany <= 0)) {
1!
2151
                howmany = rlen - start + 1;
1✔
2152
            }
2153
        } else {
1✔
2154
            howmany = 0;
1✔
2155
        }
2156
        final String method = outputProperties.getProperty(SERIALIZATION_METHOD_PROPERTY, "xml");
1✔
2157

2158
        if ("json".equals(method)) {
1!
2159
            writeResultJSON(response, broker, transaction, results, howmany, start, outputProperties, wrap, compilationTime, executionTime);
×
2160
        } else {
×
2161
            writeResultXML(response, broker, results, howmany, start, typed, outputProperties, wrap, compilationTime, executionTime);
1✔
2162
        }
2163

2164
    }
1✔
2165

2166
    private static String getEncoding(final Properties outputProperties) {
2167
        return outputProperties.getProperty(OutputKeys.ENCODING, DEFAULT_ENCODING);
1✔
2168
    }
2169

2170
    private void writeResultXML(final HttpServletResponse response,
2171
        final DBBroker broker, final Sequence results, final int howmany,
2172
        final int start, final boolean typed, final Properties outputProperties,
2173
        final boolean wrap, final long compilationTime, final long executionTime) throws BadRequestException {
2174

2175
        // serialize the results to the response output stream
2176
        outputProperties.setProperty(Serializer.GENERATE_DOC_EVENTS, "false");
1✔
2177
        try {
2178

2179
            // set output headers
2180
            final String encoding = getEncoding(outputProperties);
1✔
2181
            if (!response.containsHeader("Content-Type")) {
1!
2182
                String mimeType = outputProperties.getProperty(OutputKeys.MEDIA_TYPE);
1✔
2183
                if (mimeType != null) {
1!
2184
                    final int semicolon = mimeType.indexOf(';');
1✔
2185
                    if (semicolon != Constants.STRING_NOT_FOUND) {
1!
2186
                        mimeType = mimeType.substring(0, semicolon);
×
2187
                    }
2188
                    if (wrap) {
1✔
2189
                        mimeType = "application/xml";
1✔
2190
                    }
2191
                    response.setContentType(mimeType + "; charset=" + encoding);
1✔
2192
                }
2193
            }
2194
            if (wrap) {
1✔
2195
                outputProperties.setProperty("method", "xml");
1✔
2196
            }
2197
            final Writer writer = new OutputStreamWriter(response.getOutputStream(), encoding);
1✔
2198
            final XQuerySerializer serializer = new XQuerySerializer(broker, outputProperties, writer);
1✔
2199

2200
            //Marshaller.marshall(broker, results, start, howmany, serializer.getContentHandler());
2201
            serializer.serialize(results, start, howmany, wrap, typed, compilationTime, executionTime);
1✔
2202

2203
            writer.flush();
1✔
2204
            writer.close();
1✔
2205

2206
        } catch (final SAXException e) {
1✔
2207
            LOG.warn(e);
×
2208
            throw new BadRequestException("Error while serializing xml: "
×
2209
                    + e.toString(), e);
×
2210
        } catch (final Exception e) {
×
2211
            LOG.warn(e.getMessage(), e);
×
2212
            throw new BadRequestException("Error while serializing xml: "
×
2213
                    + e.toString(), e);
×
2214
        }
2215
    }
1✔
2216

2217
    private void writeResultJSON(final HttpServletResponse response,
2218
        final DBBroker broker, final Txn transaction, final Sequence results, int howmany,
2219
        int start, final Properties outputProperties, final boolean wrap, final long compilationTime, final long executionTime)
2220
            throws BadRequestException {
2221

2222
        // calculate number of results to return
2223
        final int rlen = results.getItemCount();
×
2224
        if (!results.isEmpty()) {
×
2225
            if ((start < 1) || (start > rlen)) {
×
2226
                throw new BadRequestException("Start parameter out of range");
×
2227
            }
2228
            // FD : correct bound evaluation
2229
            if (((howmany + start) > rlen) || (howmany <= 0)) {
×
2230
                howmany = rlen - start + 1;
×
2231
            }
2232
        } else {
×
2233
            howmany = 0;
×
2234
        }
2235

2236
        final Serializer serializer = broker.borrowSerializer();
×
2237
        outputProperties.setProperty(Serializer.GENERATE_DOC_EVENTS, "false");
×
2238
        try {
2239
            serializer.setProperties(outputProperties);
×
2240
            try (Writer writer = new OutputStreamWriter(response.getOutputStream(), getEncoding(outputProperties))) {
×
2241
                final JSONObject root = new JSONObject();
×
2242
                root.addObject(new JSONSimpleProperty("start", Integer.toString(start), true));
×
2243
                root.addObject(new JSONSimpleProperty("count", Integer.toString(howmany), true));
×
2244
                root.addObject(new JSONSimpleProperty("hits", Integer.toString(results.getItemCount()), true));
×
2245
                if (outputProperties.getProperty(Serializer.PROPERTY_SESSION_ID) != null) {
×
2246
                    root.addObject(new JSONSimpleProperty("session",
×
2247
                            outputProperties.getProperty(Serializer.PROPERTY_SESSION_ID)));
×
2248
                }
2249
                root.addObject(new JSONSimpleProperty("compilationTime", Long.toString(compilationTime), true));
×
2250
                root.addObject(new JSONSimpleProperty("executionTime", Long.toString(executionTime), true));
×
2251

2252
                final JSONObject data = new JSONObject("data");
×
2253
                root.addObject(data);
×
2254

2255
                Item item;
2256
                for (int i = --start; i < start + howmany; i++) {
×
2257
                    item = results.itemAt(i);
×
2258
                    if (Type.subTypeOf(item.getType(), Type.NODE)) {
×
2259
                        final NodeValue value = (NodeValue) item;
×
2260
                        JSONValue json;
2261
                        if ("json".equals(outputProperties.getProperty("method", "xml"))) {
×
2262
                            json = new JSONValue(serializer.serialize(value), false);
×
2263
                            json.setSerializationDataType(JSONNode.SerializationDataType.AS_LITERAL);
×
2264
                        } else {
×
2265
                            json = new JSONValue(serializer.serialize(value));
×
2266
                            json.setSerializationType(JSONNode.SerializationType.AS_ARRAY);
×
2267
                        }
2268
                        data.addObject(json);
×
2269
                    } else {
×
2270
                        final JSONValue json = new JSONValue(item.getStringValue());
×
2271
                        json.setSerializationType(JSONNode.SerializationType.AS_ARRAY);
×
2272
                        data.addObject(json);
×
2273
                    }
2274
                }
2275

2276
                root.serialize(writer, true);
×
2277

2278
                writer.flush();
×
2279
            }
2280
        } catch (final IOException | XPathException | SAXException e) {
×
2281
            throw new BadRequestException("Error while serializing xml: " + e.toString(), e);
×
2282
        } finally {
2283
            broker.returnSerializer(serializer);
×
2284
        }
2285
    }
×
2286

2287
    private boolean isExecutableType(final DocumentImpl resource) {
2288
        return (
1✔
2289
            resource != null
1!
2290
            && (
2291
                    MimeType.XQUERY_TYPE.getName().equals(resource.getMimeType()) // xquery
1✔
2292
                    || MimeType.XPROC_TYPE.getName().equals(resource.getMimeType()) // xproc
1!
2293
            )
2294
        );
2295
    }
2296
}
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