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

com.github.dzieciou.testing.curl.Http2Curl Maven / Gradle / Ivy

There is a newer version: 3.0.0
Show newest version
/*
 * Copyright (C) 2007, 2008 Apple Inc.  All rights reserved.
 * Copyright (C) 2008, 2009 Anthony Ricaud 
 * Copyright (C) 2011 Google Inc. All rights reserved.
 * Copyright (C) 2016 Maciej Gawinecki 
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1.  Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer.
 * 2.  Redistributions in binary form must reproduce the above copyright
 *     notice, this list of conditions and the following disclaimer in the
 *     documentation and/or other materials provided with the distribution.
 * 3.  Neither the name of Apple Computer, Inc. ("Apple") nor the names of
 *     its contributors may be used to endorse or promote products derived
 *     from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

package com.github.dzieciou.testing.curl;

import io.restassured.internal.multipart.RestAssuredMultiPartEntity;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.lang.reflect.Field;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Base64;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import org.apache.http.Header;
import org.apache.http.HttpEntity;
import org.apache.http.HttpEntityEnclosingRequest;
import org.apache.http.HttpRequest;
import org.apache.http.client.methods.HttpRequestWrapper;
import org.apache.http.entity.mime.FormBodyPart;
import org.apache.http.entity.mime.MultipartEntityBuilder;
import org.apache.http.entity.mime.content.ContentBody;
import org.apache.http.impl.client.RequestWrapper;
import org.apache.http.message.BasicHeader;
import org.apache.http.util.EntityUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;


/**
 * Generates CURL command for a given HTTP request.
 */
@SuppressWarnings("deprecation")
public class Http2Curl {

  private static final Logger log = LoggerFactory.getLogger(Http2Curl.class);

  private final Options options;

  public Http2Curl(Options options) {
    this.options = options;
  }

  private static String getContent(FormBodyPart bodyPart) throws IOException {
    ContentBody content = bodyPart.getBody();
    ByteArrayOutputStream out = new ByteArrayOutputStream((int) content.getContentLength());
    content.writeTo(out);
    return out.toString();
  }

  private static String removeQuotes(String s) {
    return s.replaceAll("^\"|\"$", "");
  }

  private static boolean isBasicAuthentication(Header h) {
    return h.getName().equals("Authorization") && h.getValue().startsWith("Basic");
  }

  @SuppressWarnings("deprecation")
  private static String getOriginalRequestUri(HttpRequest request) {
    if (request instanceof HttpRequestWrapper) {
      return ((HttpRequestWrapper) request).getOriginal().getRequestLine().getUri();
    } else if (request instanceof RequestWrapper) {
      return ((RequestWrapper) request).getOriginal().getRequestLine().getUri();
    } else {
      throw new IllegalArgumentException("Unsupported request class type: " + request.getClass());
    }
  }

  private static String getHost(HttpRequest request) {
    return tryGetHeaderValue(Arrays.asList(request.getAllHeaders()), "Host")
        .orElseGet(() -> URI.create(getOriginalRequestUri(request)).getHost());
  }

  private static boolean isValidUrl(String url) {
    try {
      new URL(url);
      return true;
    } catch (MalformedURLException e) {
      return false;
    }
  }

