All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.restheart.mongodb.exchange.MongoRequestPropsInjector Maven / Gradle / Ivy

There is a newer version: 8.1.5
Show newest version
/*-
 * ========================LICENSE_START=================================
 * restheart-mongodb
 * %%
 * Copyright (C) 2014 - 2024 SoftInstigate
 * %%
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see .
 * =========================LICENSE_END==================================
 */
package org.restheart.mongodb.exchange;

import io.undertow.server.HttpServerExchange;
import java.util.Arrays;
import java.util.Deque;
import java.util.Optional;
import org.bson.BsonDocument;
import org.bson.BsonValue;
import org.bson.json.JsonParseException;
import static org.restheart.exchange.ExchangeKeys.AGGREGATION_VARIABLES_QPARAM_KEY;
import org.restheart.exchange.ExchangeKeys.DOC_ID_TYPE;
import static org.restheart.exchange.ExchangeKeys.DOC_ID_TYPE_QPARAM_KEY;
import static org.restheart.exchange.ExchangeKeys.FILTER_QPARAM_KEY;
import org.restheart.exchange.ExchangeKeys.HAL_MODE;
import static org.restheart.exchange.ExchangeKeys.HAL_QPARAM_KEY;
import static org.restheart.exchange.ExchangeKeys.HINT_QPARAM_KEY;
import static org.restheart.exchange.ExchangeKeys.KEYS_QPARAM_KEY;
import static org.restheart.exchange.ExchangeKeys.PAGESIZE_QPARAM_KEY;
import static org.restheart.exchange.ExchangeKeys.PAGE_QPARAM_KEY;
import org.restheart.exchange.ExchangeKeys.REPRESENTATION_FORMAT;
import static org.restheart.exchange.ExchangeKeys.REPRESENTATION_FORMAT_KEY;
import static org.restheart.exchange.ExchangeKeys.SHARDKEY_QPARAM_KEY;
import static org.restheart.exchange.ExchangeKeys.SORT_BY_QPARAM_KEY;
import static org.restheart.exchange.ExchangeKeys.SORT_QPARAM_KEY;
import org.restheart.exchange.ExchangeKeys.TYPE;
import org.restheart.exchange.MongoRequest;
import org.restheart.exchange.MongoResponse;
import org.restheart.exchange.UnsupportedDocumentIdException;
import org.restheart.mongodb.MongoServiceConfiguration;
import org.restheart.mongodb.utils.StagesInterpolator;
import org.restheart.mongodb.utils.MongoURLUtils;
import org.restheart.utils.HttpStatus;
import org.restheart.utils.BsonUtils;

/**
 * initialize the MongoRequest
 * Assumes that the MongoRequest and MongoRequest are already initialized
 *
 * @author Andrea Di Cesare {@literal }
 */
public class MongoRequestPropsInjector {
    private static final int DEFAULT_PAGESIZE = MongoServiceConfiguration.get().getDefaultPagesize();
    private static final int MAX_PAGESIZE = MongoServiceConfiguration.get().getMaxPagesize();

