001package org.hl7.fhir.utilities;
002
003import java.io.DataOutputStream;
004import java.io.IOException;
005import java.net.HttpURLConnection;
006import java.net.URL;
007import java.net.URLDecoder;
008import java.nio.charset.StandardCharsets;
009import java.util.ArrayList;
010import java.util.Base64;
011import java.util.HashMap;
012import java.util.List;
013import java.util.Map;
014
015import org.hl7.fhir.utilities.SimpleHTTPClient.HTTPResult;
016import org.hl7.fhir.utilities.SimpleHTTPClient.Header;
017import org.hl7.fhir.utilities.npm.SSLCertTruster;
018
019public class SimpleHTTPClient {
020  
021  public class Header {
022    private String name;
023    private String value;
024    public Header(String name, String value) {
025      super();
026      this.name = name;
027      this.value = value;
028    }
029    public String getName() {
030      return name;
031    }
032    public String getValue() {
033      return value;
034    }
035  }
036
037  private static final int MAX_REDIRECTS = 5;
038  private static int counter = 1;
039
040  public class HTTPResult {
041    private int code;
042    private String contentType;
043    private byte[] content;
044    private String source;
045    private String message;
046    
047    
048    public HTTPResult(String source, int code, String message, String contentType, byte[] content) {
049      super();
050      this.source = source;
051      this.code = code;
052      this.contentType = contentType;
053      this.content = content;
054      this.message = message;
055    }
056    
057    public int getCode() {
058      return code;
059    }
060    public String getContentType() {
061      return contentType;
062    }
063    public byte[] getContent() {
064      return content;
065    }
066
067    public String getSource() {
068      return source;
069    }
070
071    public void checkThrowException() throws IOException {
072      if (code >= 300) {
073        String filename = Utilities.path("[tmp]", "fhir-http-"+(++counter)+".log");
074        if (content == null || content.length == 0) {
075          throw new IOException("Invalid HTTP response "+code+" from "+source+" ("+message+") (no content)");          
076        } else {
077          TextFile.bytesToFile(content, filename);
078          throw new IOException("Invalid HTTP response "+code+" from "+source+" ("+message+") (content in "+filename+")");
079        }
080      }      
081    }
082
083    public String getMessage() {
084      return message;
085    }    
086  }
087
088  private List<Header> headers = new ArrayList<>();
089  private String username;
090  private String password;
091  
092  public void addHeader(String name, String value) {
093    headers.add(new Header(name, value));
094  }
095
096  public String getUsername() {
097    return username;
098  }
099
100  public void setUsername(String username) {
101    this.username = username;
102  }
103
104  public String getPassword() {
105    return password;
106  }
107
108  public void setPassword(String password) {
109    this.password = password;
110  }
111
112
113  private boolean trustAll = false;
114  
115  public void trustAllhosts() {
116    trustAll  = true;
117    SSLCertTruster.trustAllHosts();    
118  }
119 
120  public HTTPResult get(String url) throws IOException {
121    return get(url, null);    
122  }
123  
124  public HTTPResult get(String url, String accept) throws IOException {
125    URL u = new URL(url);
126//    boolean isSSL = url.startsWith("https://");
127    
128    // handling redirects - setInstanceFollowRedirects(true) doesn't handle crossing http to https
129
130    Map<String, Integer> visited = new HashMap<>();
131    HttpURLConnection c = null;
132    boolean done = false;
133
134    while (!done) {
135      int times = visited.compute(url, (key, count) -> count == null ? 1 : count + 1);
136      if (times > MAX_REDIRECTS)
137        throw new IOException("Stuck in redirect loop");
138
139      u = new URL(url);
140      c = (HttpURLConnection) u.openConnection();
141      c.setRequestMethod("GET");
142      if (accept != null) {
143        c.setRequestProperty("Accept", accept);
144      }
145      setHeaders(c);
146      c.setInstanceFollowRedirects(false); 
147      if (trustAll && url.startsWith("https://")) {
148        ((javax.net.ssl.HttpsURLConnection) c).setHostnameVerifier(SSLCertTruster.DO_NOT_VERIFY);
149      }
150
151      switch (c.getResponseCode()) {
152      case HttpURLConnection.HTTP_MOVED_PERM:
153      case HttpURLConnection.HTTP_MOVED_TEMP:
154        String location = c.getHeaderField("Location");
155        location = URLDecoder.decode(location, "UTF-8");
156        URL base = new URL(url);               
157        URL next = new URL(base, location);  // Deal with relative URLs
158        url      = next.toExternalForm();
159        continue;
160      default:
161        done = true;
162      }
163    }
164    
165    return new HTTPResult(url, c.getResponseCode(), c.getResponseMessage(),  c.getRequestProperty("Content-Type"), TextFile.streamToBytes(c.getResponseCode() >= 400 ? c.getErrorStream() : c.getInputStream()));
166  }
167
168  private void setHeaders(HttpURLConnection c) {
169    for (Header h : headers) {
170      c.setRequestProperty(h.getName(), h.getValue());        
171    }
172    c.setConnectTimeout(15000);
173    c.setReadTimeout(15000);
174    if (username != null) {
175      String auth = username+":"+password;
176      byte[] encodedAuth = Base64.getEncoder().encode(auth.getBytes(StandardCharsets.UTF_8));
177      String authHeaderValue = "Basic " + new String(encodedAuth);
178      c.setRequestProperty("Authorization", authHeaderValue);
179    }
180  }
181
182  public HTTPResult post(String url, String contentType, byte[] content, String accept) throws IOException {
183    URL u = new URL(url);
184    HttpURLConnection c = (HttpURLConnection) u.openConnection();
185    c.setDoOutput(true);
186    c.setDoInput(true);
187    c.setRequestMethod("POST");
188    c.setRequestProperty("Content-type", contentType);
189    if (accept != null) {
190      c.setRequestProperty("Accept", accept);
191    }
192    setHeaders(c);
193    c.getOutputStream().write(content);
194    c.getOutputStream().close();    
195    return new HTTPResult(url, c.getResponseCode(), c.getResponseMessage(), c.getRequestProperty("Content-Type"), TextFile.streamToBytes(c.getResponseCode() >= 400 ? c.getErrorStream() : c.getInputStream()));
196  }
197
198 
199  public HTTPResult put(String url, String contentType, byte[] content, String accept) throws IOException {
200    URL u = new URL(url);
201    HttpURLConnection c = (HttpURLConnection) u.openConnection();
202    c.setDoOutput(true);
203    c.setDoInput(true);
204    c.setRequestMethod("PUT");
205    c.setRequestProperty("Content-type", contentType);
206    if (accept != null) {
207      c.setRequestProperty("Accept", accept);
208    }
209    setHeaders(c);
210    c.getOutputStream().write(content);
211    c.getOutputStream().close();    
212    return new HTTPResult(url, c.getResponseCode(), c.getResponseMessage(), c.getRequestProperty("Content-Type"), TextFile.streamToBytes(c.getResponseCode() >= 400 ? c.getErrorStream() : c.getInputStream()));
213  }
214
215
216}