/* * Copyright 2017 Google Inc. * * Licensed 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 com.google.firebase.database.utilities; import com.google.common.io.BaseEncoding; import com.google.firebase.database.DatabaseError; import com.google.firebase.database.DatabaseException; import com.google.firebase.database.DatabaseReference; import com.google.firebase.database.core.Path; import com.google.firebase.database.core.RepoInfo; import com.google.firebase.tasks.Task; import com.google.firebase.tasks.TaskCompletionSource; import java.io.UnsupportedEncodingException; import java.net.URI; import java.net.URISyntaxException; import java.net.URLEncoder; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.ArrayList; import java.util.Map; public class Utilities { private static final char[] HEX_CHARACTERS = "0123456789abcdef".toCharArray(); public static ParsedUrl parseUrl(String url) throws DatabaseException { String original = url; try { int schemeOffset = original.indexOf("//"); if (schemeOffset == -1) { throw new URISyntaxException(original, "Invalid scheme specified"); } int pathOffset = original.substring(schemeOffset + 2).indexOf("/"); if (pathOffset != -1) { pathOffset += schemeOffset + 2; String[] pathSegments = original.substring(pathOffset).split("/"); StringBuilder builder = new StringBuilder(); for (int i = 0; i < pathSegments.length; ++i) { if (!pathSegments[i].equals("")) { builder.append("/"); builder.append(URLEncoder.encode(pathSegments[i], "UTF-8")); } } original = original.substring(0, pathOffset) + builder.toString(); } URI uri = new URI(original); // URLEncoding a space turns it into a '+', which is different // from our expected behavior. Do a manual replace to fix it. final String pathString = uri.getPath().replace("+", " "); Validation.validateRootPathString(pathString); String scheme = uri.getScheme(); RepoInfo repoInfo = new RepoInfo(); repoInfo.host = uri.getHost().toLowerCase(); int port = uri.getPort(); if (port != -1) { repoInfo.secure = scheme.equals("https"); repoInfo.host += ":" + port; } else { repoInfo.secure = true; } String[] parts = repoInfo.host.split("\\."); repoInfo.namespace = parts[0].toLowerCase(); repoInfo.internalHost = repoInfo.host; ParsedUrl parsedUrl = new ParsedUrl(); parsedUrl.path = new Path(pathString); parsedUrl.repoInfo = repoInfo; return parsedUrl; } catch (URISyntaxException e) { throw new DatabaseException("Invalid Firebase Database url specified", e); } catch (UnsupportedEncodingException e) { throw new DatabaseException("Failed to URLEncode the path", e); } } public static String[] splitIntoFrames(String src, int maxFrameSize) { if (src.length() <= maxFrameSize) { return new String[] {src}; } else { ArrayList<String> segs = new ArrayList<>(); for (int i = 0; i < src.length(); i += maxFrameSize) { int end = Math.min(i + maxFrameSize, src.length()); String seg = src.substring(i, end); segs.add(seg); } return segs.toArray(new String[segs.size()]); } } public static String sha1HexDigest(String input) { try { MessageDigest md = MessageDigest.getInstance("SHA-1"); md.update(input.getBytes("UTF-8")); byte[] bytes = md.digest(); return BaseEncoding.base64().encode(bytes); } catch (NoSuchAlgorithmException e) { throw new RuntimeException("Missing SHA-1 MessageDigest provider.", e); } catch (UnsupportedEncodingException e) { throw new RuntimeException("UTF-8 encoding is required for Firebase Database to run!"); } } public static String stringHashV2Representation(String value) { String escaped = value; if (value.indexOf('\\') != -1) { escaped = escaped.replace("\\", "\\\\"); } if (value.indexOf('"') != -1) { escaped = escaped.replace("\"", "\\\""); } return '"' + escaped + '"'; } public static String doubleToHashString(double value) { StringBuilder sb = new StringBuilder(16); long bits = Double.doubleToLongBits(value); // We use big-endian to encode the bytes for (int i = 7; i >= 0; i--) { int byteValue = (int) ((bits >>> (8 * i)) & 0xff); int high = ((byteValue >> 4) & 0xf); int low = (byteValue & 0xf); sb.append(HEX_CHARACTERS[high]); sb.append(HEX_CHARACTERS[low]); } return sb.toString(); } // NOTE: We could use Ints.tryParse from guava, but I don't feel like pulling in guava (~2mb) // for // that small purpose. public static Integer tryParseInt(String num) { if (num.length() > 11 || num.length() == 0) { return null; } int i = 0; boolean negative = false; if (num.charAt(0) == '-') { if (num.length() == 1) { return null; } negative = true; i = 1; } // long to prevent overflow long number = 0; while (i < num.length()) { char c = num.charAt(i); if (c < '0' || c > '9') { return null; } number = number * 10 + (c - '0'); i++; } if (negative) { if (-number < Integer.MIN_VALUE) { return null; } else { return (int) (-number); } } else { if (number > Integer.MAX_VALUE) { return null; } return (int) number; } } public static int compareInts(int i, int j) { if (i < j) { return -1; } else if (i == j) { return 0; } else { return 1; } } public static int compareLongs(long i, long j) { if (i < j) { return -1; } else if (i == j) { return 0; } else { return 1; } } @SuppressWarnings("unchecked") public static <C> C castOrNull(Object o, Class<C> clazz) { if (clazz.isAssignableFrom(o.getClass())) { return (C) o; } else { return null; } } @SuppressWarnings("rawtypes") public static <C> C getOrNull(Object o, String key, Class<C> clazz) { if (o == null) { return null; } Map map = castOrNull(o, Map.class); Object result = map.get(key); if (result != null) { return castOrNull(result, clazz); } else { return null; } } public static void hardAssert(boolean condition) { hardAssert(condition, ""); } public static void hardAssert(boolean condition, String message) { if (!condition) { throw new AssertionError("hardAssert failed: " + message); } } public static Pair<Task<Void>, DatabaseReference.CompletionListener> wrapOnComplete( DatabaseReference.CompletionListener optListener) { if (optListener == null) { final TaskCompletionSource<Void> source = new TaskCompletionSource<>(); DatabaseReference.CompletionListener listener = new DatabaseReference.CompletionListener() { @Override public void onComplete(DatabaseError error, DatabaseReference ref) { if (error != null) { source.setException(error.toException()); } else { source.setResult(null); } } }; return new Pair<>(source.getTask(), listener); } else { // If a listener is supplied we do not want to create a Task return new Pair<>(null, optListener); } } }