    /**
     *
     * @param exchange
     */
    public static void inject(HttpServerExchange exchange) {
        var request = MongoRequest.of(exchange);
        var response = MongoResponse.of(exchange);

        // skip parameters injection if method is OPTIONS
        // this makes sure OPTIONS works even on wrong paramenter
        // e.g. OPTIONS 127.0.0.1:8080?page=a
        if (request.isOptions()) {
            return;
        }

        // get and check rep parameter (representation format)
        var __rep = exchange.getQueryParameters().get(REPRESENTATION_FORMAT_KEY);

        // default value
        var rep = MongoServiceConfiguration.get().getDefaultRepresentationFormat();

        if (__rep != null && !__rep.isEmpty()) {
            var _rep = __rep.getFirst();

            if (_rep != null && !_rep.isEmpty()) {
                try {
                    rep = REPRESENTATION_FORMAT.valueOf(_rep.strip().toUpperCase());
                } catch (IllegalArgumentException iae) {
                    response.addWarning("illegal rep parameter " + _rep + " (must be STANDARD, NESTED or HAL; S is an alias for STANDARD; PLAIN_JSON, PJ are aliases for NESTED)");
                }
            }
        }

        request.setRepresentationFormat(rep);

        // check database name to be a valid mongodb name
        if (request.getDBName() != null && request.isDbNameInvalid()) {
            response.setInError(HttpStatus.SC_BAD_REQUEST, "illegal database name, see https://www.mongodb.com/docs/manual/reference/limits/#naming-restrictions");
            return;
        }

        // check collection name to be a valid mongodb name
        if (request.getCollectionName() != null && request.isCollectionNameInvalid()) {
            response.setInError(HttpStatus.SC_BAD_REQUEST, "illegal collection name, see https://www.mongodb.com/docs/manual/reference/limits/#naming-restrictions");
            return;
        }

        // check txnId to be a valid long
        if (request.isTxn()) {
            try {
                request.getTxnId();
            } catch (Throwable t) {
                response.setInError(HttpStatus.SC_BAD_REQUEST, "illegal txnId: it must be a number");
                return;
            }
        }

        // check collection name to be a valid mongodb name
        if (request.isReservedResource()) {
            response.setInError(HttpStatus.SC_FORBIDDEN, "reserved resource");
            return;
        }

        var __pagesize = exchange.getQueryParameters().get(PAGESIZE_QPARAM_KEY);

        int page = 1; // default page

        int pagesize = DEFAULT_PAGESIZE;

        if (__pagesize != null && !(__pagesize.isEmpty())) {
            try {
                pagesize = Integer.parseInt(__pagesize.getFirst());
            } catch (NumberFormatException ex) {
                response.setInError(HttpStatus.SC_BAD_REQUEST, "illegal pagesize paramenter, it is not a number", ex);
                return;
            }
        }

        if (pagesize < 0 || pagesize > MAX_PAGESIZE) {
            response.setInError(HttpStatus.SC_BAD_REQUEST, "illegal pagesize parameter, pagesize must be >= 0 and <= " + MAX_PAGESIZE);
            return;
        } else {
            request.setPagesize(pagesize);
        }

        var __page = exchange.getQueryParameters().get(PAGE_QPARAM_KEY);

        if (__page != null && !(__page.isEmpty())) {
            try {
                page = Integer.parseInt(__page.getFirst());
            } catch (NumberFormatException ex) {
                response.setInError(HttpStatus.SC_BAD_REQUEST, "illegal page paramenter, it is not a number", ex);
                return;
            }
        }

        if (page < 1) {
            response.setInError(HttpStatus.SC_BAD_REQUEST, "illegal page paramenter, it is < 1");
            return;
        } else {
            request.setPage(page);
        }

        var __count = exchange.getQueryParameters().get("count");

        if (__count != null) {
            request.setCount(true);
        }

        // get and check sort_by parameter
        Deque sort_by = null;

        if (exchange.getQueryParameters().containsKey(SORT_BY_QPARAM_KEY)) {
            sort_by = exchange.getQueryParameters().get(SORT_BY_QPARAM_KEY);

        } else if (exchange.getQueryParameters().containsKey(SORT_QPARAM_KEY)) {
            sort_by = exchange.getQueryParameters().get(SORT_QPARAM_KEY);
        }

        if (sort_by != null) {
            if (sort_by.stream().anyMatch(s -> s == null || s.isEmpty())) {
                response.setInError(HttpStatus.SC_BAD_REQUEST, "Illegal sort paramenter");
                return;
            }

            if (sort_by.stream().anyMatch(s -> s.strip().equals("_last_updated_on") || s.strip().equals("+_last_updated_on") || s.strip().equals("-_last_updated_on"))) {
                response.addWarning("unexepecting sorting; the _last_updated_on timestamp is generated from the _etag property if present");
            }

            if (sort_by.stream().anyMatch(s -> s.strip().equals("_created_on"))) {
                response.addWarning("unexepecting sorting; the _created_on timestamp is generated from the _id property if it is an ObjectId");
            }

            request.setSortBy(sort_by);
        }

        // get and check hint parameter
        Deque hint = null;

        if (exchange.getQueryParameters().containsKey(HINT_QPARAM_KEY)) {
            hint = exchange.getQueryParameters().get(HINT_QPARAM_KEY);
        }

        if (hint != null) {
            if (hint.stream().anyMatch(s -> s == null || s.isEmpty())) {
                response.setInError(HttpStatus.SC_BAD_REQUEST, "illegal hint paramenter");
                return;
            }

            request.setHint(hint);
        }

        var keys = exchange.getQueryParameters().get(KEYS_QPARAM_KEY);

        if (keys != null) {
            if (keys.stream().anyMatch(f -> {
                if (f == null || f.isEmpty()) {
                    response.setInError(HttpStatus.SC_BAD_REQUEST, "illegal keys paramenter (empty)");
                    return true;
                }

                try {
                    var _keys = BsonUtils.parse(f);

                    if (!_keys.isDocument()) {
                        response.setInError(HttpStatus.SC_BAD_REQUEST, "illegal keys paramenter, it is not a json object: " + f + " => " + f.getClass().getSimpleName());
                        return true;
                    }
                } catch (Throwable t) {
                    response.setInError(HttpStatus.SC_BAD_REQUEST, "illegal keys paramenter: " + f, t);
                    return true;
                }

                return false;
            })) {
                return; // an error occurred
            }
            request.setKeys(exchange.getQueryParameters().get(KEYS_QPARAM_KEY));
        }

        // get and check filter parameter
        var filters = exchange.getQueryParameters().get(FILTER_QPARAM_KEY);

        if (filters != null) {
            if (filters.stream().anyMatch(f -> {
                if (f == null || f.isEmpty()) {
                    response.setInError(HttpStatus.SC_BAD_REQUEST, "illegal filter paramenter (empty)");
                    return true;
                }

                try {
                    BsonValue _filter = BsonUtils.parse(f);

                    if (!_filter.isDocument()) {
                        response.setInError(HttpStatus.SC_BAD_REQUEST, "illegal filter paramenter, it is not a json object: " + f + " => " + f.getClass().getSimpleName());
                        return true;
                    } else if (_filter.asDocument().keySet().isEmpty()) {
                        response.setInError(HttpStatus.SC_BAD_REQUEST, "illegal filter paramenter (empty json object)");
                        return true;
                    }

                } catch (Throwable t) {
                    response.setInError(HttpStatus.SC_BAD_REQUEST, "illegal filter paramenter: " + f + ", " + t.getMessage(), t);
                    return true;
                }

                return false;
            })) {
                return; // an error occurred
            }

            request.setFilter(exchange.getQueryParameters().get(FILTER_QPARAM_KEY));
        }

        // filter qparam is mandatory for bulk DELETE and PATCH
        if (request.getType() == TYPE.BULK_DOCUMENTS && (request.isDelete() || request.isPatch()) && (filters == null || filters.isEmpty())) {
            response.setInError( HttpStatus.SC_BAD_REQUEST, "filter paramenter is mandatory for bulk write requests");
            return;
        }

        // get and check avars parameter
        var avars = exchange.getQueryParameters().get(AGGREGATION_VARIABLES_QPARAM_KEY);

        if (avars != null) {
            Optional _qvars = avars.stream().findFirst();

            if (!_qvars.isPresent()) {
                response.setInError(HttpStatus.SC_BAD_REQUEST, "Illegal avars paramenter (empty)");
                return;
            }

            try {
                BsonDocument qvars;

                try {
                    qvars = BsonDocument.parse(_qvars.get());
                } catch (JsonParseException jpe) {
                    response.setInError(HttpStatus.SC_BAD_REQUEST, "illegal avars paramenter, it is not a json object: " + _qvars.get());
                    return;
                }

                // throws SecurityException if aVars contains operators
                if (MongoServiceConfiguration.get().getAggregationCheckOperators()) {
                    StagesInterpolator.shouldNotContainOperators(qvars);
                }

                request.setAggregationVars(qvars);
            } catch (SecurityException t) {
                response.setInError(HttpStatus.SC_BAD_REQUEST, "illegal avars paramenter: " + _qvars.get(), t);
                return;
            }
        }

        // get and check the doc id type parameter
        var __docIdType = exchange.getQueryParameters().get(DOC_ID_TYPE_QPARAM_KEY);

        // default value
        var docIdType = DOC_ID_TYPE.STRING_OID;

        if (__docIdType != null && !__docIdType.isEmpty()) {
            var _docIdType = __docIdType.getFirst();

            if (_docIdType != null && !_docIdType.isEmpty()) {
                try {
                    docIdType = DOC_ID_TYPE.valueOf(_docIdType.strip().toUpperCase());
                } catch (IllegalArgumentException iae) {
                    response.setInError(HttpStatus.SC_BAD_REQUEST, "illegal " + DOC_ID_TYPE_QPARAM_KEY + " paramenter; must be " + Arrays.toString(DOC_ID_TYPE.values()));
                    return;
                }
            }
        }

        request.setDocIdType(docIdType);

        // for POST the doc _id is set by BodyjectorHandler
        if (!request.isPost()) {
            // get and check the document id
            String _docId = request.getDocumentIdRaw();

            try {
                request.setDocumentId(MongoURLUtils.getDocumentIdFromURI(_docId, docIdType));
            } catch (UnsupportedDocumentIdException idide) {
                response.setInError(HttpStatus.SC_BAD_REQUEST, "wrong document id format: not a valid " + docIdType.name(), idide);
                return;
            }
        }

        // get the HAL query parameter
        var __halMode = exchange.getQueryParameters().get(HAL_QPARAM_KEY);

        if (__halMode == null || __halMode.isEmpty()) {
            // default is compact mode
            request.setHalMode(HAL_MODE.COMPACT);
        } else {
            String _halMode = __halMode.getFirst();

            try {
                request.setHalMode(HAL_MODE.valueOf(_halMode.strip().toUpperCase()));
            } catch (IllegalArgumentException iae) {
                response.setInError(HttpStatus.SC_BAD_REQUEST, "illegal " + HAL_QPARAM_KEY + " paramenter; valid values are " + Arrays.toString(HAL_MODE.values()));
                return;
            }

            // if representation has not been set explicitly, set to default
            if (exchange.getQueryParameters().get(REPRESENTATION_FORMAT_KEY) == null) {

                request.setRepresentationFormat(MongoServiceConfiguration.get().getDefaultRepresentationFormat());
            }
        }

        // get the shardkey query parameter
        // get and check shardKeys parameter
        var shardKeys = exchange.getQueryParameters().get(SHARDKEY_QPARAM_KEY);

        if (shardKeys != null) {
            if (shardKeys.stream().anyMatch(f -> {
                if (f == null || f.isEmpty()) {
                    response.setInError(HttpStatus.SC_BAD_REQUEST, "illegal shardkey paramenter (empty)");
                    return true;
                }

                try {
                    var _shardKeys = BsonDocument.parse(f);

                    if (_shardKeys.keySet().isEmpty()) {
                        response.setInError(HttpStatus.SC_BAD_REQUEST, "illegal shardkey paramenter (empty json object)");
                        return true;
                    }

                    request.setShardKey(_shardKeys);

                } catch (Throwable t) {
                    response.setInError(HttpStatus.SC_BAD_REQUEST, "illegal shardkey paramenter: " + f, t);
                    return true;
                }

                return false;
            })) {
                return; // an error occurred
            }

            request.setFilter(exchange.getQueryParameters().get(FILTER_QPARAM_KEY));
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy