1 /*
2  * Copyright (C) 2014 Square, Inc.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package com.squareup.okhttp.curl;
17 
18 import com.google.common.base.Joiner;
19 import com.squareup.okhttp.ConnectionPool;
20 import com.squareup.okhttp.Headers;
21 import com.squareup.okhttp.MediaType;
22 import com.squareup.okhttp.OkHttpClient;
23 import com.squareup.okhttp.Protocol;
24 import com.squareup.okhttp.Request;
25 import com.squareup.okhttp.RequestBody;
26 import com.squareup.okhttp.Response;
27 import com.squareup.okhttp.internal.http.StatusLine;
28 import com.squareup.okhttp.internal.framed.Http2;
29 
30 import io.airlift.command.Arguments;
31 import io.airlift.command.Command;
32 import io.airlift.command.HelpOption;
33 import io.airlift.command.Option;
34 import io.airlift.command.SingleCommand;
35 import java.io.IOException;
36 import java.io.InputStream;
37 import java.security.cert.CertificateException;
38 import java.security.cert.X509Certificate;
39 import java.util.List;
40 import java.util.Properties;
41 import java.util.logging.ConsoleHandler;
42 import java.util.logging.Level;
43 import java.util.logging.LogRecord;
44 import java.util.logging.Logger;
45 import java.util.logging.SimpleFormatter;
46 import javax.net.ssl.HostnameVerifier;
47 import javax.net.ssl.SSLContext;
48 import javax.net.ssl.SSLSession;
49 import javax.net.ssl.SSLSocketFactory;
50 import javax.net.ssl.TrustManager;
51 import javax.net.ssl.X509TrustManager;
52 import okio.BufferedSource;
53 import okio.Okio;
54 import okio.Sink;
55 
56 import static java.util.concurrent.TimeUnit.SECONDS;
57 
58 @Command(name = Main.NAME, description = "A curl for the next-generation web.")
59 public class Main extends HelpOption implements Runnable {
60   static final String NAME = "okcurl";
61   static final int DEFAULT_TIMEOUT = -1;
62 
fromArgs(String... args)63   static Main fromArgs(String... args) {
64     return SingleCommand.singleCommand(Main.class).parse(args);
65   }
66 
main(String... args)67   public static void main(String... args) {
68     fromArgs(args).run();
69   }
70 
versionString()71   private static String versionString() {
72     try {
73       Properties prop = new Properties();
74       InputStream in = Main.class.getResourceAsStream("/okcurl-version.properties");
75       prop.load(in);
76       in.close();
77       return prop.getProperty("version");
78     } catch (IOException e) {
79       throw new AssertionError("Could not load okcurl-version.properties.");
80     }
81   }
82 
protocols()83   private static String protocols() {
84     return Joiner.on(", ").join(Protocol.values());
85   }
86 
87   @Option(name = { "-X", "--request" }, description = "Specify request command to use")
88   public String method;
89 
90   @Option(name = { "-d", "--data" }, description = "HTTP POST data")
91   public String data;
92 
93   @Option(name = { "-H", "--header" }, description = "Custom header to pass to server")
94   public List<String> headers;
95 
96   @Option(name = { "-A", "--user-agent" }, description = "User-Agent to send to server")
97   public String userAgent = NAME + "/" + versionString();
98 
99   @Option(name = "--connect-timeout", description = "Maximum time allowed for connection (seconds)")
100   public int connectTimeout = DEFAULT_TIMEOUT;
101 
102   @Option(name = "--read-timeout", description = "Maximum time allowed for reading data (seconds)")
103   public int readTimeout = DEFAULT_TIMEOUT;
104 
105   @Option(name = { "-L", "--location" }, description = "Follow redirects")
106   public boolean followRedirects;
107 
108   @Option(name = { "-k", "--insecure" },
109       description = "Allow connections to SSL sites without certs")
110   public boolean allowInsecure;
111 
112   @Option(name = { "-i", "--include" }, description = "Include protocol headers in the output")
113   public boolean showHeaders;
114 
115   @Option(name = "--frames", description = "Log HTTP/2 frames to STDERR")
116   public boolean showHttp2Frames;
117 
118   @Option(name = { "-e", "--referer" }, description = "Referer URL")
119   public String referer;
120 
121   @Option(name = { "-V", "--version" }, description = "Show version number and quit")
122   public boolean version;
123 
124   @Arguments(title = "url", description = "Remote resource URL")
125   public String url;
126 
127   private OkHttpClient client;
128 
run()129   @Override public void run() {
130     if (showHelpIfRequested()) {
131       return;
132     }
133     if (version) {
134       System.out.println(NAME + " " + versionString());
135       System.out.println("Protocols: " + protocols());
136       return;
137     }
138 
139     if (showHttp2Frames) {
140       enableHttp2FrameLogging();
141     }
142 
143     client = createClient();
144     Request request = createRequest();
145     try {
146       Response response = client.newCall(request).execute();
147       if (showHeaders) {
148         System.out.println(StatusLine.get(response));
149         Headers headers = response.headers();
150         for (int i = 0, size = headers.size(); i < size; i++) {
151           System.out.println(headers.name(i) + ": " + headers.value(i));
152         }
153         System.out.println();
154       }
155 
156       // Stream the response to the System.out as it is returned from the server.
157       Sink out = Okio.sink(System.out);
158       BufferedSource source = response.body().source();
159       while (!source.exhausted()) {
160         out.write(source.buffer(), source.buffer().size());
161         out.flush();
162       }
163 
164       response.body().close();
165     } catch (IOException e) {
166       e.printStackTrace();
167     } finally {
168       close();
169     }
170   }
171 
createClient()172   private OkHttpClient createClient() {
173     OkHttpClient client = new OkHttpClient();
174     client.setFollowSslRedirects(followRedirects);
175     if (connectTimeout != DEFAULT_TIMEOUT) {
176       client.setConnectTimeout(connectTimeout, SECONDS);
177     }
178     if (readTimeout != DEFAULT_TIMEOUT) {
179       client.setReadTimeout(readTimeout, SECONDS);
180     }
181     if (allowInsecure) {
182       client.setSslSocketFactory(createInsecureSslSocketFactory());
183       client.setHostnameVerifier(createInsecureHostnameVerifier());
184     }
185     // If we don't set this reference, there's no way to clean shutdown persistent connections.
186     client.setConnectionPool(ConnectionPool.getDefault());
187     return client;
188   }
189 
getRequestMethod()190   private String getRequestMethod() {
191     if (method != null) {
192       return method;
193     }
194     if (data != null) {
195       return "POST";
196     }
197     return "GET";
198   }
199 
getRequestBody()200   private RequestBody getRequestBody() {
201     if (data == null) {
202       return null;
203     }
204     String bodyData = data;
205 
206     String mimeType = "application/x-form-urlencoded";
207     if (headers != null) {
208       for (String header : headers) {
209         String[] parts = header.split(":", -1);
210         if ("Content-Type".equalsIgnoreCase(parts[0])) {
211           mimeType = parts[1].trim();
212           headers.remove(header);
213           break;
214         }
215       }
216     }
217 
218     return RequestBody.create(MediaType.parse(mimeType), bodyData);
219   }
220 
createRequest()221   Request createRequest() {
222     Request.Builder request = new Request.Builder();
223 
224     request.url(url);
225     request.method(getRequestMethod(), getRequestBody());
226 
227     if (headers != null) {
228       for (String header : headers) {
229         String[] parts = header.split(":", 2);
230         request.header(parts[0], parts[1]);
231       }
232     }
233     if (referer != null) {
234       request.header("Referer", referer);
235     }
236     request.header("User-Agent", userAgent);
237 
238     return request.build();
239   }
240 
close()241   private void close() {
242     client.getConnectionPool().evictAll(); // Close any persistent connections.
243   }
244 
createInsecureSslSocketFactory()245   private static SSLSocketFactory createInsecureSslSocketFactory() {
246     try {
247       SSLContext context = SSLContext.getInstance("TLS");
248       TrustManager permissive = new X509TrustManager() {
249         @Override public void checkClientTrusted(X509Certificate[] chain, String authType)
250             throws CertificateException {
251         }
252 
253         @Override public void checkServerTrusted(X509Certificate[] chain, String authType)
254             throws CertificateException {
255         }
256 
257         @Override public X509Certificate[] getAcceptedIssuers() {
258           return null;
259         }
260       };
261       context.init(null, new TrustManager[] { permissive }, null);
262       return context.getSocketFactory();
263     } catch (Exception e) {
264       throw new AssertionError(e);
265     }
266   }
267 
createInsecureHostnameVerifier()268   private static HostnameVerifier createInsecureHostnameVerifier() {
269     return new HostnameVerifier() {
270       @Override public boolean verify(String s, SSLSession sslSession) {
271         return true;
272       }
273     };
274   }
275 
276   private static void enableHttp2FrameLogging() {
277     Logger logger = Logger.getLogger(Http2.class.getName() + "$FrameLogger");
278     logger.setLevel(Level.FINE);
279     ConsoleHandler handler = new ConsoleHandler();
280     handler.setLevel(Level.FINE);
281     handler.setFormatter(new SimpleFormatter() {
282       @Override public String format(LogRecord record) {
283         return String.format("%s%n", record.getMessage());
284       }
285     });
286     logger.addHandler(handler);
287   }
288 }
289