/* * RESTHeart - the Web API for MongoDB * Copyright (C) SoftInstigate Srl * * 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 Affero 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 <http://www.gnu.org/licenses/>. */ package org.restheart.utils; import com.mongodb.util.JSONParseException; import io.undertow.server.HttpServerExchange; import io.undertow.util.Headers; import java.io.PrintWriter; import java.io.StringWriter; import org.bson.BsonArray; import org.bson.BsonDocument; import org.bson.BsonInt32; import org.bson.BsonString; import org.bson.BsonValue; import org.bson.Document; import org.bson.types.ObjectId; import org.restheart.hal.Representation; import org.restheart.handlers.RequestContext; /** * * @author Andrea Di Cesare {@literal <andrea@softinstigate.com>} */ public class ResponseHelper { /** * * @param exchange * @param context * @param code * @param message */ public static void endExchangeWithMessage( HttpServerExchange exchange, RequestContext context, int code, String message) { endExchangeWithMessage(exchange, context, code, message, null); } /** * * @param exchange * @param context might be null * @param code * @param message * @param t */ public static void endExchangeWithMessage( HttpServerExchange exchange, RequestContext context, int code, String message, Throwable t) { context.setResponseStatusCode(code); String httpStatusText = HttpStatus.getStatusText(code); context.setInError(true); context.setResponseContent( getErrorJsonDocument( exchange.getRequestPath(), code, context, httpStatusText, message, t, false) .asBsonDocument()); } /** * * @param exchange * @param context * @param code * @param rep * @param t */ public static void endExchangeWithRepresentation( HttpServerExchange exchange, RequestContext context, int code, Representation rep) { context.setResponseStatusCode(code); context.setInError(true); context.setResponseContent(rep.asBsonDocument()); } public static Representation getErrorJsonDocument(String href, int code, RequestContext context, String httpStatusText, String message, Throwable t, boolean includeStackTrace) { Representation rep = new Representation(href); rep.addProperty("http status code", new BsonInt32(code)); rep.addProperty("http status description", new BsonString(httpStatusText)); if (message != null) { rep.addProperty( "message", new BsonString(avoidEscapedChars(message))); } Representation nrep = new Representation(); if (t != null) { nrep.addProperty( "exception", new BsonString(t.getClass().getName())); if (t.getMessage() != null) { if (t instanceof JSONParseException) { nrep.addProperty("exception message", new BsonString("invalid json")); } else { nrep.addProperty("exception message", new BsonString( avoidEscapedChars(t.getMessage()))); } } if (includeStackTrace) { BsonArray stackTrace = getStackTraceJson(t); if (stackTrace != null) { nrep.addProperty("stack trace", stackTrace); } } rep.addRepresentation("rh:exception", nrep); } // add warnings if (context != null && context.getWarnings() != null) { context.getWarnings().forEach(w -> rep.addWarning(w)); } return rep; } private static BsonArray getStackTraceJson(Throwable t) { if (t == null || t.getStackTrace() == null) { return null; } StringWriter sw = new StringWriter(); PrintWriter pw = new PrintWriter(sw); t.printStackTrace(pw); String st = sw.toString(); st = avoidEscapedChars(st); String[] lines = st.split("\n"); BsonArray list = new BsonArray(); for (String line : lines) { list.add(new BsonString(line)); } return list; } private static String avoidEscapedChars(String s) { return s == null ? null : s .replaceAll("\"", "'") .replaceAll("\t", " "); } /** * Set the ETag in the response's header * * @param exchange * @param etag */ protected static void setETagHeader(final HttpServerExchange exchange, final String etag) { exchange.getResponseHeaders().put(Headers.ETAG, etag); } /** * * @param exchange * @param properties */ public static void injectEtagHeader( HttpServerExchange exchange, BsonDocument properties) { if (properties == null) { return; } BsonValue _etag = properties.get("_etag"); if (_etag == null) { return; } if (_etag.isObjectId()) { setETagHeader(exchange, _etag.asObjectId().getValue().toString()); } else if (_etag.isString()) { setETagHeader(exchange, _etag.asString().getValue()); } } /** * * @param exchange * @param properties */ public static void injectEtagHeader( HttpServerExchange exchange, Document properties) { if (properties == null) { return; } Object _etag = properties.get("_etag"); if (_etag == null) { return; } if (_etag instanceof ObjectId) { setETagHeader(exchange, _etag.toString()); } else if (_etag instanceof String) { setETagHeader(exchange, (String) _etag); } } /** * * @param exchange * @param etag */ public static void injectEtagHeader( HttpServerExchange exchange, Object etag) { if (etag == null) { return; } if (etag instanceof BsonValue) { BsonValue _etag = (BsonValue) etag; if (_etag.isObjectId()) { setETagHeader(exchange, _etag.asObjectId().getValue().toString()); } else if (_etag.isString()) { setETagHeader(exchange, _etag.asString().getValue()); } } else if (etag instanceof ObjectId) { setETagHeader(exchange, etag.toString()); } else if (etag instanceof String) { setETagHeader(exchange, (String) etag); } } /** * * @param code mongodb error code from MongoException.getCode() * @return */ public static int getHttpStatusFromErrorCode(int code) { switch (code) { case 13: // The MongoDB user does not have enough permissions to execute this operation. return HttpStatus.SC_FORBIDDEN; case 18: // Wrong MongoDB user credentials return HttpStatus.SC_FORBIDDEN; case 61: // Write request for sharded collection must specify the shardkey. return HttpStatus.SC_BAD_REQUEST; case 66: // Update tried to change the immutable shardkey return HttpStatus.SC_FORBIDDEN; case 121: //Document failed validation return HttpStatus.SC_BAD_REQUEST; default: // Other return HttpStatus.SC_INTERNAL_SERVER_ERROR; } } /** * * @param code mongodb error code from MongoException.getCode() * @return */ public static String getMessageFromErrorCode(int code) { switch (code) { case 13: return "The MongoDB user does not have enough " + "permissions to execute this operation."; case 18: return "Wrong MongoDB user credentials " + "(wrong password or need to specify the " + "authentication dababase " + "with 'authSource=<db>' option in mongo-uri)."; case 61: return "Write request for sharded " + "collection must specify the shardkey. " + "Use the 'shardkey' query parameter."; case 66: return "Update tried to change the immutable shardkey."; case 121: //Document failed validation return "Document failed collection validation."; default: return "Error handling the request, " + "see log for more information"; } } private ResponseHelper() { } }