diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..b9486a2
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,179 @@
+# Created by https://www.gitignore.io/api/java,eclipse,intellij,linux,maven,macos
+
+### Eclipse ###
+
+.metadata
+bin/
+tmp/
+*.tmp
+*.bak
+*.swp
+*~.nib
+local.properties
+.settings/
+.loadpath
+.recommenders
+
+# Eclipse Core
+.project
+
+# External tool builders
+.externalToolBuilders/
+
+# Locally stored "Eclipse launch configurations"
+*.launch
+
+# PyDev specific (Python IDE for Eclipse)
+*.pydevproject
+
+# CDT-specific (C/C++ Development Tooling)
+.cproject
+
+# JDT-specific (Eclipse Java Development Tools)
+.classpath
+
+# Java annotation processor (APT)
+.factorypath
+
+# PDT-specific (PHP Development Tools)
+.buildpath
+
+# sbteclipse plugin
+.target
+
+# Tern plugin
+.tern-project
+
+# TeXlipse plugin
+.texlipse
+
+# STS (Spring Tool Suite)
+.springBeans
+
+# Code Recommenders
+.recommenders/
+
+
+### Intellij ###
+# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm
+# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
+
+# User-specific stuff:
+.idea/workspace.xml
+.idea/tasks.xml
+.idea*
+*.iml
+# Sensitive or high-churn files:
+.idea/dataSources/
+.idea/dataSources.ids
+.idea/dataSources.xml
+.idea/dataSources.local.xml
+.idea/sqlDataSources.xml
+.idea/dynamic.xml
+.idea/uiDesigner.xml
+
+# Gradle:
+.idea/gradle.xml
+.idea/libraries
+
+# Mongo Explorer plugin:
+.idea/mongoSettings.xml
+
+## File-based project format:
+*.iws
+
+## Plugin-specific files:
+
+# IntelliJ
+/out/
+
+# mpeltonen/sbt-idea plugin
+.idea_modules/
+
+# JIRA plugin
+atlassian-ide-plugin.xml
+
+# Crashlytics plugin (for Android Studio and IntelliJ)
+com_crashlytics_export_strings.xml
+crashlytics.properties
+crashlytics-build.properties
+fabric.properties
+
+### Intellij Patch ###
+# Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721
+
+# *.iml
+# modules.xml
+# .idea/misc.xml
+# *.ipr
+
+
+### Linux ###
+*~
+
+# temporary files which can be created if a process still has a handle open of a deleted file
+.fuse_hidden*
+
+# KDE directory preferences
+.directory
+
+# Linux trash folder which might appear on any partition or disk
+.Trash-*
+
+# .nfs files are created when an open file is removed but is still being accessed
+.nfs*
+
+
+### Maven ###
+target/
+pom.xml.tag
+pom.xml.releaseBackup
+pom.xml.versionsBackup
+pom.xml.next
+release.properties
+dependency-reduced-pom.xml
+buildNumber.properties
+.mvn/timing.properties
+
+# Exclude maven wrapper
+!/.mvn/wrapper/maven-wrapper.jar
+
+
+### macOS ###
+*.DS_Store
+.AppleDouble
+.LSOverride
+
+# Icon must end with two \r
+Icon
+# Thumbnails
+._*
+# Files that might appear in the root of a volume
+.DocumentRevisions-V100
+.fseventsd
+.Spotlight-V100
+.TemporaryItems
+.Trashes
+.VolumeIcon.icns
+.com.apple.timemachine.donotpresent
+# Directories potentially created on remote AFP share
+.AppleDB
+.AppleDesktop
+Network Trash Folder
+Temporary Items
+.apdisk
+
+
+### Java ###
+*.class
+
+# Mobile Tools for Java (J2ME)
+.mtj.tmp/
+
+# Package Files #
+*.jar
+*.war
+*.ear
+
+# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
+hs_err_pid*
diff --git a/.travis.yml b/.travis.yml
new file mode 100644
index 0000000..844b577
--- /dev/null
+++ b/.travis.yml
@@ -0,0 +1,8 @@
+language: java
+jdk: oraclejdk8
+
+install: mvn clean install -q
+
+cache:
+ directories:
+ - '$HOME/.m2/repo'
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..6853811
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,202 @@
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "{}"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright (C) 2018 Burak SARP (sarp_burak@yahoo.com)
+
+ 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.
+
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..b65cb52
--- /dev/null
+++ b/README.md
@@ -0,0 +1,58 @@
+Apache Traffic Server Url Signer 4j
+========
+
+[![Apache License](http://img.shields.io/badge/license-ASL-blue.svg)](https://github.com/buraksarp/ats-url-signer/blob/master/LICENSE)
+[![Build Status](https://travis-ci.org/buraksarp/ats-url-signer.svg)](https://travis-ci.org/buraksarp/ats-url-signer)
+[![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg)](http://makeapullrequest.com)
+
+:lock: Java client to use Signed URL Plugin [Apache Traffic Server](http://trafficserver.apache.org), implementation based on the [Signed URL Plugin](https://docs.trafficserver.apache.org/en/latest/admin-guide/plugins/url_sig.en.html)
+
+Usage
+--------
+
+We will be at Maven Central Repository soon:
+```xml
+
+ io.github.buraksarp.urlsigner
+ urlsigner
+ 1.0
+
+```
+or Gradle:
+```groovy
+compile 'io.github.buraksarp.urlsigner:urlsigner:1.0'
+```
+
+Supported Operations & Examples
+--------
+
+#### Sign URL
+
+```java
+String signedUrl = UrlSign.builder().setContentUrl("http://edge.sarp.net/public/magazine/object.pdf")
+ .setClientIp("1.2.3.4")
+ .setTimeToLive(2, TimeUnit.HOURS)
+ .setParts(SigningPart.ONLY_ALL_DIRECTORY)
+ .signWith(SignatureAlgorithm.HMAC_SHA1, "secret_key", 0)
+ .compact();
+```
+
+#### (More operations to be added)
+
+Building from the source
+--------
+```
+git clone https://github.com/buraksarp/ats-url-signer
+cd ats-url-signer
+mvn clean install
+```
+
+Documentation
+--------
+
+Please see the [documentation](https://docs.trafficserver.apache.org/en/latest/admin-guide/plugins/url_sig.en.html)
+
+License
+--------
+
+Please check the [license](https://github.com/buraksarp/ats-url-signer/blob/master/LICENSE) file.
diff --git a/pom.xml b/pom.xml
new file mode 100644
index 0000000..4330482
--- /dev/null
+++ b/pom.xml
@@ -0,0 +1,160 @@
+
+
+ 4.0.0
+
+ io.github.buraksarp
+ urlsigner
+ 1.0
+ jar
+ ats-url-signer
+ Apache Traffic Server URL Signer 4j
+ http://github.com/buraksarp/ats-url-signer
+
+
+
+ The Apache License, Version 2.0
+ http://www.apache.org/licenses/LICENSE-2.0.txt
+
+
+
+
+
+ Burak Sarp
+ https://www.github.com/buraksarp
+
+
+
+
+ scm:git:git@github.com:buraksarp/ats-url-signer.git
+ scm:git:git@github.com:buraksarp/ats-url-signer.git
+ git@github.com:buraksarp/ats-url-signer.git
+
+
+
+ 2.9.7
+ 1.6
+ 1.8
+ 1.8
+ 3.0
+
+
+
+
+ joda-time
+ joda-time
+ ${joda-time.version}
+
+
+
+ junit
+ junit
+ 4.12
+ test
+
+
+
+ org.mockito
+ mockito-all
+ 1.10.19
+ test
+
+
+
+
+
+ ossrh
+ https://oss.sonatype.org/content/repositories/snapshots
+
+
+
+
+
+
+ org.sonatype.plugins
+ nexus-staging-maven-plugin
+ 1.6.3
+ true
+
+ ossrh
+ https://oss.sonatype.org/
+ true
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+ ${maven-compiler-plugin.version}
+
+
+ ${maven-compiler-plugin.java.target.version}
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+
+
+ 1.8
+
+
+
+
+
+
+
+ release-sign-artifacts
+
+
+ gpg-sign
+ true
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-source-plugin
+ 3.0.1
+
+
+ attach-sources
+
+ jar-no-fork
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-javadoc-plugin
+
+
+ attach-javadocs
+
+ jar
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-gpg-plugin
+ ${maven-gpg-plugin.version}
+
+
+ sign-artifacts
+ verify
+
+ sign
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/main/java/io/github/buraksarp/urlsigner/AtsSignedUrlBuilder.java b/src/main/java/io/github/buraksarp/urlsigner/AtsSignedUrlBuilder.java
new file mode 100644
index 0000000..400c942
--- /dev/null
+++ b/src/main/java/io/github/buraksarp/urlsigner/AtsSignedUrlBuilder.java
@@ -0,0 +1,171 @@
+package io.github.buraksarp.urlsigner;
+
+import io.github.buraksarp.urlsigner.enums.SigningPart;
+import org.joda.time.DateTime;
+import io.github.buraksarp.urlsigner.crypto.MacSigner;
+import io.github.buraksarp.urlsigner.enums.SignatureAlgorithm;
+import io.github.buraksarp.urlsigner.util.Assert;
+
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.concurrent.TimeUnit;
+
+public class AtsSignedUrlBuilder implements SignedUrlBuilder {
+
+ private String contentUrl;
+ private int timeToLive;
+ private TimeUnit timeUnit;
+ private String clientIp;
+ private int keyIndex;
+ private SigningPart signingPart;
+ private SignatureAlgorithm signatureAlgorithm;
+ private String secretKey;
+
+ public static final String X_LATE = "0123456789abcdef";
+
+ public SignedUrlBuilder setContentUrl(String contentUrl) {
+ this.contentUrl = contentUrl;
+ return this;
+ }
+
+ public SignedUrlBuilder setTimeToLive(int timeToLive, TimeUnit timeUnit) {
+ this.timeToLive = timeToLive;
+ this.timeUnit = timeUnit;
+ return this;
+ }
+
+ public SignedUrlBuilder setClientIp(String clientIp) {
+ this.clientIp = clientIp;
+ return this;
+ }
+
+ public SignedUrlBuilder setParts(SigningPart signingPart) {
+ this.signingPart = signingPart;
+ return this;
+ }
+
+ public SignedUrlBuilder signWith(SignatureAlgorithm signatureAlgorithm, String secretKey, int keyIndex) {
+ this.signatureAlgorithm = signatureAlgorithm;
+ this.secretKey = secretKey;
+ this.keyIndex = keyIndex;
+ return this;
+ }
+
+ public String compact() {
+
+ Assert.notNull(signingPart, "signing part must NOT be null");
+ Assert.notNullorEmpty(secretKey, "secret key must NOT be null or empty");
+ Assert.notNull(timeUnit, "Time to live must be set");
+ Assert.notNull(signatureAlgorithm, "Sign with must be set");
+
+ StringBuilder sb = new StringBuilder();
+ sb.append(this.getUrlPart(contentUrl, signingPart)).append("?");
+ sb.append(Assert.hasText(clientIp) ? "C=" + clientIp + "&" : "");
+ sb.append("E=").append(this.getExpireTimeSeconds()).append("&");
+ sb.append("A=").append(this.signatureAlgorithm.getValue()).append("&");
+ sb.append("K=").append(this.keyIndex).append("&");
+ sb.append("P=").append(this.signingPart.getValue()).append("&");
+ sb.append("S=");
+
+ MacSigner macSigner = new MacSigner(this.signatureAlgorithm, this.secretKey);
+ byte[] digest = macSigner.sign(sb.toString().getBytes());
+
+ String token = this.hexlate(digest);
+ sb.append(token);
+
+ return sb.toString();
+ }
+
+ private String hexlate(byte[] bytes) {
+
+ char[] chars = new char[bytes.length * 2];
+
+ for (int i = 0; i < bytes.length; i++) {
+ int val = bytes[i];
+ if (val < 0) {
+ val += 256;
+ }
+ chars[(2 * i)] = X_LATE.charAt(val / 16);
+ chars[(2 * i + 1)] = X_LATE.charAt(val % 16);
+ }
+
+ return new String(chars);
+ }
+
+ private long getExpireTimeSeconds() {
+
+ DateTime dateTime = new DateTime();
+
+ switch (timeUnit) {
+ case DAYS:
+ dateTime = dateTime.plusDays(timeToLive);
+ break;
+ case HOURS:
+ dateTime = dateTime.plusHours(timeToLive);
+ break;
+ case MINUTES:
+ dateTime = dateTime.plusMinutes(timeToLive);
+ break;
+ case SECONDS:
+ dateTime = dateTime.plusSeconds(timeToLive);
+ break;
+ case MILLISECONDS:
+ dateTime = dateTime.plusMillis(timeToLive);
+ break;
+ default:
+ throw new IllegalStateException(timeUnit.name() + " is NOT supported, try [DAYS, HOURS, MINUTES, SECONDS, MILLISECONDS]");
+ }
+
+ //seconds since Jan 01 1970
+ return TimeUnit.MILLISECONDS.toSeconds(dateTime.getMillis());
+ }
+
+ private String getUrlPart(String contentUrl, SigningPart signingPart) {
+
+ URL url;
+ try {
+ url = new URL(contentUrl);
+ } catch (MalformedURLException e) {
+ throw new IllegalStateException("Malformed URL");
+ }
+
+ String urlSigningPart = null;
+
+ switch (signingPart) {
+ case FQDN_AND_ALL_DIRECTORY:
+ urlSigningPart = url.getHost() + url.getPath();
+ break;
+ case ONLY_ALL_DIRECTORY:
+ urlSigningPart = url.getPath().substring(1);
+ break;
+ case FQDN_AND_FIRST_DIRECTORY:
+ urlSigningPart = url.getHost() + getDirectory(url.getPath(), 1);
+ break;
+ case ONLY_FIRST_TWO_DIRECTORY:
+ urlSigningPart = getDirectory(url.getPath(), 2).substring(1);
+ }
+
+ return urlSigningPart;
+ }
+
+ private String getDirectory(String path, int directoryCount) {
+
+ char[] chars = path.toCharArray();
+ String directory = null;
+
+ int counter = 1;
+ for(int i = 1; i < chars.length; i++) {
+ if(chars[i] == '/') {
+ if (counter == directoryCount) {
+ directory = path.substring(0, i);
+ break;
+ } else {
+ counter++;
+ }
+ }
+ }
+
+ return directory == null ? path : directory;
+ }
+
+}
diff --git a/src/main/java/io/github/buraksarp/urlsigner/SignedUrlBuilder.java b/src/main/java/io/github/buraksarp/urlsigner/SignedUrlBuilder.java
new file mode 100644
index 0000000..6a01f26
--- /dev/null
+++ b/src/main/java/io/github/buraksarp/urlsigner/SignedUrlBuilder.java
@@ -0,0 +1,23 @@
+package io.github.buraksarp.urlsigner;
+
+import io.github.buraksarp.urlsigner.enums.SigningPart;
+import io.github.buraksarp.urlsigner.enums.SignatureAlgorithm;
+
+import java.util.concurrent.TimeUnit;
+
+public interface SignedUrlBuilder {
+
+ SignedUrlBuilder setContentUrl(String contentUrl);
+
+ SignedUrlBuilder setTimeToLive(int timeToLive, TimeUnit timeUnit);
+
+ SignedUrlBuilder setClientIp(String clientIp);
+
+ SignedUrlBuilder setParts(SigningPart signingPart);
+
+ SignedUrlBuilder signWith(SignatureAlgorithm signatureAlgorithm, String secretKey, int keyIndex);
+
+ String compact();
+
+}
+
diff --git a/src/main/java/io/github/buraksarp/urlsigner/UrlSign.java b/src/main/java/io/github/buraksarp/urlsigner/UrlSign.java
new file mode 100644
index 0000000..4ac979b
--- /dev/null
+++ b/src/main/java/io/github/buraksarp/urlsigner/UrlSign.java
@@ -0,0 +1,10 @@
+package io.github.buraksarp.urlsigner;
+
+public final class UrlSign {
+
+ private UrlSign () {}
+
+ public static AtsSignedUrlBuilder builder() {
+ return new AtsSignedUrlBuilder();
+ }
+}
diff --git a/src/main/java/io/github/buraksarp/urlsigner/crypto/MacSigner.java b/src/main/java/io/github/buraksarp/urlsigner/crypto/MacSigner.java
new file mode 100644
index 0000000..f518945
--- /dev/null
+++ b/src/main/java/io/github/buraksarp/urlsigner/crypto/MacSigner.java
@@ -0,0 +1,42 @@
+package io.github.buraksarp.urlsigner.crypto;
+
+import io.github.buraksarp.urlsigner.exception.SignatureException;
+import io.github.buraksarp.urlsigner.enums.SignatureAlgorithm;
+
+import javax.crypto.Mac;
+import javax.crypto.spec.SecretKeySpec;
+import java.security.InvalidKeyException;
+import java.security.Key;
+import java.security.NoSuchAlgorithmException;
+
+public class MacSigner implements Signer {
+
+ private final SignatureAlgorithm signatureAlgorithm;
+ private final String secretKey;
+
+ public MacSigner (SignatureAlgorithm signatureAlgorithm, String secretKey) {
+ this.signatureAlgorithm = signatureAlgorithm;
+ this.secretKey = secretKey;
+ }
+
+ @Override
+ public byte[] sign(byte[] data) {
+ Mac mac = getMacInstance();
+ return mac.doFinal(data);
+ }
+
+ private Mac getMacInstance() {
+
+ Mac mac;
+ try {
+ mac = Mac.getInstance(signatureAlgorithm.getJcaName());
+ Key key = new SecretKeySpec(secretKey.getBytes(), signatureAlgorithm.getJcaName());
+ mac.init(key);
+ } catch (NoSuchAlgorithmException | InvalidKeyException e) {
+ throw new SignatureException(e.getMessage());
+ }
+
+ return mac;
+ }
+
+}
diff --git a/src/main/java/io/github/buraksarp/urlsigner/crypto/Signer.java b/src/main/java/io/github/buraksarp/urlsigner/crypto/Signer.java
new file mode 100644
index 0000000..6848d88
--- /dev/null
+++ b/src/main/java/io/github/buraksarp/urlsigner/crypto/Signer.java
@@ -0,0 +1,7 @@
+package io.github.buraksarp.urlsigner.crypto;
+
+public interface Signer {
+
+ byte[] sign(byte[] data);
+
+}
diff --git a/src/main/java/io/github/buraksarp/urlsigner/enums/SignatureAlgorithm.java b/src/main/java/io/github/buraksarp/urlsigner/enums/SignatureAlgorithm.java
new file mode 100644
index 0000000..375bb31
--- /dev/null
+++ b/src/main/java/io/github/buraksarp/urlsigner/enums/SignatureAlgorithm.java
@@ -0,0 +1,29 @@
+package io.github.buraksarp.urlsigner.enums;
+
+public enum SignatureAlgorithm {
+
+ HMAC_SHA1(1, "HmacSHA1", "HMAC using SHA-1"),
+ HMAC_MD5(2, "HmacMD5","HMAC using MD5");
+
+ private final Integer value;
+ private final String jcaName;
+ private final String description;
+
+ SignatureAlgorithm(Integer value, String jcaName, String description) {
+ this.value = value;
+ this.jcaName = jcaName;
+ this.description = description;
+ }
+
+ public Integer getValue() {
+ return value;
+ }
+
+ public String getJcaName() {
+ return jcaName;
+ }
+
+ public String getDescription() {
+ return description;
+ }
+}
diff --git a/src/main/java/io/github/buraksarp/urlsigner/enums/SigningPart.java b/src/main/java/io/github/buraksarp/urlsigner/enums/SigningPart.java
new file mode 100644
index 0000000..e9d2647
--- /dev/null
+++ b/src/main/java/io/github/buraksarp/urlsigner/enums/SigningPart.java
@@ -0,0 +1,26 @@
+package io.github.buraksarp.urlsigner.enums;
+
+public enum SigningPart {
+
+ FQDN_AND_ALL_DIRECTORY("1", "Use the FQDN and all directory parts for signature verification"),
+ FQDN_AND_FIRST_DIRECTORY("110", "Use the FQDN and first directory for signature verification, but ignore the remainder of the path"),
+ ONLY_ALL_DIRECTORY("01", "Ignore the FQDN, but verify using all directory parts"),
+ ONLY_FIRST_TWO_DIRECTORY("0110", "Ignore the FQDN, and use only the first two directory parts, skipping the remainder, for signatures");
+
+ private final String value;
+ private final String effect;
+
+ SigningPart(String value, String effect) {
+ this.value = value;
+ this.effect = effect;
+ }
+
+ public String getValue() {
+ return value;
+ }
+
+ public String getEffect() {
+ return effect;
+ }
+
+}
diff --git a/src/main/java/io/github/buraksarp/urlsigner/exception/SignatureException.java b/src/main/java/io/github/buraksarp/urlsigner/exception/SignatureException.java
new file mode 100644
index 0000000..7c558f3
--- /dev/null
+++ b/src/main/java/io/github/buraksarp/urlsigner/exception/SignatureException.java
@@ -0,0 +1,13 @@
+package io.github.buraksarp.urlsigner.exception;
+
+public class SignatureException extends RuntimeException {
+
+ public SignatureException(String message) {
+ super(message);
+ }
+
+ public SignatureException(String message, Throwable cause) {
+ super(message, cause);
+ }
+
+}
diff --git a/src/main/java/io/github/buraksarp/urlsigner/exception/UrlSignException.java b/src/main/java/io/github/buraksarp/urlsigner/exception/UrlSignException.java
new file mode 100644
index 0000000..08d1356
--- /dev/null
+++ b/src/main/java/io/github/buraksarp/urlsigner/exception/UrlSignException.java
@@ -0,0 +1,12 @@
+package io.github.buraksarp.urlsigner.exception;
+
+public class UrlSignException extends RuntimeException {
+
+ public UrlSignException(String message) {
+ super(message);
+ }
+
+ public UrlSignException(String message, Throwable cause) {
+ super(message, cause);
+ }
+}
diff --git a/src/main/java/io/github/buraksarp/urlsigner/util/Assert.java b/src/main/java/io/github/buraksarp/urlsigner/util/Assert.java
new file mode 100644
index 0000000..0797ffc
--- /dev/null
+++ b/src/main/java/io/github/buraksarp/urlsigner/util/Assert.java
@@ -0,0 +1,26 @@
+package io.github.buraksarp.urlsigner.util;
+
+public final class Assert {
+
+ private Assert() {}
+
+ public static boolean hasText(String text) {
+ if (text == null || text.trim().length() == 0) {
+ return false;
+ }
+ return true;
+ }
+
+ public static void notNullorEmpty(String text, String message) {
+ if (text == null || text.trim().length() == 0) {
+ throw new IllegalArgumentException(message);
+ }
+ }
+
+ public static void notNull(Object object, String message) {
+ if(object == null) {
+ throw new IllegalArgumentException(message);
+ }
+ }
+
+}
diff --git a/src/test/java/io/github/buraksarp/urlsigner/AtsUrlSignerTest.java b/src/test/java/io/github/buraksarp/urlsigner/AtsUrlSignerTest.java
new file mode 100644
index 0000000..33d1204
--- /dev/null
+++ b/src/test/java/io/github/buraksarp/urlsigner/AtsUrlSignerTest.java
@@ -0,0 +1,4 @@
+package io.github.buraksarp.urlsigner;
+
+public class AtsUrlSignerTest {
+}