• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

tomdesair / tus-java-server / 27439656983

12 Jun 2026 07:58PM UTC coverage: 93.861% (-1.1%) from 95.005%
27439656983

Pull #79

github

web-flow
Merge f398d2179 into a7a0299f3
Pull Request #79: Implement File Deduplication by Hash

561 of 640 branches covered (87.66%)

Branch coverage included in aggregate %.

146 of 159 new or added lines in 8 files covered. (91.82%)

7 existing lines in 1 file now uncovered.

1610 of 1673 relevant lines covered (96.23%)

6.06 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

82.95
/src/main/java/me/desair/tus/server/util/Utils.java
1
package me.desair.tus.server.util;
2

3
import static java.nio.file.StandardOpenOption.CREATE;
4
import static java.nio.file.StandardOpenOption.READ;
5
import static java.nio.file.StandardOpenOption.TRUNCATE_EXISTING;
6
import static java.nio.file.StandardOpenOption.WRITE;
7

8
import jakarta.servlet.http.HttpServletRequest;
9
import java.io.BufferedOutputStream;
10
import java.io.IOException;
11
import java.io.ObjectInputStream;
12
import java.io.ObjectOutput;
13
import java.io.ObjectOutputStream;
14
import java.io.OutputStream;
15
import java.io.Serializable;
16
import java.nio.channels.Channels;
17
import java.nio.channels.FileChannel;
18
import java.nio.channels.FileLock;
19
import java.nio.file.Path;
20
import java.util.LinkedList;
21
import java.util.List;
22
import me.desair.tus.server.HttpHeader;
23
import me.desair.tus.server.checksum.ChecksumAlgorithm;
24
import org.apache.commons.lang3.StringUtils;
25
import org.slf4j.Logger;
26
import org.slf4j.LoggerFactory;
27

