package com.getsentry.raven.dsn; import com.getsentry.raven.config.Lookup; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.UnsupportedEncodingException; import java.net.URI; import java.net.URISyntaxException; import java.net.URLDecoder; import java.util.*; /** * Data Source name allowing a direct connection to a Sentry server. */ public class Dsn { /** * Default DSN to use when auto detection fails. */ public static final String DEFAULT_DSN = "noop://user:password@localhost:0/0"; private static final Logger logger = LoggerFactory.getLogger(Dsn.class); private String secretKey; private String publicKey; private String projectId; private String protocol; private String host; private int port; private String path; private Set<String> protocolSettings; private Map<String, String> options; private URI uri; /** * Creates a DSN based on the {@link #dsnLookup()} result. */ public Dsn() { this(dsnLookup()); } /** * Creates a DSN based on a String. * * @param dsn DSN in a string form. * @throws InvalidDsnException the given DSN is not valid. */ public Dsn(String dsn) throws InvalidDsnException { this(URI.create(dsn)); } /** * Creates a DSN based on a URI. * * @param dsn DSN in URI form. * @throws InvalidDsnException the given DSN is not valid. */ public Dsn(URI dsn) throws InvalidDsnException { if (dsn == null) { throw new InvalidDsnException("The sentry DSN must be provided and not be null"); } options = new HashMap<>(); protocolSettings = new HashSet<>(); extractProtocolInfo(dsn); extractUserKeys(dsn); extractHostInfo(dsn); extractPathInfo(dsn); extractOptions(dsn); makeOptionsImmutable(); validate(); try { uri = new URI(protocol, null, host, port, path, null, null); } catch (URISyntaxException e) { throw new InvalidDsnException("Impossible to determine Sentry's URI from the DSN '" + dsn + "'", e); } } /** * Looks for a DSN configuration within JNDI, the System environment or Java properties. * * @return a DSN configuration or null if nothing could be found. */ public static String dsnLookup() { String dsn = Lookup.lookup("dsn"); if (dsn == null) { logger.warn("Couldn't find a suitable DSN, defaulting to a Noop one."); dsn = DEFAULT_DSN; } return dsn; } /** * Extracts the path and the project ID from the DSN provided as an {@code URI}. * * @param dsnUri DSN as an URI. */ private void extractPathInfo(URI dsnUri) { String uriPath = dsnUri.getPath(); if (uriPath == null) { return; } int projectIdStart = uriPath.lastIndexOf("/") + 1; path = uriPath.substring(0, projectIdStart); projectId = uriPath.substring(projectIdStart); } /** * Extracts the hostname and port of the Sentry server from the DSN provided as an {@code URI}. * * @param dsnUri DSN as an URI. */ private void extractHostInfo(URI dsnUri) { host = dsnUri.getHost(); port = dsnUri.getPort(); } /** * Extracts the scheme and additional protocol options from the DSN provided as an {@code URI}. * * @param dsnUri DSN as an URI. */ private void extractProtocolInfo(URI dsnUri) { String scheme = dsnUri.getScheme(); if (scheme == null) { return; } String[] schemeDetails = scheme.split("\\+"); protocolSettings.addAll(Arrays.asList(schemeDetails).subList(0, schemeDetails.length - 1)); protocol = schemeDetails[schemeDetails.length - 1]; } /** * Extracts the public and secret keys from the DSN provided as an {@code URI}. * * @param dsnUri DSN as an URI. */ private void extractUserKeys(URI dsnUri) { String userInfo = dsnUri.getUserInfo(); if (userInfo == null) { return; } String[] userDetails = userInfo.split(":"); publicKey = userDetails[0]; if (userDetails.length > 1) { secretKey = userDetails[1]; } } /** * Extracts the DSN options from the DSN provided as an {@code URI}. * * @param dsnUri DSN as an URI. */ private void extractOptions(URI dsnUri) { String query = dsnUri.getQuery(); if (query == null || query.isEmpty()) { return; } for (String optionPair : query.split("&")) { try { String[] pairDetails = optionPair.split("="); String key = URLDecoder.decode(pairDetails[0], "UTF-8"); String value = pairDetails.length > 1 ? URLDecoder.decode(pairDetails[1], "UTF-8") : null; options.put(key, value); } catch (UnsupportedEncodingException e) { throw new IllegalArgumentException("Impossible to decode the query parameter '" + optionPair + "'", e); } } } /** * Makes protocol and dsn options immutable to allow external usage. */ private void makeOptionsImmutable() { // Make the options immutable options = Collections.unmodifiableMap(options); protocolSettings = Collections.unmodifiableSet(protocolSettings); } /** * Validates internally the DSN, and check for mandatory elements. * <p> * Mandatory elements are the {@link #host}, {@link #publicKey}, {@link #secretKey} and {@link #projectId}. */ private void validate() { List<String> missingElements = new LinkedList<>(); if (host == null) { missingElements.add("host"); } if (publicKey == null) { missingElements.add("public key"); } if (secretKey == null) { missingElements.add("secret key"); } if (projectId == null || projectId.isEmpty()) { missingElements.add("project ID"); } if (!missingElements.isEmpty()) { throw new InvalidDsnException("Invalid DSN, the following properties aren't set '" + missingElements + "'"); } } public String getSecretKey() { return secretKey; } public String getPublicKey() { return publicKey; } public String getProjectId() { return projectId; } public String getProtocol() { return protocol; } public String getHost() { return host; } public int getPort() { return port; } public String getPath() { return path; } public Set<String> getProtocolSettings() { return protocolSettings; } public Map<String, String> getOptions() { return options; } /** * Creates the URI of the Sentry server. * * @return the URI of the Sentry server. */ public URI getUri() { return uri; } @Override public boolean equals(Object o) { if (this == o) { return true; } if (o == null || getClass() != o.getClass()) { return false; } Dsn dsn = (Dsn) o; if (port != dsn.port) { return false; } if (!host.equals(dsn.host)) { return false; } if (!options.equals(dsn.options)) { return false; } if (!path.equals(dsn.path)) { return false; } if (!projectId.equals(dsn.projectId)) { return false; } if (protocol != null ? !protocol.equals(dsn.protocol) : dsn.protocol != null) { return false; } if (!protocolSettings.equals(dsn.protocolSettings)) { return false; } if (!publicKey.equals(dsn.publicKey)) { return false; } if (!secretKey.equals(dsn.secretKey)) { return false; } return true; } @Override public int hashCode() { int result = publicKey.hashCode(); result = 31 * result + projectId.hashCode(); result = 31 * result + host.hashCode(); result = 31 * result + port; result = 31 * result + path.hashCode(); return result; } @Override public String toString() { return "Dsn{" + "uri=" + uri + '}'; } }