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
039  public class HTTPResult {
040    private int code;
041    private String contentType;
042    private byte[] content;
043    private String source;
044    
045    
046    public HTTPResult(String source, int code, String contentType, byte[] content) {
047      super();
048      this.source = source;
049      this.code = code;
050      this.contentType = contentType;
051      this.content = content;
052    }
053    
054    public int getCode() {
055      return code;
056    }
057    public String getContentType() {
058      return contentType;
059    }
060    public byte[] getContent() {
061      return content;
062    }
063
064    public String getSource() {
065      return source;
066    }
067
068    public void checkThrowException() throws IOException {
069      if (code >= 300) {
070        throw new IOException("Invalid HTTP response "+code+" from "+source);
071      }      
072    }    
073  }
074
075  private List<Header> headers = new ArrayList<>();
076  private String username;
077  private String password;
078  
079  public void addHeader(String name, String value) {
080    headers.add(new Header(name, value));
081  }
082
083  public String getUsername() {
084    return username;
085  }
086
087  public void setUsername(String username) {
088    this.username = username;
089  }
090
091  public String getPassword() {
092    return password;
093  }
094
095  public void setPassword(String password) {
096    this.password = password;
097  }
098
099
100  private boolean trustAll = false;
101  
102  public void trustAllhosts() {
103    trustAll  = true;
104    SSLCertTruster.trustAllHosts();    
105  }
106 
107  public HTTPResult get(String url) throws IOException {
108    return get(url, null);    
109  }
110  
111  public HTTPResult get(String url, String accept) throws IOException {
112    URL u = new URL(url);
113//    boolean isSSL = url.startsWith("https://");
114    
115    // handling redirects - setInstanceFollowRedirects(true) doesn't handle crossing http to https
116
117    Map<String, Integer> visited = new HashMap<>();
118    HttpURLConnection c = null;
119    boolean done = false;
120
121    while (!done) {
122      int times = visited.compute(url, (key, count) -> count == null ? 1 : count + 1);
123      if (times > MAX_REDIRECTS)
124        throw new IOException("Stuck in redirect loop");
125
126      u = new URL(url);
127      c = (HttpURLConnection) u.openConnection();
128      c.setRequestMethod("GET");
129      c.setRequestProperty("Accept", accept);
130      setHeaders(c);
131      c.setInstanceFollowRedirects(false); 
132      if (trustAll && url.startsWith("https://")) {
133        ((javax.net.ssl.HttpsURLConnection) c).setHostnameVerifier(SSLCertTruster.DO_NOT_VERIFY);
134      }
135
136      switch (c.getResponseCode()) {
137      case HttpURLConnection.HTTP_MOVED_PERM:
138      case HttpURLConnection.HTTP_MOVED_TEMP:
139        String location = c.getHeaderField("Location");
140        location = URLDecoder.decode(location, "UTF-8");
141        URL base = new URL(url);               
142        URL next = new URL(base, location);  // Deal with relative URLs
143        url      = next.toExternalForm();
144        continue;
145      default:
146        done = true;
147      }
148    }
149    
150    return new HTTPResult(url, c.getResponseCode(),  c.getRequestProperty("Content-Type"), TextFile.streamToBytes(c.getInputStream()));
151  }
152
153  private void setHeaders(HttpURLConnection c) {
154    for (Header h : headers) {
155      c.setRequestProperty(h.getName(), h.getValue());        
156    }
157    c.setConnectTimeout(15000);
158    c.setReadTimeout(15000);
159    if (username != null) {
160      String auth = username+":"+password;
161      byte[] encodedAuth = Base64.getEncoder().encode(auth.getBytes(StandardCharsets.UTF_8));
162      String authHeaderValue = "Basic " + new String(encodedAuth);
163      c.setRequestProperty("Authorization", authHeaderValue);
164    }
165  }
166
167  public HTTPResult post(String url, String contentType, byte[] content, String accept) throws IOException {
168    URL u = new URL(url);
169    HttpURLConnection c = (HttpURLConnection) u.openConnection();
170    c.setDoOutput(true);
171    c.setDoInput(true);
172    c.setRequestMethod("POST");
173    c.setRequestProperty("Content-type", contentType);
174    if (accept != null) {
175      c.setRequestProperty("Accept", accept);
176    }
177    setHeaders(c);
178    c.getOutputStream().write(content);
179    c.getOutputStream().close();    
180    return new HTTPResult(url, c.getResponseCode(),  c.getRequestProperty("Content-Type"), TextFile.streamToBytes(c.getInputStream()));
181  }
182
183 
184  public HTTPResult put(String url, String contentType, byte[] content, String accept) throws IOException {
185    URL u = new URL(url);
186    HttpURLConnection c = (HttpURLConnection) u.openConnection();
187    c.setDoOutput(true);
188    c.setDoInput(true);
189    c.setRequestMethod("PUT");
190    c.setRequestProperty("Content-type", contentType);
191    if (accept != null) {
192      c.setRequestProperty("Accept", accept);
193    }
194    setHeaders(c);
195    c.getOutputStream().write(content);
196    c.getOutputStream().close();    
197    return new HTTPResult(url, c.getResponseCode(),  c.getRequestProperty("Content-Type"), TextFile.streamToBytes(c.getInputStream()));
198  }
199
200
201}