  private static Optional tryGetHeaderValue(List
headers, String headerName) { return headers .stream() .filter(h -> h.getName().equals(headerName)) .map(Header::getValue) .findFirst(); } private static Object getFieldValue(T obj, String fieldName) throws NoSuchFieldException, IllegalAccessException { Field f = getField(obj.getClass(), fieldName); f.setAccessible(true); return f.get(obj); } private static Field getField(Class clazz, String fieldName) throws NoSuchFieldException { try { return clazz.getDeclaredField(fieldName); } catch (NoSuchFieldException e) { Class superClass = clazz.getSuperclass(); if (superClass == null) { throw e; } else { return getField(superClass, fieldName); } } } /** * Generates single-line CURL command for a given HTTP request. * * @param request HTTP request * @return CURL command * @throws Exception if failed to generate CURL command */ public String generateCurl(HttpRequest request) throws Exception { CurlCommand curl = http2curl(request); options.getCurlUpdater().ifPresent(updater -> updater.accept(curl)); return curl .asString(options.getTargetPlatform(), options.useShortForm(), options.printMultiliner(), options.escapeNonAscii()); } private static class Headers { List
toProcess; Set ignored; public Headers(List
toProcess) { this.toProcess = toProcess; this.ignored = new HashSet<>(); } } @SuppressWarnings("deprecation") private CurlCommand http2curl(HttpRequest request) throws NoSuchFieldException, IllegalAccessException, IOException { Headers headers = new Headers(Arrays.asList(request.getAllHeaders())); CurlCommand curl = new CurlCommand(); String inferredUri = inferUri(request); curl.setUrl(inferredUri); if (request instanceof HttpEntityEnclosingRequest) { HttpEntityEnclosingRequest requestWithEntity = (HttpEntityEnclosingRequest) request; try { HttpEntity entity = requestWithEntity.getEntity(); if (entity != null) { Optional maybeRequestContentType = tryGetHeaderValue(headers.toProcess, "Content-Type"); String contentType = maybeRequestContentType .orElseThrow(() -> new IllegalStateException("Missing Content-Type header")); handleEntity(entity, contentType, headers, curl); } } catch (IOException e) { log.error("Failed to consume form data (entity) from HTTP request", e); throw e; } } String requestMethod = request.getRequestLine().getMethod(); if ("GET".equals(requestMethod)) { // skip } else if ("POST".equals(requestMethod) && curl.hasData()) { // skip } else { curl.setMethod(requestMethod); } headers.toProcess = handleAuthenticationHeader(headers.toProcess, curl); List
cookiesHeaders = headers.toProcess.stream() .filter(h -> h.getName().equals("Cookie")) .collect(Collectors.toList()); if (cookiesHeaders.size() == 1) { curl.setCookieHeader(cookiesHeaders.get(0).getValue()); headers.toProcess = headers.toProcess.stream().filter(h -> !h.getName().equals("Cookie")) .collect(Collectors.toList()); } else if (cookiesHeaders.size() > 1) { // RFC 6265: When the user agent generates an HTTP request, the user agent MUST NOT attach // more than one Cookie header field. log.warn("More than one Cookie header in HTTP Request not allowed by RFC 6265"); } handleNotIgnoredHeaders(headers, curl); curl.setCompressed(true); curl.setInsecure(true); curl.setVerbose(true); return curl; } // The method updates headers and curl arguments private void handleEntity(HttpEntity entity, String contentType, Headers headers, CurlCommand curl) throws IOException { List parameters = Arrays.asList(contentType.split(";")); parameters = parameters.stream().map(s -> s.trim()).collect(Collectors.toList()); contentType = parameters.remove(0); headers.ignored.add("Content-Length"); switch (contentType) { case "multipart/form-data": headers.ignored.add("Content-Type"); // let curl command decide handleMultipartEntity(entity, curl); break; case "multipart/mixed": // Removing header headers.toProcess = filterOutHeader(headers.toProcess, "Content-Type"); headers.toProcess.add(new BasicHeader("Content-Type", "multipart/mixed")); handleMultipartEntity(entity, curl); break; default: String data = EntityUtils.toString(entity); curl.addDataBinary(data); } } private List
filterOutHeader(List
headers, String s) { return headers.stream().filter(h -> !h.getName().equals(s)) .collect(Collectors.toList()); } private String inferUri(HttpRequest request) { String inferredUri = request.getRequestLine().getUri(); if (!isValidUrl(inferredUri)) { // Missing schema and domain name String host = getHost(request); String inferredScheme = "http"; if (host.endsWith(":443")) { inferredScheme = "https"; } else if ((request instanceof RequestWrapper) || (request instanceof HttpRequestWrapper)) { if (getOriginalRequestUri(request).startsWith("https")) { // This is for original URL, so if during redirects we go out of HTTPs, this might be a wrong guess inferredScheme = "https"; } } if ("CONNECT".equals(request.getRequestLine().getMethod())) { inferredUri = String.format("%s://%s", inferredScheme, host); } else { inferredUri = String.format("%s://%s/%s", inferredScheme, host, inferredUri) .replaceAll("(? bodyParts = (List) getFieldValue(multipartEntityBuilder, "bodyParts"); bodyParts.forEach(p -> handlePart(p, curl)); } catch (NoSuchFieldException | IllegalAccessException e) { throw new RuntimeException(e); } } private void handlePart(FormBodyPart bodyPart, CurlCommand curl) { String contentDisposition = bodyPart.getHeader().getFields().stream() .filter(f -> f.getName().equals("Content-Disposition")) .findFirst() .orElseThrow(() -> new RuntimeException("Multipart missing Content-Disposition header")) .getBody(); List elements = Arrays.asList(contentDisposition.split(";")); Map map = elements.stream().map(s -> s.trim().split("=")) .collect(Collectors.toMap(a -> a[0], a -> a.length == 2 ? a[1] : "")); if (map.containsKey("form-data")) { String partName = removeQuotes(map.get("name")); StringBuilder partContent = new StringBuilder(); if (map.get("filename") != null) { partContent.append("@").append(removeQuotes(map.get("filename"))); } else { try { partContent.append(getContent(bodyPart)); } catch (IOException e) { throw new RuntimeException("Could not read content of the part", e); } } partContent.append(";type=").append(bodyPart.getHeader().getField("Content-Type").getBody()); curl.addFormPart(partName, partContent.toString()); } else { throw new RuntimeException("Unsupported type " + map.entrySet().stream().findFirst().get()); } } private void handleNotIgnoredHeaders(Headers headers, CurlCommand curl) { headers.toProcess .stream() .filter(h -> !headers.ignored.contains(h.getName())) .forEach(h -> curl.addHeader(h.getName(), h.getValue())); } private List
handleAuthenticationHeader(List
headers, CurlCommand curl) { List
remainingHeaders = new ArrayList<>(headers); Iterator
it = remainingHeaders.iterator(); while (it.hasNext()) { Header h = it.next(); if (isBasicAuthentication(h)) { try { String credentials = h.getValue().replaceAll("Basic ", ""); String decodedCredentials = new String(Base64.getDecoder().decode(credentials)); String[] userAndPassword = decodedCredentials.split(":", -1); curl.setServerAuthentication(userAndPassword[0], userAndPassword[1]); it.remove(); break; // There can be only one authentication headers } catch (IllegalArgumentException | IndexOutOfBoundsException e) { log.warn("This is not valid Basic authentication header: {}", h.getValue()); } } } return remainingHeaders; } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy