/* * Zed Attack Proxy (ZAP) and its related class files. * * ZAP is an HTTP/HTTPS proxy for assessing web application security. * * 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 org.zaproxy.zap.extension.api; import java.io.BufferedWriter; import java.io.IOException; import java.io.Writer; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; public class PythonAPIGenerator extends AbstractAPIGenerator { /** * Default output directory in zap-api-python project. */ private static final String DEFAULT_OUTPUT_DIR = "../zap-api-python/src/zapv2/"; 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" + "\"\"\"\n" + "This file was automatically generated.\n" + "\"\"\"\n\n"; /** * Map any names which are reserved in python 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 PythonAPIGenerator() { super(DEFAULT_OUTPUT_DIR); } public PythonAPIGenerator(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 generatePythonFiles(List<ApiImplementor> implementors) throws IOException { this.generateAPIFiles(implementors); } private void generatePythonElement(ApiElement element, String component, String type, Writer out) throws IOException { boolean hasParams = (element.getMandatoryParamNames() != null && element.getMandatoryParamNames().size() > 0) || (element.getOptionalParamNames() != null && element.getOptionalParamNames().size() > 0); if (!hasParams && type.equals(VIEW_ENDPOINT)) { out.write(" @property\n"); } out.write(" def " + createFunctionName(element.getName()) + "(self"); if (element.getMandatoryParamNames() != null) { for (String param : element.getMandatoryParamNames()) { out.write(", " + param.toLowerCase()); } } if (element.getOptionalParamNames() != null) { for (String param : element.getOptionalParamNames()) { out.write(", " + param.toLowerCase() + "=None"); } } if (type.equals(ACTION_ENDPOINT) || type.equals(OTHER_ENDPOINT)) { // Always add the API key - we've no way of knowing if it will be required or not out.write(", " + API.API_KEY_PARAM + "=''"); hasParams = true; } out.write("):\n"); // Add description if defined String descTag = element.getDescriptionTag(); if (descTag == null) { // This is the default, but it can be overriden by the getDescriptionTag method if required descTag = component + ".api." + type + "." + element.getName(); } try { String desc = getMessages().getString(descTag); out.write(" \"\"\"\n"); out.write(" " + desc + "\n"); if (isOptional()) { out.write(" " + OPTIONAL_MESSAGE + "\n"); } out.write(" \"\"\"\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(" \"\"\"\n"); out.write(" " + OPTIONAL_MESSAGE + "\n"); out.write(" \"\"\"\n"); } } String method = "_request"; String baseUrl = "base"; if (type.equals(OTHER_ENDPOINT)) { method += "_other"; baseUrl += "_other"; } StringBuilder reqParams = new StringBuilder(); if (hasParams) { reqParams.append("{"); boolean first = true; if (element.getMandatoryParamNames() != null) { for (String param : element.getMandatoryParamNames()) { if (first) { first = false; } else { reqParams.append(", "); } reqParams.append("'" + param + "' : " + param.toLowerCase()); } } if (type.equals(ACTION_ENDPOINT) || type.equals(OTHER_ENDPOINT)) { // Always add the API key - we've no way of knowing if it will be required or not if (!first) { reqParams.append(", "); } reqParams.append("'").append(API.API_KEY_PARAM).append("' : ").append(API.API_KEY_PARAM); } reqParams.append("}"); if (element.getOptionalParamNames() != null && !element.getOptionalParamNames().isEmpty()) { out.write(" params = "); out.write(reqParams.toString()); out.write("\n"); reqParams.replace(0, reqParams.length(), "params"); for (String param : element.getOptionalParamNames()) { out.write(" if " + param.toLowerCase() + " is not None:\n"); out.write(" params['" + param + "'] = " + param.toLowerCase() + "\n"); } } } if (type.equals(OTHER_ENDPOINT)) { out.write(" return ("); } else { out.write(" return next("); } out.write("self.zap." + method + "(self.zap." + baseUrl + " + '" + component + "/" + type + "/" + element.getName() + "/'"); // , {'url': url})) if (hasParams) { out.write(", "); out.write(reqParams.toString()); out.write(")"); if (!type.equals(OTHER_ENDPOINT)) { out.write(".itervalues())"); } else { out.write(")"); } } else if (!type.equals(OTHER_ENDPOINT)) { out.write(").itervalues())"); } else { out.write(")"); } out.write("\n\n"); } @Override protected void generateAPIFiles(ApiImplementor imp) throws IOException { Path file = getDirectory().resolve(createFileName(imp.getPrefix())); System.out.println("Generating " + file.toAbsolutePath()); try (BufferedWriter out = Files.newBufferedWriter(file, StandardCharsets.UTF_8)) { out.write(HEADER); out.write("class " + safeName(imp.getPrefix()) + "(object):\n\n"); out.write(" def __init__(self, zap):\n"); out.write(" self.zap = zap\n"); out.write("\n"); for (ApiElement view : imp.getApiViews()) { this.generatePythonElement(view, imp.getPrefix(), VIEW_ENDPOINT, out); } for (ApiElement action : imp.getApiActions()) { this.generatePythonElement(action, imp.getPrefix(), ACTION_ENDPOINT, out); } for (ApiElement other : imp.getApiOthers()) { this.generatePythonElement(other, imp.getPrefix(), OTHER_ENDPOINT, out); } out.write("\n"); } } private static String safeName (String name) { if (nameMap.containsKey(name)) { return nameMap.get(name); } return name; } private static String createFileName(String name) { return safeName(name) + ".py"; } private static String createFunctionName(String name) { return removeAllFullStopCharacters(camelCaseToLcUnderscores(safeName(name))); } private static String removeAllFullStopCharacters(String string) { return string.replaceAll("\\.", ""); } public static String camelCaseToLcUnderscores(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(); } public static void main(String[] args) throws Exception { // Command for generating a python version of the ZAP API if (!Files.exists(Paths.get(DEFAULT_OUTPUT_DIR))) { System.err.println("The directory does not exist: " + Paths.get(DEFAULT_OUTPUT_DIR).toAbsolutePath()); System.exit(1); } PythonAPIGenerator wapi = new PythonAPIGenerator(); wapi.generateCoreAPIFiles(); //System.out.println(camelCaseToLcUnderscores("TestCase")); } }