diff options
Diffstat (limited to 'gerrit-httpd/src/main/java/com/google/gerrit/httpd/restapi/ParameterParser.java')
-rw-r--r-- | gerrit-httpd/src/main/java/com/google/gerrit/httpd/restapi/ParameterParser.java | 298 |
1 files changed, 0 insertions, 298 deletions
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/restapi/ParameterParser.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/restapi/ParameterParser.java deleted file mode 100644 index bfaf0c789e..0000000000 --- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/restapi/ParameterParser.java +++ /dev/null @@ -1,298 +0,0 @@ -// Copyright (C) 2012 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package com.google.gerrit.httpd.restapi; - -import static com.google.gerrit.httpd.restapi.RestApiServlet.ALLOWED_CORS_METHODS; -import static com.google.gerrit.httpd.restapi.RestApiServlet.XD_AUTHORIZATION; -import static com.google.gerrit.httpd.restapi.RestApiServlet.XD_CONTENT_TYPE; -import static com.google.gerrit.httpd.restapi.RestApiServlet.XD_METHOD; -import static com.google.gerrit.httpd.restapi.RestApiServlet.replyBinaryResult; -import static com.google.gerrit.httpd.restapi.RestApiServlet.replyError; -import static javax.servlet.http.HttpServletResponse.SC_BAD_REQUEST; - -import com.google.auto.value.AutoValue; -import com.google.common.annotations.VisibleForTesting; -import com.google.common.base.Splitter; -import com.google.common.base.Strings; -import com.google.common.collect.ImmutableListMultimap; -import com.google.common.collect.ImmutableSet; -import com.google.common.collect.Iterables; -import com.google.common.collect.ListMultimap; -import com.google.common.collect.MultimapBuilder; -import com.google.gerrit.common.Nullable; -import com.google.gerrit.extensions.registration.DynamicMap; -import com.google.gerrit.extensions.restapi.BadRequestException; -import com.google.gerrit.extensions.restapi.BinaryResult; -import com.google.gerrit.extensions.restapi.Url; -import com.google.gerrit.server.DynamicOptions; -import com.google.gerrit.util.cli.CmdLineParser; -import com.google.gson.JsonArray; -import com.google.gson.JsonElement; -import com.google.gson.JsonObject; -import com.google.gson.JsonPrimitive; -import com.google.gwtexpui.server.CacheHeaders; -import com.google.inject.Inject; -import com.google.inject.Injector; -import java.io.IOException; -import java.io.StringWriter; -import java.util.HashSet; -import java.util.Iterator; -import java.util.Map; -import java.util.Set; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import org.kohsuke.args4j.CmdLineException; - -public class ParameterParser { - private static final ImmutableSet<String> RESERVED_KEYS = - ImmutableSet.of("pp", "prettyPrint", "strict", "callback", "alt", "fields"); - - @AutoValue - public abstract static class QueryParams { - static final String I = QueryParams.class.getName(); - - static QueryParams create( - @Nullable String accessToken, - @Nullable String xdMethod, - @Nullable String xdContentType, - ImmutableListMultimap<String, String> config, - ImmutableListMultimap<String, String> params) { - return new AutoValue_ParameterParser_QueryParams( - accessToken, xdMethod, xdContentType, config, params); - } - - @Nullable - public abstract String accessToken(); - - @Nullable - abstract String xdMethod(); - - @Nullable - abstract String xdContentType(); - - abstract ImmutableListMultimap<String, String> config(); - - abstract ImmutableListMultimap<String, String> params(); - - boolean hasXdOverride() { - return xdMethod() != null || xdContentType() != null; - } - } - - public static QueryParams getQueryParams(HttpServletRequest req) throws BadRequestException { - QueryParams qp = (QueryParams) req.getAttribute(QueryParams.I); - if (qp != null) { - return qp; - } - - String accessToken = null; - String xdMethod = null; - String xdContentType = null; - ListMultimap<String, String> config = MultimapBuilder.hashKeys(4).arrayListValues().build(); - ListMultimap<String, String> params = MultimapBuilder.hashKeys().arrayListValues().build(); - - String queryString = req.getQueryString(); - if (!Strings.isNullOrEmpty(queryString)) { - for (String kvPair : Splitter.on('&').split(queryString)) { - Iterator<String> i = Splitter.on('=').limit(2).split(kvPair).iterator(); - String key = Url.decode(i.next()); - String val = i.hasNext() ? Url.decode(i.next()) : ""; - - if (XD_AUTHORIZATION.equals(key)) { - if (accessToken != null) { - throw new BadRequestException("duplicate " + XD_AUTHORIZATION); - } - accessToken = val; - } else if (XD_METHOD.equals(key)) { - if (xdMethod != null) { - throw new BadRequestException("duplicate " + XD_METHOD); - } else if (!ALLOWED_CORS_METHODS.contains(val)) { - throw new BadRequestException("invalid " + XD_METHOD); - } - xdMethod = val; - } else if (XD_CONTENT_TYPE.equals(key)) { - if (xdContentType != null) { - throw new BadRequestException("duplicate " + XD_CONTENT_TYPE); - } - xdContentType = val; - } else if (RESERVED_KEYS.contains(key)) { - config.put(key, val); - } else { - params.put(key, val); - } - } - } - - qp = - QueryParams.create( - accessToken, - xdMethod, - xdContentType, - ImmutableListMultimap.copyOf(config), - ImmutableListMultimap.copyOf(params)); - req.setAttribute(QueryParams.I, qp); - return qp; - } - - private final CmdLineParser.Factory parserFactory; - private final Injector injector; - private final DynamicMap<DynamicOptions.DynamicBean> dynamicBeans; - - @Inject - ParameterParser( - CmdLineParser.Factory pf, - Injector injector, - DynamicMap<DynamicOptions.DynamicBean> dynamicBeans) { - this.parserFactory = pf; - this.injector = injector; - this.dynamicBeans = dynamicBeans; - } - - <T> boolean parse( - T param, ListMultimap<String, String> in, HttpServletRequest req, HttpServletResponse res) - throws IOException { - CmdLineParser clp = parserFactory.create(param); - DynamicOptions pluginOptions = new DynamicOptions(param, injector, dynamicBeans); - pluginOptions.parseDynamicBeans(clp); - pluginOptions.setDynamicBeans(); - pluginOptions.onBeanParseStart(); - try { - clp.parseOptionMap(in); - } catch (CmdLineException | NumberFormatException e) { - if (!clp.wasHelpRequestedByOption()) { - replyError(req, res, SC_BAD_REQUEST, e.getMessage(), e); - return false; - } - } - - if (clp.wasHelpRequestedByOption()) { - StringWriter msg = new StringWriter(); - clp.printQueryStringUsage(req.getRequestURI(), msg); - msg.write('\n'); - msg.write('\n'); - clp.printUsage(msg, null); - msg.write('\n'); - CacheHeaders.setNotCacheable(res); - replyBinaryResult(req, res, BinaryResult.create(msg.toString()).setContentType("text/plain")); - return false; - } - pluginOptions.onBeanParseEnd(); - - return true; - } - - private static Set<String> query(HttpServletRequest req) { - Set<String> params = new HashSet<>(); - if (!Strings.isNullOrEmpty(req.getQueryString())) { - for (String kvPair : Splitter.on('&').split(req.getQueryString())) { - params.add(Iterables.getFirst(Splitter.on('=').limit(2).split(kvPair), null)); - } - } - return params; - } - - /** - * Convert a standard URL encoded form input into a parsed JSON tree. - * - * <p>Given an input such as: - * - * <pre> - * message=Does+not+compile.&labels.Verified=-1 - * </pre> - * - * which is easily created using the curl command line tool: - * - * <pre> - * curl --data 'message=Does not compile.' --data labels.Verified=-1 - * </pre> - * - * converts to a JSON object structure that is normally expected: - * - * <pre> - * { - * "message": "Does not compile.", - * "labels": { - * "Verified": "-1" - * } - * } - * </pre> - * - * This input can then be further processed into the Java input type expected by a view using - * Gson. Here we rely on Gson to perform implicit conversion of a string {@code "-1"} to a number - * type when the Java input type expects a number. - * - * <p>Conversion assumes any field name that does not contain {@code "."} will be a property of - * the top level input object. Any field with a dot will use the first segment as the top level - * property name naming an object, and the rest of the field name as a property in the nested - * object. - * - * @param req request to parse form input from and create JSON tree. - * @return the converted JSON object tree. - * @throws BadRequestException the request cannot be cast, as there are conflicting definitions - * for a nested object. - */ - static JsonObject formToJson(HttpServletRequest req) throws BadRequestException { - Map<String, String[]> map = req.getParameterMap(); - return formToJson(map, query(req)); - } - - @VisibleForTesting - static JsonObject formToJson(Map<String, String[]> map, Set<String> query) - throws BadRequestException { - JsonObject inputObject = new JsonObject(); - for (Map.Entry<String, String[]> ent : map.entrySet()) { - String key = ent.getKey(); - String[] values = ent.getValue(); - - if (query.contains(key) || values.length == 0) { - // Disallow processing query parameters as input body fields. - // Implementations of views should avoid duplicate naming. - continue; - } - - JsonObject obj = inputObject; - int dot = key.indexOf('.'); - if (0 <= dot) { - String property = key.substring(0, dot); - JsonElement e = inputObject.get(property); - if (e == null) { - obj = new JsonObject(); - inputObject.add(property, obj); - } else if (e.isJsonObject()) { - obj = e.getAsJsonObject(); - } else { - throw new BadRequestException(String.format("key %s conflicts with %s", key, property)); - } - key = key.substring(dot + 1); - } - - if (obj.get(key) != null) { - // This error should never happen. If all form values are handled - // together in a single pass properties are set only once. Setting - // again indicates something has gone very wrong. - throw new BadRequestException("invalid form input, use JSON instead"); - } else if (values.length == 1) { - obj.addProperty(key, values[0]); - } else { - JsonArray list = new JsonArray(); - for (String v : values) { - list.add(new JsonPrimitive(v)); - } - obj.add(key, list); - } - } - return inputObject; - } -} |