28
/** Utility class that contains various static helper methods */
29
public class Utils {
30

31
  private static final Logger log = LoggerFactory.getLogger(Utils.class);
8✔
32
  private static final int LOCK_FILE_RETRY_COUNT = 3;
33
  private static final long LOCK_FILE_SLEEP_TIME = 500;
34

35
  private Utils() {
36
    // This is a utility class that only holds static utility methods
37
  }
38

39
  public static String getHeader(HttpServletRequest request, String header) {
40
    return StringUtils.trimToEmpty(request.getHeader(header));
10✔
41
  }
42

43
  public static Long getLongHeader(HttpServletRequest request, String header) {
44
    try {
45
      return Long.valueOf(getHeader(request, header));
10✔
46
    } catch (NumberFormatException ex) {
2✔
47
      return null;
4✔
48
    }
49
  }
50

51
  /**
52
   * Build a comma-separated list based on the remote address of the request and the
53
   * X-Forwareded-For header. The list is constructed as "client, proxy1, proxy2".
54
   *
55
   * @return A comma-separated list of ip-addresses
56
   */
57
  public static String buildRemoteIpList(HttpServletRequest servletRequest) {
58
    String ipAddresses = servletRequest.getRemoteAddr();
6✔
59
    String xforwardedForHeader = getHeader(servletRequest, HttpHeader.X_FORWARDED_FOR);
8✔
60
    if (xforwardedForHeader.length() > 0) {
6✔
61
      ipAddresses = xforwardedForHeader + ", " + ipAddresses;
8✔
62
    }
63
    return ipAddresses;
4✔
64
  }
65

66
  public static List<String> parseConcatenationIDsFromHeader(String uploadConcatValue) {
67
    List<String> output = new LinkedList<>();
8✔
68

69
    String idString = StringUtils.substringAfter(uploadConcatValue, ";");
8✔
70
    for (String id : StringUtils.trimToEmpty(idString).split("\\s")) {
38✔
71
      output.add(id);
8✔
72
    }
73

74
    return output;
4✔
75
  }
76

77
  public static <T> T readSerializable(Path path, Class<T> clazz) throws IOException {
78
    T info = null;
4✔
79
    if (path != null) {
4!
80
      try (FileChannel channel = FileChannel.open(path, READ)) {
18✔
81
        // Lock will be released when the channel is closed
82
        if (lockFileShared(channel) != null) {
6!
83

84
          try (ObjectInputStream ois = new ObjectInputStream(Channels.newInputStream(channel))) {
12✔
85
            info = clazz.cast(ois.readObject());
10✔
86
          } catch (ClassNotFoundException e) {
×
87
            // This should not happen
88
            info = null;
×
89
          }
2✔
90
        } else {
91
          throw new IOException("Unable to lock file " + path);
×
92
        }
93
      }
94
    }
95
    return info;
4✔
96
  }
97

98
  public static void writeSerializable(Serializable object, Path path) throws IOException {
99
    if (path != null) {
4!
100
      try (FileChannel channel = FileChannel.open(path, WRITE, CREATE, TRUNCATE_EXISTING)) {
34✔
101
        // Lock will be released when the channel is closed
102
        if (lockFileExclusively(channel) != null) {
6!
103

104
          try (OutputStream buffer = new BufferedOutputStream(Channels.newOutputStream(channel));
14✔
105
              ObjectOutput output = new ObjectOutputStream(buffer)) {
10✔
106

107
            output.writeObject(object);
6✔
108
          }
109
        } else {
110
          throw new IOException("Unable to lock file " + path);
×
111
        }
112
      }
113
    }
114
  }
2✔
115

116
  public static FileLock lockFileExclusively(FileChannel channel) throws IOException {
117
    return lockFile(channel, false);
8✔
118
  }
119

120
  public static FileLock lockFileShared(FileChannel channel) throws IOException {
121
    return lockFile(channel, true);
8✔
122
  }
123

124
  /**
125
   * Sleep the specified number of milliseconds
126
   *
127
   * @param sleepTimeMillis The time to sleep in milliseconds
128
   */
129
  public static void sleep(long sleepTimeMillis) {
130
    try {
131
      Thread.sleep(sleepTimeMillis);
4✔
132
    } catch (InterruptedException e) {
×
133
      log.warn("Sleep was interrupted");
×
134
      // Restore interrupted state...
135
      Thread.currentThread().interrupt();
×
136
    }
2✔
137
  }
2✔
138

139
  private static FileLock lockFile(FileChannel channel, boolean shared) throws IOException {
140
    int i = 0;
4✔
141
    FileLock lock = null;
4✔
142
    do {
143
      if (i > 0) {
4✔
144
        sleep(LOCK_FILE_SLEEP_TIME);
2✔
145
      }
146

147
      lock = channel.tryLock(0L, Long.MAX_VALUE, shared);
12✔
148

149
      i++;
2✔
150
    } while (lock == null && i < LOCK_FILE_RETRY_COUNT);
7✔
151

152
    return lock;
4✔
153
  }
154

155
  /** Helper class to store parsed checksum header information. */
156
  public static class ChecksumInfo {
157
    private final ChecksumAlgorithm algorithm;
158
    private final String value;
159

160
    public ChecksumInfo(ChecksumAlgorithm algorithm, String value) {
4✔
161
      this.algorithm = algorithm;
6✔
162
      this.value = value;
6✔
163
    }
2✔
164

165
    public ChecksumAlgorithm getAlgorithm() {
166
      return algorithm;
6✔
167
    }
168

169
    public String getValue() {
170
      return value;
6✔
171
    }
172
  }
173

174
  /**
175
   * Parse the Upload-Checksum header from the HTTP request.
176
   *
177
   * @param request The HttpServletRequest
178
   * @return ChecksumInfo if header is present and valid, null otherwise
179
   */
180
  public static ChecksumInfo parseUploadChecksumHeader(HttpServletRequest request) {
181
    String uploadChecksumHeader = request.getHeader(HttpHeader.UPLOAD_CHECKSUM);
8✔
182
    if (StringUtils.isNotBlank(uploadChecksumHeader)) {
6!
183
      ChecksumAlgorithm algorithm = ChecksumAlgorithm.forUploadChecksumHeader(uploadChecksumHeader);
6✔
184
      String checksumValue =
4✔
185
          StringUtils.substringAfter(
4✔
186
              uploadChecksumHeader, ChecksumAlgorithm.CHECKSUM_VALUE_SEPARATOR);
187
      if (algorithm != null && StringUtils.isNotBlank(checksumValue)) {
10!
188
        return new ChecksumInfo(algorithm, checksumValue);
12✔
189
      }
190
    }
NEW
191
    return null;
×
192
  }
193
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc