All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.cloudinary.Url Maven / Gradle / Ivy

package com.cloudinary;

import java.io.UnsupportedEncodingException;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLDecoder;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.zip.CRC32;

import com.cloudinary.utils.Analytics;
import com.cloudinary.utils.Base64Coder;
import com.cloudinary.utils.ObjectUtils;
import com.cloudinary.utils.StringUtils;

import static com.cloudinary.SignatureAlgorithm.SHA256;

public class Url {
    private final Cloudinary cloudinary;
    private final Configuration config;
    private boolean longUrlSignature;
    String publicId = null;
    String type = null;
    String resourceType = null;
    String format = null;
    String version = null;
    Transformation transformation = null;
    boolean signUrl;
    private AuthToken authToken;
    String source = null;
    private String urlSuffix;
    private Boolean useRootPath;
    Map sourceTransformation = null;
    String[] sourceTypes = null;
    String fallbackContent = null;
    Transformation posterTransformation = null;
    String posterSource = null;
    Url posterUrl = null;

    private static final String CL_BLANK = "data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7";
    public static final String[] DEFAULT_VIDEO_SOURCE_TYPES = {"webm", "mp4", "ogv"};
    private static final Pattern VIDEO_EXTENSION_RE = Pattern.compile("\\.(" + StringUtils.join(DEFAULT_VIDEO_SOURCE_TYPES, "|") + ")$");

    public Url(Cloudinary cloudinary) {
        this.cloudinary = cloudinary;
        this.config = new Configuration(cloudinary.config);
        this.longUrlSignature = config.longUrlSignature;
        this.authToken = config.authToken;
    }

    public Url clone() {
        Url cloned = cloudinary.url();
        cloned.config.update(config.asMap());
        cloned.fallbackContent = this.fallbackContent;
        cloned.format = this.format;
        cloned.posterSource = this.posterSource;
        if (this.posterTransformation != null)
            cloned.posterTransformation = new Transformation(this.posterTransformation);
        if (this.posterUrl != null) cloned.posterUrl = this.posterUrl.clone();
        cloned.publicId = this.publicId;
        cloned.resourceType = this.resourceType;
        cloned.signUrl = this.signUrl;
        cloned.source = this.source;
        if (this.transformation != null) cloned.transformation = new Transformation(this.transformation);
        if (this.sourceTransformation != null) {
            cloned.sourceTransformation = new HashMap();
            for (Map.Entry keyValuePair : this.sourceTransformation.entrySet()) {
                cloned.sourceTransformation.put(keyValuePair.getKey(), keyValuePair.getValue());
            }
        }
        cloned.sourceTypes = this.sourceTypes;
        cloned.urlSuffix = this.urlSuffix;
        cloned.useRootPath = this.useRootPath;
        cloned.longUrlSignature = this.longUrlSignature;
        return cloned;
    }

    private static Pattern identifierPattern = Pattern.compile("^(?:([^/]+)/)??(?:([^/]+)/)??(?:v(\\d+)/)?" + "(?:([^#/]+?)(?:\\.([^.#/]+))?)(?:#([^/]+))?$");

