View Javadoc
1   package com.github.davidmoten.aws.lw.client.internal.util;
2   
3   import java.io.ByteArrayOutputStream;
4   import java.io.IOException;
5   import java.io.InputStream;
6   import java.io.UncheckedIOException;
7   import java.io.UnsupportedEncodingException;
8   import java.net.HttpURLConnection;
9   import java.net.MalformedURLException;
10  import java.net.URL;
11  import java.net.URLEncoder;
12  import java.nio.charset.StandardCharsets;
13  import java.security.MessageDigest;
14  import java.security.NoSuchAlgorithmException;
15  import java.util.Locale;
16  import java.util.Map;
17  import java.util.Map.Entry;
18  import java.util.Optional;
19  
20  /**
21   * Utilities for encoding and decoding binary data to and from different forms.
22   */
23  public final class Util {
24  
25      private Util() {
26          // prevent instantiation
27      }
28  
29      public static HttpURLConnection createHttpConnection(URL endpointUrl, String httpMethod,
30              Map<String, String> headers, int connectTimeoutMs, int readTimeoutMs) throws IOException {
31          Preconditions.checkNotNull(headers);
32          HttpURLConnection connection = (HttpURLConnection) endpointUrl.openConnection();
33          connection.setRequestMethod(httpMethod);
34  
35          for (Entry<String, String> entry : headers.entrySet()) {
36              connection.setRequestProperty(entry.getKey(), entry.getValue());
37          }
38  
39          connection.setUseCaches(false);
40          connection.setDoInput(true);
41          connection.setDoOutput(true);
42          connection.setConnectTimeout(connectTimeoutMs);
43          connection.setReadTimeout(readTimeoutMs);
44          return connection;
45      }
46  
47      public static String canonicalMetadataKey(String meta) {
48          StringBuilder b = new StringBuilder();
49          String s = meta.toLowerCase(Locale.ENGLISH);
50          for (int ch : s.toCharArray()) {
51              if (Character.isDigit(ch) || Character.isAlphabetic(ch)) {
52                  b.append((char) ch);
53              }
54          }
55          return b.toString();
56      }
57  
58      /**
59       * Converts byte data to a Hex-encoded string.
60       *
61       * @param data data to hex encode.
62       *
63       * @return hex-encoded string.
64       */
65      public static String toHex(byte[] data) {
66          StringBuilder sb = new StringBuilder(data.length * 2);
67          for (int i = 0; i < data.length; i++) {
68              String hex = Integer.toHexString(data[i]);
69              if (hex.length() == 1) {
70                  // Append leading zero.
71                  sb.append("0");
72              } else if (hex.length() == 8) {
73                  // Remove ff prefix from negative numbers.
74                  hex = hex.substring(6);
75              }
76              sb.append(hex);
77          }
78          return sb.toString().toLowerCase(Locale.getDefault());
79      }
80  
81      public static URL toUrl(String url) {
82          try {
83              return new URL(url);
84          } catch (MalformedURLException e) {
85              throw new RuntimeException(e);
86          }
87      }
88  
89      public static String urlEncode(String url, boolean keepPathSlash) {
90          return urlEncode(url, keepPathSlash, "UTF-8");
91      }
92  
93      // VisibleForTesting
94      static String urlEncode(String url, boolean keepPathSlash, String charset) {
95          String encoded;
96          try {
97              encoded = URLEncoder.encode(url, charset).replace("+", "%20");
98          } catch (UnsupportedEncodingException e) {
99              throw new RuntimeException(e);
100         }
101         if (keepPathSlash) {
102             return encoded.replace("%2F", "/");
103         } else {
104             return encoded;
105         }
106     }
107 
108     /**
109      * Hashes the string contents (assumed to be UTF-8) using the SHA-256 algorithm.
110      */
111     public static byte[] sha256(String text) {
112         return sha256(text.getBytes(StandardCharsets.UTF_8));
113     }
114 
115     public static byte[] sha256(byte[] data) {
116         return hash(data, "SHA-256");
117     }
118 
119     // VisibleForTesting
120     static byte[] hash(byte[] data, String algorithm) {
121         try {
122             MessageDigest md = MessageDigest.getInstance(algorithm);
123             md.update(data);
124             return md.digest();
125         } catch (NoSuchAlgorithmException e) {
126             throw new RuntimeException(e);
127         }
128     }
129 
130     public static byte[] readBytesAndClose(InputStream in) {
131         try {
132             byte[] buffer = new byte[8192];
133             int n;
134             ByteArrayOutputStream bytes = new ByteArrayOutputStream();
135             while ((n = in.read(buffer)) != -1) {
136                 bytes.write(buffer, 0, n);
137             }
138             return bytes.toByteArray();
139         } catch (IOException e) {
140             throw new UncheckedIOException(e);
141         } finally {
142             try {
143                 in.close();
144             } catch (IOException e) {
145                 throw new UncheckedIOException(e);
146             }
147         }
148     }
149 
150     private static final InputStream EMPTY_INPUT_STREAM = new InputStream() {
151         @Override
152         public int read() throws IOException {
153             return -1;
154         }
155     };
156 
157     public static final InputStream emptyInputStream() {
158         return EMPTY_INPUT_STREAM;
159     }
160 
161     public static Optional<String> jsonFieldText(String json, String fieldName) {
162         // it is assumed that the json field is valid object json
163         String key = "\"" + fieldName + "\"";
164         int keyPosition = json.indexOf(key);
165         if (keyPosition == -1) {
166             return Optional.empty(); // Field not found
167         }
168 
169         // Find the position of the colon after the key and skip any whitespace
170         int colonPosition = json.indexOf(":", keyPosition + key.length());
171         if (colonPosition == -1) {
172             return Optional.empty(); // Colon not found, malformed JSON
173         }
174 
175         // Skip whitespace after the colon
176         int valueStart = colonPosition + 1;
177         while (valueStart < json.length() && Character.isWhitespace(json.charAt(valueStart))) {
178             valueStart++;
179         }
180 
181         // Check if the value is a string
182         boolean isString = json.charAt(valueStart) == '"';
183         StringBuilder value = new StringBuilder();
184         boolean isEscaped = false;
185 
186         // Parse the value, handling escaped quotes
187         for (int i = valueStart + (isString ? 1 : 0); i < json.length(); i++) {
188             char c = json.charAt(i);
189 
190             if (isString) {
191                 // Handle string value
192                 if (isEscaped) {
193                     // Append escaped character and reset flag
194                     value.append(c);
195                     isEscaped = false;
196                 } else if (c == '\\') {
197                     // Next character is escaped
198                     isEscaped = true;
199                 } else if (c == '"') {
200                     // End of string
201                     break;
202                 } else {
203                     value.append(c);
204                 }
205             } else {
206                 // Handle non-string value
207                 if (c == ',' || c == '}') {
208                     // End of non-string value
209                     break;
210                 } else {
211                     value.append(c);
212                 }
213             }
214         }
215         String v = value.toString().trim();
216         if (!isString && "null".equals(v)) {
217             return Optional.empty();
218         } else {
219             return Optional.of(v);
220         }
221     }
222 }