blob: 765c60e8a65ff613f39b55ed087eb92cb07b1516 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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.netbeans.modules.docker;
import java.io.BufferedInputStream;
import java.io.ByteArrayOutputStream;
import java.io.FilterInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.nio.charset.Charset;
import java.nio.charset.UnsupportedCharsetException;
import java.util.Base64;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.netbeans.api.annotations.common.CheckForNull;
import org.netbeans.api.annotations.common.NonNull;
import org.openide.util.Pair;
/**
*
* @author Petr Hejl
*/
public final class HttpUtils {
private static final Pattern HTTP_RESPONSE_PATTERN = Pattern.compile("^HTTP/1\\.1 (\\d\\d\\d) (.*)$");
private HttpUtils() {
super();
}
public static Response readResponse(InputStream is) throws IOException {
String response = HttpUtils.readResponseLine(is);
if (response == null) {
throw new IOException("No response from server");
}
Matcher m = HTTP_RESPONSE_PATTERN.matcher(response);
if (!m.matches()) {
throw new IOException("Wrong response from server");
}
int responseCode = Integer.parseInt(m.group(1));
String message = m.group(2);
Map<String, String> headers = parseHeaders(is);
return new Response(responseCode, message, headers);
}
@CheckForNull
public static String readContent(InputStream is, Response response) throws IOException {
Integer length = getLength(response.getHeaders());
if (length == null || length == 0) {
return null;
}
Charset encoding = getCharset(response.getHeaders().get("CONTENT-TYPE")); // NOI18N
byte[] content = new byte[length];
int count = 0;
do {
int current = is.read(content, count, length - count);
if (current < 0 && count < length) {
throw new IOException("Stream closed before reading content");
}
count += current;
} while (count < length);
return new String(content, encoding);
}
public static InputStream getResponseStream(InputStream is, Response response, boolean allowSocketStream) throws IOException {
if (isChunked(response.getHeaders())) {
return new ChunkedInputStream(new BufferedInputStream(is));
} else {
Integer l = getLength(response.getHeaders());
if (l != null) {
return new BufferedInputStream(new FilterInputStream(is) {
private int available = l;
@Override
public int available() throws IOException {
return Math.min(super.available(), available);
}
@Override
public int read(byte[] b, int off, int len) throws IOException {
if (available <= 0) {
return -1;
}
int real = Math.min(available, len);
int count = super.read(b, off, real);
available -= count;
return count;
}
@Override
public int read() throws IOException {
if (available <= 0) {
return -1;
}
available--;
return super.read();
}
});
} else if (allowSocketStream) {
return new BufferedInputStream(is);
} else {
throw new IOException("Undefined content length");
}
}
}
public static Integer getLength(Map<String, String> headers) throws IOException {
String value = headers.get("CONTENT-LENGTH"); // NOI18N
if (value == null) {
return null;
}
try {
return Integer.parseInt(value);
} catch (NumberFormatException ex) {
throw new IOException("Wrong content length: " + value);
}
}
@NonNull
public static Charset getCharset(Response response) {
return getCharset(response.getHeaders().get("CONTENT-TYPE")); // NOI18N
}
public static String encodeParameter(String value) throws UnsupportedEncodingException {
return URLEncoder.encode(value, "UTF-8");
}
public static String encodeBase64(String value) throws UnsupportedEncodingException {
return Base64.getEncoder().encodeToString(value.getBytes("UTF-8"));
}
public static void configureHeaders(OutputStream os, Map<String, String> defaultHeaders,
Pair<String, String>... headers) throws IOException {
StringBuilder sb = new StringBuilder();
configureHeaders(sb, defaultHeaders, headers);
os.write(sb.toString().getBytes("ISO-8859-1")); // NOI18N
}
public static void configureHeaders(StringBuilder sb, Map<String, String> defaultHeaders,
Pair<String, String>... headers) throws IOException {
Map<String, String> toUse = new HashMap<>(defaultHeaders);
for (Pair<String, String> h : headers) {
if (h == null) {
continue;
}
toUse.put(h.first(), h.second());
}
for (Map.Entry<String, String> e : toUse.entrySet()) {
sb.append(e.getKey()).append(":").append(" "); // NOI18N
sb.append(e.getValue()).append("\r\n"); // NOI18N
}
}
static String readResponseLine(InputStream is) throws IOException {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
int b;
while ((b = is.read()) != -1) {
if (b == '\r') {
int next = is.read();
if (next == '\n') {
return bos.toString("ISO-8859-1"); // NOI18N
} else if (next == -1) {
return null;
} else {
bos.write(b);
bos.write(next);
}
} else {
bos.write(b);
}
}
return null;
}
private static Map<String, String> parseHeaders(InputStream is) throws IOException {
Map<String, String> result = new HashMap<>();
String line;
for (;;) {
line = HttpUtils.readResponseLine(is).trim();
if (line != null && !"".equals(line)) {
int index = line.indexOf(':'); // NOI18N
if (index <= 0) {
throw new IOException("Invalid header: " + line);
}
if (index == line.length() - 1) {
// XXX empty header ?
continue;
}
result.put(line.substring(0, index).toUpperCase(Locale.ENGLISH).trim(), line.substring(index + 1).trim());
} else {
break;
}
}
return result;
}
private static Charset getCharset(String contentType) {
// FIXME the spec default is ISO-8859-1
Charset encoding = Charset.forName("UTF-8"); // NOI18N
if (contentType != null) {
String[] parts = contentType.trim().split(";"); // NOI18N
for (String p : parts) {
String upper = p.toUpperCase(Locale.ENGLISH);
if (upper.startsWith("CHARSET")) { // NOI18N
int index = upper.indexOf("=", 7); // NOI18N
if (index > 0 && index < upper.length() -1) {
try {
encoding = Charset.forName(upper.substring(index + 1).trim());
} catch (UnsupportedCharsetException ex) {
// noop using the UTF-8
}
}
break;
}
}
}
return encoding;
}
private static boolean isChunked(Map<String, String> headers) {
String value = headers.get("TRANSFER-ENCODING"); // NOI18N
return value != null && value.contains("chunked"); // NOI18N
}
public static class Response {
private final int code;
private final String message;
private final Map<String, String> headers;
Response(int code, String message, Map<String, String> headers) {
this.code = code;
this.message = message;
this.headers = headers;
}
public int getCode() {
return code;
}
public String getMessage() {
return message;
}
public Map<String, String> getHeaders() {
return headers;
}
}
}