    /**
     * Parses a cloudinary identifier of the form:
* {@code [/][/][v/][.][#]} */ public Url fromIdentifier(String identifier) { Matcher matcher = identifierPattern.matcher(identifier); if (!matcher.matches()) { throw new RuntimeException(String.format("Couldn't parse identifier %s", identifier)); } String resourceType = matcher.group(1); if (resourceType != null) { resourceType(resourceType); } String type = matcher.group(2); if (type != null) { type(type); } String version = matcher.group(3); if (version != null) { version(version); } String publicId = matcher.group(4); if (publicId != null) { publicId(publicId); } String format = matcher.group(5); if (format != null) { format(format); } // Signature (group 6) is not used return this; } public Url type(String type) { this.type = type; return this; } public Url resourcType(String resourceType) { return resourceType(resourceType); } public Url resourceType(String resourceType) { this.resourceType = resourceType; return this; } public Url publicId(Object publicId) { this.publicId = ObjectUtils.asString(publicId); return this; } public Url format(String format) { this.format = format; return this; } public Url cloudName(String cloudName) { this.config.cloudName = cloudName; return this; } public Url secureDistribution(String secureDistribution) { this.config.secureDistribution = secureDistribution; return this; } public Url secureCdnSubdomain(boolean secureCdnSubdomain) { this.config.secureCdnSubdomain = secureCdnSubdomain; return this; } public Url suffix(String urlSuffix) { this.urlSuffix = urlSuffix; return this; } public Url useRootPath(boolean useRootPath) { this.useRootPath = useRootPath; return this; } public Url cname(String cname) { this.config.cname = cname; return this; } public Url version(Object version) { this.version = ObjectUtils.asString(version); return this; } public Url transformation(Transformation transformation) { this.transformation = transformation; return this; } public Url secure(boolean secure) { this.config.secure = secure; return this; } public Url privateCdn(boolean privateCdn) { this.config.privateCdn = privateCdn; return this; } public Url cdnSubdomain(boolean cdnSubdomain) { this.config.cdnSubdomain = cdnSubdomain; return this; } public Url shorten(boolean shorten) { this.config.shorten = shorten; return this; } public Transformation transformation() { if (this.transformation == null) this.transformation = new Transformation(); return this.transformation; } public Url signed(boolean signUrl) { this.signUrl = signUrl; return this; } /** * Set the authorization token. If authToken has already been set the parameter is merged with the current value unless the parameter value is null or NULL_AUTH_TOKEN.

* For example, to generate an authorized URL with a different duration:
*
     *  {@code
     *   cloudinary.config.authToken = new AuthToken(KEY).duration(500);
     *   // later...
     *   cloudinary.url().signed(true).authToken(new AuthToken().duration(300))
     *                   .type("authenticated").version("1486020273").generate("sample.jpg");
     *  }
     * 
* * @param authToken an authorization token object * @return this */ public Url authToken(AuthToken authToken) { if (this.authToken == null) { this.authToken = authToken; } else if (authToken == null || authToken.equals(AuthToken.NULL_AUTH_TOKEN)) { this.authToken = authToken; } else { this.authToken = this.authToken.merge(authToken); } return this; } public Url longUrlSignature(boolean isLong) { this.longUrlSignature = isLong; return this; } public Url sourceTransformation(Map sourceTransformation) { this.sourceTransformation = sourceTransformation; return this; } public Url sourceTransformationFor(String source, Transformation transformation) { if (this.sourceTransformation == null) { this.sourceTransformation = new HashMap(); } this.sourceTransformation.put(source, transformation); return this; } public Url sourceTypes(String[] sourceTypes) { this.sourceTypes = sourceTypes; return this; } public Url fallbackContent(String fallbackContent) { this.fallbackContent = fallbackContent; return this; } public Url posterTransformation(Transformation posterTransformation) { this.posterTransformation = posterTransformation; return this; } @SuppressWarnings("rawtypes") public Url posterTransformation(List posterTransformations) { this.posterTransformation = new Transformation(posterTransformations); return this; } @SuppressWarnings({"rawtypes", "unchecked"}) public Url posterTransformation(Map posterTransformations) { List transformations = new ArrayList(); Map copy = new HashMap(); copy.putAll(posterTransformations); transformations.add(copy); this.posterTransformation = new Transformation(transformations); return this; } public Url posterSource(String posterSource) { this.posterSource = posterSource; return this; } public Url posterUrl(Url posterUrl) { this.posterUrl = posterUrl; return this; } public Url poster(Object poster) { if (poster instanceof Transformation) { return posterTransformation((Transformation) poster); } else if (poster instanceof List) { return posterTransformation((List) poster); } else if (poster instanceof Map) { return posterTransformation((Map) poster); } else if (poster instanceof Url) { return posterUrl((Url) poster); } else if (poster instanceof String) { return posterSource((String) poster); } else if (poster == null || poster.equals(Boolean.FALSE)) { return posterSource(""); } else { throw new IllegalArgumentException("Illegal value type supplied to poster. must be one of: , >, , , "); } } /** * Indicates whether to add '/v1/' to the URL when the public ID includes folders and a 'version' value was * not defined. * When no version is explicitly specified and the public id contains folders, a default v1 version * is added to the url. This boolean can disable that behaviour. * @param forceVersion Whether to add the version to the url. * @return This same Url instance for chaining. */ public Url forceVersion(boolean forceVersion){ this.config.forceVersion = forceVersion; return this; } public String generate() { return generate(null); } public String generate(String source) { boolean useRootPath = this.config.useRootPath; if (this.useRootPath != null) { useRootPath = this.useRootPath; } if (StringUtils.isEmpty(this.config.cloudName)) { throw new IllegalArgumentException("Must supply cloud_name in tag or in configuration"); } if (source == null) { if (publicId == null) { if (this.source == null) { return null; } source = this.source; } else { source = publicId; } } boolean httpSource = StringUtils.isHttpUrl(source); if (httpSource) { if (StringUtils.isEmpty(type) || "asset".equals(type)) { return source; } } if (type != null && type.equals("fetch") && !StringUtils.isEmpty(format)) { transformation().fetchFormat(format); this.format = null; } String transformationStr = transformation().generate(); String signature = ""; String[] finalizedSource = finalizeSource(source, format, urlSuffix); source = finalizedSource[0]; String sourceToSign = finalizedSource[1]; if (this.config.forceVersion && sourceToSign.contains("/") && !StringUtils.startWithVersionString(sourceToSign) && !httpSource && StringUtils.isEmpty(version)) { version = "1"; } if (version == null) version = ""; else version = "v" + version; if (signUrl && (authToken == null || authToken.equals(AuthToken.NULL_AUTH_TOKEN))) { SignatureAlgorithm signatureAlgorithm = longUrlSignature ? SHA256 : config.signatureAlgorithm; String toSign = StringUtils.join(new String[]{transformationStr, sourceToSign}, "/"); toSign = StringUtils.removeStartingChars(toSign, '/'); toSign = StringUtils.mergeSlashesInUrl(toSign); byte[] hash = Util.hash(toSign + this.config.apiSecret, signatureAlgorithm); signature = Base64Coder.encodeURLSafeString(hash); signature = "s--" + signature.substring(0, longUrlSignature ? 32 : 8) + "--"; } String resourceType = this.resourceType; if (resourceType == null) resourceType = "image"; String finalResourceType = finalizeResourceType(resourceType, type, urlSuffix, useRootPath, config.shorten); String prefix = unsignedDownloadUrlPrefix(source, config.cloudName, config.privateCdn, config.cdnSubdomain, config.secureCdnSubdomain, config.cname, config.secure, config.secureDistribution); String join = StringUtils.join(new String[]{prefix, finalResourceType, signature, transformationStr, version, source}, "/"); String url = StringUtils.mergeSlashesInUrl(join); if (signUrl && authToken != null && !authToken.equals(AuthToken.NULL_AUTH_TOKEN)) { try { URL tempUrl = new URL(url); String path = tempUrl.getPath(); String token = authToken.generate(path); url = url + "?" + token; } catch (MalformedURLException ignored) { } } if (cloudinary.config.analytics != null && cloudinary.config.analytics) { try { URL tempUrl = new URL(url); // if any other query param already exist on the URL do not add analytics query param. if (tempUrl.getQuery() == null) { String path = tempUrl.getPath(); url = url + "?" + cloudinary.analytics.toQueryParam(); } } catch (MalformedURLException ignored) { } } return url; } private String[] finalizeSource(String source, String format, String urlSuffix) { source = StringUtils.mergeSlashesInUrl(source); String[] result = new String[2]; String sourceToSign; if (StringUtils.isHttpUrl(source)) { source = SmartUrlEncoder.encode(source); sourceToSign = source; } else { try { source = SmartUrlEncoder.encode(URLDecoder.decode(source.replace("+", "%2B"), "UTF-8")); } catch (UnsupportedEncodingException e) { throw new RuntimeException(e); } sourceToSign = source; if (StringUtils.isNotBlank(urlSuffix)) { if (urlSuffix.contains(".") || urlSuffix.contains("/")) { throw new IllegalArgumentException("url_suffix should not include . or /"); } source = source + "/" + urlSuffix; } if (StringUtils.isNotBlank(format)) { source = source + "." + format; sourceToSign = sourceToSign + "." + format; } } result[0] = source; result[1] = sourceToSign; return result; } public String finalizeResourceType(String resourceType, String type, String urlSuffix, boolean useRootPath, boolean shorten) { if (type == null) { type = "upload"; } if (!StringUtils.isBlank(urlSuffix)) { if (resourceType.equals("image") && type.equals("upload")) { resourceType = "images"; type = null; } else if (resourceType.equals("image") && type.equals("private")) { resourceType = "private_images"; type = null; } else if (resourceType.equals("image") && type.equals("authenticated")) { resourceType = "authenticated_images"; type = null; } else if (resourceType.equals("raw") && type.equals("upload")) { resourceType = "files"; type = null; } else if (resourceType.equals("video") && type.equals("upload")) { resourceType = "videos"; type = null; } else { throw new IllegalArgumentException("URL Suffix only supported for image/upload, image/private, raw/upload, image/authenticated and video/upload"); } } if (useRootPath) { if ((resourceType.equals("image") && type.equals("upload")) || (resourceType.equals("images") && StringUtils.isBlank(type))) { resourceType = null; type = null; } else { throw new IllegalArgumentException("Root path only supported for image/upload"); } } if (shorten && resourceType.equals("image") && type.equals("upload")) { resourceType = "iu"; type = null; } String result = resourceType; if (type != null) { result += "/" + type; } return result; } public String unsignedDownloadUrlPrefix(String source, String cloudName, boolean privateCdn, boolean cdnSubdomain, Boolean secureCdnSubdomain, String cname, boolean secure, String secureDistribution) { if (this.config.cloudName.startsWith("/")) { return "/res" + this.config.cloudName; } boolean sharedDomain = !this.config.privateCdn; String prefix; if (this.config.secure) { if (StringUtils.isEmpty(this.config.secureDistribution) || this.config.secureDistribution.equals(Cloudinary.OLD_AKAMAI_SHARED_CDN)) { secureDistribution = this.config.privateCdn ? this.config.cloudName + "-res.cloudinary.com" : Cloudinary.SHARED_CDN; } if (!sharedDomain) { sharedDomain = secureDistribution.equals(Cloudinary.SHARED_CDN); } if (secureCdnSubdomain == null && sharedDomain) { secureCdnSubdomain = this.config.cdnSubdomain; } if (secureCdnSubdomain != null && secureCdnSubdomain == true) { secureDistribution = this.config.secureDistribution.replace("res.cloudinary.com", "res-" + shard(source) + ".cloudinary.com"); } prefix = "https://" + secureDistribution; } else if (StringUtils.isNotBlank(this.config.cname)) { String subdomain = this.config.cdnSubdomain ? "a" + shard(source) + "." : ""; prefix = "http://" + subdomain + this.config.cname; } else { String protocol = "http://"; cloudName = this.config.privateCdn ? this.config.cloudName + "-" : ""; String res = "res"; String subdomain = this.config.cdnSubdomain ? "-" + shard(source) : ""; String domain = ".cloudinary.com"; prefix = StringUtils.join(new String[]{protocol, cloudName, res, subdomain, domain}, ""); } if (sharedDomain) { prefix += "/" + this.config.cloudName; } return prefix; } private String shard(String input) { CRC32 crc32 = new CRC32(); crc32.update(Util.getUTF8Bytes(input)); return String.valueOf((crc32.getValue() % 5 + 5) % 5 + 1); } @SuppressWarnings("unchecked") public String imageTag(String source) { return imageTag(source, ObjectUtils.emptyMap()); } public String imageTag(Map attributes) { return imageTag(null, attributes); } public String imageTag(String source, Map attributes) { String url = generate(source); attributes = new TreeMap(attributes); // Make sure they // are ordered. if (transformation().getHtmlHeight() != null) attributes.put("height", transformation().getHtmlHeight()); if (transformation().getHtmlWidth() != null) attributes.put("width", transformation().getHtmlWidth()); boolean hiDPI = transformation().isHiDPI(); boolean responsive = transformation().isResponsive(); if (!config.clientHints && (hiDPI || responsive)) { attributes.put("data-src", url); String extraClass = responsive ? "cld-responsive" : "cld-hidpi"; attributes.put("class", (StringUtils.isBlank(attributes.get("class")) ? "" : attributes.get("class") + " ") + extraClass); String responsivePlaceholder = attributes.remove("responsive_placeholder"); if ("blank".equals(responsivePlaceholder)) { responsivePlaceholder = CL_BLANK; } url = responsivePlaceholder; } StringBuilder builder = new StringBuilder(); builder.append(" attr : attributes.entrySet()) { builder.append(" ").append(attr.getKey()).append("='").append(attr.getValue()).append("'"); } builder.append("/>"); return builder.toString(); } public String videoTag() { return videoTag("", new HashMap()); } public String videoTag(String source) { return videoTag(source, new HashMap()); } public String videoTag(Map attributes) { return videoTag("", attributes); } private String finalizePosterUrl(String source) { String posterUrl = null; if (this.posterUrl != null) { posterUrl = this.posterUrl.generate(); } else if (this.posterTransformation != null) { posterUrl = this.clone().format("jpg").transformation(new Transformation(this.posterTransformation)) .generate(source); } else if (this.posterSource != null) { if (!StringUtils.isEmpty(this.posterSource)) posterUrl = this.clone().format("jpg").generate(this.posterSource); } else { posterUrl = this.clone().format("jpg").generate(source); } return posterUrl; } private void appendVideoSources(StringBuilder html, String source, String sourceType) { Url sourceUrl = this.clone(); if (this.sourceTransformation != null) { Transformation transformation = this.transformation; Transformation sourceTransformation = null; if (this.sourceTransformation.get(sourceType) != null) sourceTransformation = new Transformation(this.sourceTransformation.get(sourceType)); if (transformation == null) { transformation = sourceTransformation; } else if (sourceTransformation != null) { transformation = transformation.chainWith(sourceTransformation); } sourceUrl.transformation(transformation); } String src = sourceUrl.format(sourceType).generate(source); String videoType = sourceType; if (sourceType.equals("ogv")) videoType = "ogg"; String mimeType = "video/" + videoType; html.append(""); } public String videoTag(String source, Map attributes) { if (StringUtils.isEmpty(source)) source = this.source; if (StringUtils.isEmpty(source)) source = publicId; if (StringUtils.isEmpty(source)) throw new IllegalArgumentException("must supply source or public id"); source = VIDEO_EXTENSION_RE.matcher(source).replaceFirst(""); if (this.resourceType == null) this.resourceType = "video"; attributes = new TreeMap(attributes); // Make sure they are ordered. String[] sourceTypes = this.sourceTypes; if (sourceTypes == null) { sourceTypes = DEFAULT_VIDEO_SOURCE_TYPES; } String posterUrl = this.finalizePosterUrl(source); if (!StringUtils.isEmpty(posterUrl)) attributes.put("poster", posterUrl); StringBuilder html = new StringBuilder().append(" 1; if (!multiSource) { url = generate(source + "." + sourceTypes[0]); attributes.put("src", url); } else { generate(source); } if (this.transformation.getHtmlHeight() != null) attributes.put("height", this.transformation.getHtmlHeight()); if (attributes.containsKey("html_height")) attributes.put("height", attributes.remove("html_height")); if (this.transformation.getHtmlWidth() != null) attributes.put("width", this.transformation.getHtmlWidth()); if (attributes.containsKey("html_width")) attributes.put("width", attributes.remove("html_width")); for (Map.Entry attr : attributes.entrySet()) { html.append(" ").append(attr.getKey()); if (attr.getValue() != null) { String value = ObjectUtils.asString(attr.getValue()); html.append("='").append(value).append("'"); } } html.append(">"); if (multiSource) { for (String sourceType : sourceTypes) { this.appendVideoSources(html, source, sourceType); } } if (this.fallbackContent != null) html.append(this.fallbackContent); html.append(""); return html.toString(); } public String generateSpriteCss(String source) { this.type = "sprite"; if (!source.endsWith(".css")) this.format = "css"; return generate(source); } public Url source(String source) { this.source = source; return this; } public Url source(StoredFile source) { if (source.getResourceType() != null) this.resourceType = source.getResourceType(); if (source.getType() != null) this.type = source.getType(); if (source.getVersion() != null) this.version = source.getVersion().toString(); this.format = source.getFormat(); this.source = source.getPublicId(); return this; } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy