package org.zaproxy.zap.extension.api; import java.io.BufferedWriter; import java.io.IOException; import java.io.Writer; import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import org.apache.commons.lang.StringUtils; public class GoAPIGenerator extends AbstractAPIGenerator { private boolean addImports = false; private final String HEADER = "// Zed Attack Proxy (ZAP) and its related class files.\n" + "//\n" + "// ZAP is an HTTP/HTTPS proxy for assessing web application security.\n" + "//\n" + "// Copyright 2016 the ZAP development team\n" + "//\n" + "// Licensed under the Apache License, Version 2.0 (the \"License\");\n" + "// you may not use this file except in compliance with the License.\n" + "// You may obtain a copy of the License at\n" + "//\n" + "// http://www.apache.org/licenses/LICENSE-2.0\n" + "//\n" + "// Unless required by applicable law or agreed to in writing, software\n" + "// distributed under the License is distributed on an \"AS IS\" BASIS,\n" + "// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n" + "// See the License for the specific language governing permissions and\n" + "// limitations under the License.\n"; /** * Map any names which are reserved in Go to something legal */ private static final Map<String, String> nameMap; static { Map<String, String> initMap = new HashMap<>(); initMap.put("break", "brk"); initMap.put("continue", "cont"); nameMap = Collections.unmodifiableMap(initMap); } public GoAPIGenerator() { super("../zap-api-go/zap/"); } public GoAPIGenerator(String path, boolean optional) { super(path, optional); } /** * Generates the API client files of the given API implementors. * * @param implementors the implementors * @throws IOException if an error occurred while generating the APIs. * @deprecated (2.6.0) Use {@link #generateAPIFiles(List)} instead. */ @Deprecated public void generateGoFiles(List<ApiImplementor> implementors) throws IOException { this.generateAPIFiles(implementors); } @Override protected void generateAPIFiles(ApiImplementor imp) throws IOException { String className = imp.getPrefix().substring(0, 1).toUpperCase() + imp.getPrefix().substring(1); String pkgName = safeName(camelCaseToLowerCaseDash(className)); Path file = getDirectory().resolve(pkgName + ".go"); System.out.println("Generating " + file.toAbsolutePath()); try (BufferedWriter out = Files.newBufferedWriter(file, StandardCharsets.UTF_8)) { out.write(HEADER); out.write("//\n"); out.write("// *** This file was automatically generated. ***\n"); out.write("//\n\n"); out.write("package zap\n\n"); out.write("_imports_"); out.write("type " + className + " struct {}" + "\n\n"); for (ApiElement view : imp.getApiViews()) { this.generateGoElement(view, className, imp.getPrefix(), VIEW_ENDPOINT, out); } for (ApiElement action : imp.getApiActions()) { this.generateGoElement(action, className, imp.getPrefix(), ACTION_ENDPOINT, out); } for (ApiElement other : imp.getApiOthers()) { this.generateGoElement(other, className, imp.getPrefix(), OTHER_ENDPOINT, out); } } addImports(file, addImports); addImports = false; } private void generateGoElement(ApiElement element, String className, String component, String type, Writer out) throws IOException { boolean typeOther = type.equals(OTHER_ENDPOINT); boolean hasParams = (element.getMandatoryParamNames() != null && element.getMandatoryParamNames().size() > 0) || (element.getOptionalParamNames() != null && element.getOptionalParamNames().size() > 0); // Add description if defined String descTag = element.getDescriptionTag(); if (descTag == null) { // This is the default, but it can be overridden by the getDescriptionTag method if required descTag = component + ".api." + type + "." + element.getName(); } try { String desc = getMessages().getString(descTag); out.write("// " + desc + "\n"); if (isOptional()) { out.write("//\n"); out.write("// " + OPTIONAL_MESSAGE + "\n"); } } catch (Exception e) { // Might not be set, so just print out the ones that are missing System.out.println("No i18n for: " + descTag); if (isOptional()) { out.write("// " + OPTIONAL_MESSAGE + "\n"); } } // Function declaration out.write("func (" + className.substring(0, 1).toLowerCase() + " " + className + ") "); out.write(toTitleCase(createMethodName(element.getName()))); out.write("("); // Iterate through and write out mandatory function arguments writeOutArgs(element.getMandatoryParamNames(), out, hasParams); if (element.getMandatoryParamNames() != null && element.getMandatoryParamNames().size() > 0 && element.getOptionalParamNames() != null && element.getOptionalParamNames().size() > 0) { out.write(", "); } // Iterate through and write out optional function arguments writeOutArgs(element.getOptionalParamNames(), out, hasParams); out.write(")"); // Function return types if (typeOther) { out.write(" ([]byte, error) "); } else { out.write(" (interface{}, error) "); } out.write("{\n"); // Function content if (hasParams) { out.write("\tm := map[string]string{"); // Iterate through and write out mandatory request parameters writeOutRequestParams(element.getMandatoryParamNames(), out); // Iterate through and write out optional request parameters writeOutRequestParams(element.getOptionalParamNames(), out); out.write("\n\t}\n"); } String method = "Request"; if (typeOther) { method += "Other"; } out.write("\treturn " + method + "(\"" + component + "/" + type + "/" + element.getName() + "/\""); if (hasParams) { out.write(", m"); } out.write(")\n"); out.write("}\n\n"); } // Writes the function arguments private void writeOutArgs(List<String> elements, Writer out, boolean hasParams) throws IOException { if (elements != null && elements.size() > 0) { ArrayList<String> args = new ArrayList<String>(); for (String param : elements) { if (param.toLowerCase().equals("boolean")) { args.add("boolean bool"); } else if (param.toLowerCase().equals("integer")) { args.add("i int"); } else if (param.toLowerCase().equals("string")) { args.add("str string"); } else if (param.toLowerCase().equals("type")) { args.add("t string"); } else { args.add(param.toLowerCase() + " string"); } } out.write(StringUtils.join(args, ", ")); } } // Writes the request parameters private void writeOutRequestParams(List<String> elements, Writer out) throws IOException { if (elements != null && elements.size() > 0) { for (String param : elements) { out.write("\n\t\t\"" + param + "\": "); if (param.toLowerCase().equals("boolean")) { addImports = true; out.write("strconv.FormatBool(boolean)"); } else if (param.toLowerCase().equals("integer")) { addImports = true; out.write("strconv.Itoa(i)"); } else if (param.toLowerCase().equals("string")) { out.write("str"); } else if (param.toLowerCase().equals("type")) { out.write("t"); } else { out.write(param.toLowerCase()); } out.write(","); } } } private static String safeName(String name) { if (nameMap.containsKey(name)) { return nameMap.get(name); } return name; } private static String createMethodName(String name) { if (nameMap.containsKey(name)) { name = nameMap.get(name); } return removeAllFullStopCharacters(name); } private static String removeAllFullStopCharacters(String string) { return string.replaceAll("\\.", ""); } public static String camelCaseToLowerCaseDash(String s) { // Ripped off / inspired by // http://stackoverflow.com/questions/2559759/how-do-i-convert-camelcase-into-human-readable-names-in-java return safeName(s).replaceAll( String.format("%s|%s|%s", "(?<=[A-Z])(?=[A-Z][a-z])", "(?<=[^A-Z])(?=[A-Z])", "(?<=[A-Za-z])(?=[^A-Za-z])"), "-").toLowerCase(); } private static String toTitleCase(String input) { StringBuilder titleCase = new StringBuilder(); boolean nextTitleCase = true; for (char c : input.toCharArray()) { if (Character.isSpaceChar(c)) { nextTitleCase = true; } else if (nextTitleCase) { c = Character.toTitleCase(c); nextTitleCase = false; } titleCase.append(c); } return titleCase.toString(); } // It replaces the placeholder _imports_ with the proper import packages, // or removes it in case there isn't any packages to import private static void addImports(Path file, boolean addImports) throws IOException { Charset charset = StandardCharsets.UTF_8; String content = new String(Files.readAllBytes(file), charset); if (addImports) { content = content.replaceAll("_imports_", "import \"strconv\"\n\n"); } else { content = content.replaceAll("_imports_", ""); } Files.write(file, content.getBytes(charset)); } public static void main(String[] args) throws Exception { // Command for generating a java version of the ZAP API GoAPIGenerator gapi = new GoAPIGenerator(); gapi.generateCoreAPIFiles(); } }