Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,10 @@ Documentation: [Javadocs](https://dropbox.github.io/dropbox-sdk-java/)

### Java Version

The current release of Dropbox SDK Java supports Java 17+.
Starting with version 8.0.0, Dropbox SDK Java requires Java 21+.

If your project needs Java 8 through Java 20 support, use the latest 7.x release
of the SDK. The 7.x line remains available for Java 8+.

### Android Version

Expand Down Expand Up @@ -370,7 +373,7 @@ dependencies {
The JAR's manifest has the following line:

```
Require-Capability: osgi.ee;filter:="(&(osgi.ee=JavaSE)(version=17))"
Require-Capability: osgi.ee;filter:="(&(osgi.ee=JavaSE)(version=21))"
```

Most OSGi containers should provide this capability. Unfortunately, some OSGi containers don't do this correctly and will reject the bundle JAR in the OSGi subsystem context.
Expand Down
5 changes: 3 additions & 2 deletions android/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,14 @@ android {
}

compileOptions {
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
sourceCompatibility = JavaVersion.VERSION_21
targetCompatibility = JavaVersion.VERSION_21
}
}

kotlin {
compilerOptions {
jvmTarget.set(org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_21)
freeCompilerArgs.add('-Xexplicit-api=strict')
}
}
Expand Down
8 changes: 4 additions & 4 deletions core/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@ dependencyGuard {
}

java {
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
sourceCompatibility = JavaVersion.VERSION_21
targetCompatibility = JavaVersion.VERSION_21
}

ext {
Expand Down Expand Up @@ -105,7 +105,7 @@ configurations {
}

tasks.withType(JavaCompile).configureEach {
options.release.set(17)
options.release.set(21)
}

tasks.named("compileJava", JavaCompile) {
Expand Down Expand Up @@ -215,7 +215,7 @@ tasks.named("javadoc", Javadoc) {
if (JavaVersion.current().isJava8Compatible()) {
options.addBooleanOption "Xdoclint:all,-missing", true
}
options.addStringOption "link", "https://docs.oracle.com/en/java/javase/17/docs/api/"
options.addStringOption "link", "https://docs.oracle.com/en/java/javase/21/docs/api/"
}

tasks.named("jar", Jar) {
Expand Down
10 changes: 2 additions & 8 deletions core/src/main/java/com/dropbox/core/DbxOAuth1Upgrader.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,14 @@
import com.dropbox.core.json.JsonReadException;
import com.dropbox.core.json.JsonReader;
import com.dropbox.core.v1.DbxClientV1;
import static com.dropbox.core.util.LangUtil.mkAssert;

import com.fasterxml.jackson.core.JsonLocation;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonToken;

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;

/**
Expand Down Expand Up @@ -106,12 +105,7 @@ private String buildOAuth1Header(DbxOAuth1AccessToken token)

private static String encode(String s)
{
try {
return URLEncoder.encode(s, "UTF-8");
}
catch (UnsupportedEncodingException ex) {
throw mkAssert("UTF-8 should always be supported", ex);
}
return URLEncoder.encode(s, StandardCharsets.UTF_8);
}

/**
Expand Down
14 changes: 5 additions & 9 deletions core/src/main/java/com/dropbox/core/DbxPKCEManager.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,14 @@
import com.dropbox.core.util.LangUtil;
import com.dropbox.core.v2.DbxRawClientV2;

import java.io.IOException;
import java.io.Serializable;
import java.io.UnsupportedEncodingException;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;

import static com.dropbox.core.util.StringUtil.urlSafeBase64Encode;

/**
* This class should be lib/jar private. We make it public so that Android related code can use it.
*
Expand All @@ -29,6 +26,7 @@ public class DbxPKCEManager {
private static final SecureRandom RAND = new SecureRandom();
private static final String CODE_VERIFIER_CHAR_SET =
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._~";
private static final Base64.Encoder UrlSafeBase64Encoder = Base64.getUrlEncoder().withoutPadding();

private String codeVerifier;
private String codeChallenge;
Expand Down Expand Up @@ -61,12 +59,10 @@ String generateCodeVerifier() {
static String generateCodeChallenge(String codeVerifier) {
try {
MessageDigest digest = MessageDigest.getInstance("SHA-256");
byte[] signiture = digest.digest(codeVerifier.getBytes("US-ASCII"));
return urlSafeBase64Encode(signiture).replaceAll("=+$", ""); // remove trailing equal
byte[] signature = digest.digest(codeVerifier.getBytes(StandardCharsets.US_ASCII));
return UrlSafeBase64Encoder.encodeToString(signature);
} catch (NoSuchAlgorithmException e) {
throw LangUtil.mkAssert("Impossible", e);
} catch (UnsupportedEncodingException e) {
throw LangUtil.mkAssert("Impossible", e);
}
}

Expand Down
7 changes: 0 additions & 7 deletions core/src/main/java/com/dropbox/core/DbxPKCEWebAuth.java
Original file line number Diff line number Diff line change
@@ -1,18 +1,11 @@
package com.dropbox.core;

import com.dropbox.core.http.HttpRequestor;
import com.dropbox.core.util.LangUtil;
import com.dropbox.core.v2.DbxRawClientV2;

import java.io.UnsupportedEncodingException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.util.HashMap;
import java.util.Map;

import static com.dropbox.core.util.StringUtil.urlSafeBase64Encode;

/**
*
*
Expand Down
14 changes: 4 additions & 10 deletions core/src/main/java/com/dropbox/core/DbxRequestUtil.java
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
package com.dropbox.core;

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URLEncoder;
import java.nio.charset.CharacterCodingException;
import java.nio.charset.StandardCharsets;
import java.util.concurrent.TimeUnit;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.concurrent.ThreadLocalRandom;

import com.dropbox.core.stone.StoneSerializer;
import com.dropbox.core.v2.auth.AuthError;
Expand All @@ -35,16 +35,10 @@
/*>>> import checkers.nullness.quals.Nullable; */

public final class DbxRequestUtil {
private static final Random RAND = new Random();

public static DbxGlobalCallbackFactory sharedCallbackFactory;

public static String encodeUrlParam(String s) {
try {
return URLEncoder.encode(s, "UTF-8");
} catch (UnsupportedEncodingException ex) {
throw mkAssert("UTF-8 should always be supported", ex);
}
return URLEncoder.encode(s, StandardCharsets.UTF_8);
}

public static String buildUrlWithParams(/*@Nullable*/String userLocale,
Expand Down Expand Up @@ -572,7 +566,7 @@ public static <T, E extends Throwable> T runAndRetry(int maxRetries, RequestMake

// add a random jitter to the backoff to avoid stampeding herd. This is especially
// useful for ServerExceptions, where backoff is 0.
backoff += RAND.nextInt(1000);
backoff += ThreadLocalRandom.current().nextInt(1000);

if (backoff > 0L) {
try {
Expand Down
33 changes: 7 additions & 26 deletions core/src/main/java/com/dropbox/core/json/JsonDateReader.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,11 @@
import com.fasterxml.jackson.core.JsonParser;

import java.io.IOException;
import java.time.Instant;
import java.time.format.DateTimeParseException;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.TimeZone;
import java.text.DateFormat;
import java.text.SimpleDateFormat;

public class JsonDateReader
{
Expand Down Expand Up @@ -261,32 +261,13 @@ public static Date parseDropbox8601Date(char[] buffer, int offset, int length)
throw new java.text.ParseException("expecting date to be 20 or 24 characters, got " + length, 0);
}

// TODO: This needs to be looked at further.
// Does this need to handle arbitrary timezones?
String s = new String(b, i, length);
final DateFormat format;
if (length == 20) {
// Assume this is an ISO 8601 date with a trailing Z to indicate UTC:
// e.g. "2015-04-01T12:01:12Z",
format = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'");
} else {
// Assume this is an ISO 8601 date with a trailing Z to indicate UTC:
// plus milliseconds, e.g. "2012-04-23T18:25:43.511Z".
format = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'");
}
format.setTimeZone(TimeZone.getTimeZone("UTC"));

Date result;
try {
result = format.parse(s);
} catch (IllegalArgumentException ex) {
throw new java.text.ParseException("invalid characters in date" + s, 0);
return Date.from(Instant.parse(s));
} catch (DateTimeParseException | IllegalArgumentException ex) {
java.text.ParseException parseException = new java.text.ParseException("invalid date" + s, 0);
parseException.initCause(ex);
throw parseException;
}

if (result == null) {
throw new java.text.ParseException("invalid date" + s, 0);
}

return result;
}
}
12 changes: 10 additions & 2 deletions core/src/main/java/com/dropbox/core/util/IOUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -74,9 +74,17 @@ public static byte[] slurp(InputStream in, int byteLimit) throws IOException {

public static byte[] slurp(InputStream in, int byteLimit, byte[] slurpBuffer) throws IOException {
if (byteLimit < 0) throw new RuntimeException("'byteLimit' must be non-negative: " + byteLimit);
if (slurpBuffer.length == 0) throw new IllegalArgumentException("'slurpBuffer' must not be empty");

ByteArrayOutputStream baos = new ByteArrayOutputStream();
copyStreamToStream(in, baos, slurpBuffer);
int remaining = byteLimit;
ByteArrayOutputStream baos = new ByteArrayOutputStream(Math.min(byteLimit, slurpBuffer.length));
while (remaining > 0) {
int count = in.read(slurpBuffer, 0, Math.min(slurpBuffer.length, remaining));
if (count == -1) break;

baos.write(slurpBuffer, 0, count);
remaining -= count;
}
return baos.toByteArray();
}

Expand Down
27 changes: 13 additions & 14 deletions core/src/main/java/com/dropbox/core/util/StringUtil.java
Original file line number Diff line number Diff line change
@@ -1,21 +1,23 @@
package com.dropbox.core.util;

import static com.dropbox.core.util.LangUtil.mkAssert;

import java.io.UnsupportedEncodingException;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.charset.CharacterCodingException;
import java.nio.charset.Charset;
import java.nio.charset.CharsetDecoder;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.Base64;
import java.util.Collection;

public class StringUtil
{
public static final Charset UTF8 = Charset.forName("UTF-8");
public static final Charset UTF8 = StandardCharsets.UTF_8;

private static final char[] HexDigits = {'0','1','2','3','4','5','6','7','8','9','a','b','c','d','e','f',};
private static final Base64.Encoder Base64Encoder = Base64.getEncoder();
private static final Base64.Encoder UrlSafeBase64Encoder = Base64.getUrlEncoder();

public static char hexDigit(int i) { return HexDigits[i]; }

public static String utf8ToString(byte[] utf8Data)
Expand All @@ -36,14 +38,7 @@ public static String utf8ToString(byte[] utf8Data, int offset, int length)

public static byte[] stringToUtf8(String s)
{
try {
// Java 1.5 doesn't have the version of getBytes that takes a Charset object, so we
// just use this one and catch the exception.
return s.getBytes("UTF-8");
}
catch (UnsupportedEncodingException ex) {
throw mkAssert("UTF-8 should always be supported", ex);
}
return s.getBytes(UTF8);
}

/**
Expand Down Expand Up @@ -163,19 +158,23 @@ public static boolean secureStringEquals(String a, String b)

public static String base64Encode(byte[] data)
{
return base64EncodeGeneric(Base64Digits, data);
if (data == null) throw new IllegalArgumentException("'data' can't be null");
return Base64Encoder.encodeToString(data);
}

public static String urlSafeBase64Encode(byte[] data)
{
return base64EncodeGeneric(UrlSafeBase64Digits, data);
if (data == null) throw new IllegalArgumentException("'data' can't be null");
return UrlSafeBase64Encoder.encodeToString(data);
}

public static String base64EncodeGeneric(String digits, byte[] data)
{
if (data == null) throw new IllegalArgumentException("'data' can't be null");
if (digits == null) throw new IllegalArgumentException("'digits' can't be null");
if (digits.length() != 64) throw new IllegalArgumentException("'digits' must be 64 characters long: " + jq(digits));
if (Base64Digits.equals(digits)) return Base64Encoder.encodeToString(data);
if (UrlSafeBase64Digits.equals(digits)) return UrlSafeBase64Encoder.encodeToString(data);

int numGroupsOfThreeInputBytes = (data.length + 2) / 3;
int numOutputChars = numGroupsOfThreeInputBytes * 4;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ protected List<HttpRequestor.Header> getHeaders() {
}

List<HttpRequestor.Header> headers = new ArrayList<HttpRequestor.Header>();
String rangeValue = String.format("bytes=%d-", start.longValue());
String rangeValue = "bytes=" + start.longValue() + "-";
if (length != null) {
// Range header is inclusive (e.g. bytes=0-499 means first 500 bytes)
rangeValue += Long.toString(start.longValue() + length.longValue() - 1);
Expand Down
18 changes: 18 additions & 0 deletions core/src/test/java/com/dropbox/core/json/JsonDateReaderTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
import java.util.GregorianCalendar;
import java.util.Locale;

import static org.testng.Assert.assertEquals;

public class JsonDateReaderTest
{
@Test
Expand Down Expand Up @@ -43,6 +45,14 @@ public void parseDropboxDateTestMany()
if (count < 1000) throw new AssertionError("Loop didn't run enough: " + count);
}

@Test
public void parseDropbox8601DateTest()
throws java.text.ParseException
{
validateDropbox8601DateParser("2015-04-01T12:01:12Z", 1427889672000L);
validateDropbox8601DateParser("2012-04-23T18:25:43.511Z", 1335205543511L);
}

private static final ThreadLocal<SimpleDateFormat> dateFormatHolder = new ThreadLocal<SimpleDateFormat>() {
protected SimpleDateFormat initialValue()
{
Expand Down Expand Up @@ -93,4 +103,12 @@ private static void validateDropboxDateParser(String date)
throw new AssertionError(jq(date) + ": us=Date(" + preciseDateFormatHolder.get().format(ourResult) + "), lib=Date(" + preciseDateFormatHolder.get().format(libResult) + ")");
}
}

private static void validateDropbox8601DateParser(String date, long expectedMillis)
throws java.text.ParseException
{
char[] buf = date.toCharArray();
Date result = JsonDateReader.parseDropbox8601Date(buf, 0, buf.length);
assertEquals(result.getTime(), expectedMillis);
}
}
Loading