1616import java .io .IOException ;
1717import java .io .InputStream ;
1818import java .net .HttpURLConnection ;
19+ import java .net .MalformedURLException ;
20+ import java .net .URL ;
1921import java .nio .file .Path ;
2022import java .util .ArrayList ;
2123import java .util .Comparator ;
2224import java .util .List ;
25+ import java .util .stream .IntStream ;
26+ import java .util .stream .Stream ;
2327
2428public class WebDavClient {
2529
2630 private final WebDavCompatibleHttpClient httpClient ;
27- private final Path baseUrl ;
31+ private final URL baseUrl ;
2832 private final int HTTP_INSUFFICIENT_STORAGE = 507 ;
2933
3034 private enum PROPFIND_DEPTH {
@@ -98,7 +102,7 @@ private Response executePropfindRequest(final Path path, final PROPFIND_DEPTH pr
98102
99103 final var builder = new Request .Builder () //
100104 .method ("PROPFIND" , RequestBody .create (body , MediaType .parse (body ))) //
101- .url (absolutePathFrom (path )) //
105+ .url (absoluteURLFrom (path )) //
102106 .header ("DEPTH" , propfind_depth .value ) //
103107 .header ("Content-Type" , "text/xml" );
104108
@@ -138,8 +142,8 @@ private CloudItemList processDirList(final List<PropfindEntryData> entryData) {
138142 Path move (final Path from , final Path to , boolean replace ) throws CloudProviderException {
139143 final var builder = new Request .Builder () //
140144 .method ("MOVE" , null ) //
141- .url (absolutePathFrom (from )) //
142- .header ("Destination" , absolutePathFrom (to )) //
145+ .url (absoluteURLFrom (from )) //
146+ .header ("Destination" , absoluteURLFrom (to ). toExternalForm ( )) //
143147 .header ("Content-Type" , "text/xml" ) //
144148 .header ("Depth" , "infinity" );
145149
@@ -149,7 +153,7 @@ Path move(final Path from, final Path to, boolean replace) throws CloudProviderE
149153
150154 try (final var response = httpClient .execute (builder )) {
151155 if (response .code () == HttpURLConnection .HTTP_PRECON_FAILED ) {
152- throw new AlreadyExistsException (absolutePathFrom (to ));
156+ throw new AlreadyExistsException (absoluteURLFrom (to ). toExternalForm ( ));
153157 }
154158
155159 checkExecutionSucceeded (response .code ());
@@ -163,15 +167,15 @@ Path move(final Path from, final Path to, boolean replace) throws CloudProviderE
163167 InputStream read (final Path path , final ProgressListener progressListener ) throws CloudProviderException {
164168 final var getRequest = new Request .Builder () //
165169 .get () //
166- .url (absolutePathFrom (path ));
170+ .url (absoluteURLFrom (path ));
167171 return read (getRequest , progressListener );
168172 }
169173
170174 InputStream read (final Path path , final long offset , final long count , final ProgressListener progressListener ) throws CloudProviderException {
171175 final var getRequest = new Request .Builder () //
172176 .header ("Range" , String .format ("bytes=%d-%d" , offset , offset + count - 1 ))
173177 .get () //
174- .url (absolutePathFrom (path ));
178+ .url (absoluteURLFrom (path ));
175179 return read (getRequest , progressListener );
176180 }
177181
@@ -193,7 +197,7 @@ CloudItemMetadata write(final Path file, final boolean replace, final InputStrea
193197
194198 final var countingBody = new ProgressRequestWrapper (InputStreamRequestBody .from (data ), progressListener );
195199 final var requestBuilder = new Request .Builder ()
196- .url (absolutePathFrom (file ))
200+ .url (absoluteURLFrom (file ))
197201 .put (countingBody );
198202
199203 try (final var response = httpClient .execute (requestBuilder )) {
@@ -215,7 +219,7 @@ private boolean exists(Path path) throws CloudProviderException {
215219 Path createFolder (final Path path ) throws CloudProviderException {
216220 final var builder = new Request .Builder () //
217221 .method ("MKCOL" , null ) //
218- .url (absolutePathFrom (path ));
222+ .url (absoluteURLFrom (path ));
219223
220224 try (final var response = httpClient .execute (builder )) {
221225 checkExecutionSucceeded (response .code ());
@@ -228,7 +232,7 @@ Path createFolder(final Path path) throws CloudProviderException {
228232 void delete (final Path path ) throws CloudProviderException {
229233 final var builder = new Request .Builder () //
230234 .delete () //
231- .url (absolutePathFrom (path ));
235+ .url (absoluteURLFrom (path ));
232236
233237 try (final var response = httpClient .execute (builder )) {
234238 checkExecutionSucceeded (response .code ());
@@ -240,7 +244,7 @@ void delete(final Path path) throws CloudProviderException {
240244 void checkServerCompatibility () throws ServerNotWebdavCompatibleException {
241245 final var optionsRequest = new Request .Builder ()
242246 .method ("OPTIONS" , null )
243- .url (baseUrl . toString () );
247+ .url (baseUrl );
244248
245249 try (final var response = httpClient .execute (optionsRequest )) {
246250 checkExecutionSucceeded (response .code ());
@@ -281,9 +285,15 @@ private void checkExecutionSucceeded(final int status) throws CloudProviderExcep
281285 }
282286 }
283287
284- private String absolutePathFrom (final Path relativePath ) {
285- // TODO improve path appending
286- return baseUrl .toString () + relativePath .toString ();
288+ // visible for testing
289+ URL absoluteURLFrom (final Path relativePath ) {
290+ var basePath = Path .of (baseUrl .getPath ());
291+ var fullPath = IntStream .range (0 , relativePath .getNameCount ()).mapToObj (i -> relativePath .getName (i )).reduce (basePath , Path ::resolve );
292+ try {
293+ return new URL (baseUrl , fullPath .toString ());
294+ } catch (MalformedURLException e ) {
295+ throw new IllegalArgumentException ("The relative path contains invalid URL elements." );
296+ }
287297 }
288298
289299}
0 commit comments