55import java .io .InputStream ;
66import java .math .BigInteger ;
77import java .net .URI ;
8+ import java .net .URLDecoder ;
89import java .net .http .HttpClient ;
910import java .net .http .HttpClient .Redirect ;
1011import java .net .http .HttpRequest ;
1112import java .net .http .HttpResponse ;
1213import java .net .http .HttpResponse .BodyHandlers ;
14+ import java .nio .charset .StandardCharsets ;
1315import java .text .MessageFormat ;
1416import java .time .Duration ;
1517import java .time .LocalDateTime ;
2426import java .util .concurrent .atomic .AtomicLong ;
2527import java .util .stream .Collectors ;
2628
27- import jakarta .inject .Inject ;
28- import jakarta .inject .Named ;
29-
3029import org .apache .commons .io .IOUtils ;
3130import org .apache .commons .io .input .ProxyInputStream ;
3231import org .cloudfoundry .multiapps .common .SLException ;
3635import org .cloudfoundry .multiapps .controller .api .model .FileUrl ;
3736import org .cloudfoundry .multiapps .controller .api .model .ImmutableAsyncUploadResult ;
3837import org .cloudfoundry .multiapps .controller .api .model .ImmutableFileMetadata ;
38+ import org .cloudfoundry .multiapps .controller .api .model .UserCredentials ;
3939import org .cloudfoundry .multiapps .controller .client .util .CheckedSupplier ;
4040import org .cloudfoundry .multiapps .controller .client .util .ResilientOperationExecutor ;
4141import org .cloudfoundry .multiapps .controller .core .auditlogging .FilesApiServiceAuditLog ;
6969import org .springframework .web .multipart .MultipartFile ;
7070import org .springframework .web .multipart .MultipartHttpServletRequest ;
7171
72+ import jakarta .inject .Inject ;
73+ import jakarta .inject .Named ;
74+
7275@ Named
7376public class FilesApiServiceImpl implements FilesApiService {
7477
@@ -77,6 +80,11 @@ public class FilesApiServiceImpl implements FilesApiService {
7780 private static final int INPUT_STREAM_BUFFER_SIZE = 16 * 1024 ;
7881 private static final Duration HTTP_CONNECT_TIMEOUT = Duration .ofMinutes (10 );
7982 private static final String RETRY_AFTER_SECONDS = "30" ;
83+ private static final String USERNAME_PASSWORD_URL_FORMAT = "{0}:{1}" ;
84+ static {
85+ System .setProperty (Constants .RETRY_LIMIT_PROPERTY , "0" );
86+ }
87+
8088 private final CachedMap <String , AtomicLong > jobCounters = new CachedMap <>(Duration .ofHours (1 ));
8189 private final CachedMap <String , Future <?>> runningTasks = new CachedMap <>(Duration .ofHours (1 ));
8290 private final ResilientOperationExecutor resilientOperationExecutor = getResilientOperationExecutor ();
@@ -97,10 +105,6 @@ public class FilesApiServiceImpl implements FilesApiService {
97105 @ Inject
98106 private ExecutorService fileStorageThreadPool ;
99107
100- static {
101- System .setProperty (Constants .RETRY_LIMIT_PROPERTY , "0" );
102- }
103-
104108 @ Override
105109 public ResponseEntity <List <FileMetadata >> getFiles (String spaceGuid , String namespace ) {
106110 try {
@@ -156,7 +160,7 @@ public ResponseEntity<Void> startUploadFromUrl(String spaceGuid, String namespac
156160 deleteAsyncJobEntry (existingJob );
157161 }
158162 }
159- return triggerUploadFromUrl (spaceGuid , namespace , urlWithoutUserInfo , decodedUrl );
163+ return triggerUploadFromUrl (spaceGuid , namespace , urlWithoutUserInfo , decodedUrl , fileUrl . getUserCredentials () );
160164 }
161165
162166 private String getLocationHeader (String spaceGuid , String jobId ) {
@@ -289,12 +293,14 @@ private void deleteAsyncJobEntry(AsyncUploadJobEntry entry) {
289293 }
290294 }
291295
292- private ResponseEntity <Void > triggerUploadFromUrl (String spaceGuid , String namespace , String urlWithoutUserInfo , String decodedUrl ) {
296+ private ResponseEntity <Void > triggerUploadFromUrl (String spaceGuid , String namespace , String urlWithoutUserInfo , String decodedUrl ,
297+ UserCredentials userCredentials ) {
293298 var entry = createJobEntry (spaceGuid , namespace , urlWithoutUserInfo );
294299 LOGGER .debug (Messages .CREATING_ASYNC_UPLOAD_JOB , urlWithoutUserInfo , entry .getId ());
295300 uploadJobService .add (entry );
296301 try {
297- Future <?> runningTask = deployFromUrlExecutor .submit (() -> uploadFileFromUrl (entry , spaceGuid , namespace , decodedUrl ));
302+ Future <?> runningTask = deployFromUrlExecutor .submit (() -> uploadFileFromUrl (entry , spaceGuid , namespace , decodedUrl ,
303+ userCredentials ));
298304 runningTasks .put (entry .getId (), runningTask );
299305 } catch (RejectedExecutionException ignored ) {
300306 LOGGER .debug (Messages .ASYNC_UPLOAD_JOB_REJECTED , entry .getId ());
@@ -345,7 +351,8 @@ private AsyncUploadResult createErrorResult(String error, AsyncUploadResult.Clie
345351 .build ();
346352 }
347353
348- private void uploadFileFromUrl (AsyncUploadJobEntry jobEntry , String spaceGuid , String namespace , String fileUrl ) {
354+ private void uploadFileFromUrl (AsyncUploadJobEntry jobEntry , String spaceGuid , String namespace , String fileUrl ,
355+ UserCredentials userCredentials ) {
349356 var counter = new AtomicLong (0 );
350357 jobCounters .put (jobEntry .getId (), counter );
351358 LOGGER .info (Messages .STARTING_DOWNLOAD_OF_MTAR , jobEntry .getUrl ());
@@ -358,7 +365,8 @@ private void uploadFileFromUrl(AsyncUploadJobEntry jobEntry, String spaceGuid, S
358365 FileEntry fileEntry = resilientOperationExecutor .execute ((CheckedSupplier <FileEntry >) () -> doUploadFileFromUrl (spaceGuid ,
359366 namespace ,
360367 fileUrl ,
361- counter ));
368+ counter ,
369+ userCredentials ));
362370 LOGGER .trace (Messages .UPLOADED_MTAR_FROM_REMOTE_ENDPOINT_AND_JOB_ID , jobEntry .getUrl (), jobEntry .getId (),
363371 ChronoUnit .MILLIS .between (startTime , LocalDateTime .now ()));
364372 var descriptor = fileService .processFileContent (spaceGuid , fileEntry .getId (), this ::extractDeploymentDescriptor );
@@ -376,14 +384,16 @@ private void uploadFileFromUrl(AsyncUploadJobEntry jobEntry, String spaceGuid, S
376384 }
377385 }
378386
379- private FileEntry doUploadFileFromUrl (String spaceGuid , String namespace , String fileUrl , AtomicLong counter ) throws Exception {
387+ private FileEntry doUploadFileFromUrl (String spaceGuid , String namespace , String fileUrl , AtomicLong counter ,
388+ UserCredentials userCredentials )
389+ throws Exception {
380390 if (!UriUtil .isUrlSecure (fileUrl )) {
381391 throw new SLException (Messages .MTAR_ENDPOINT_NOT_SECURE );
382392 }
383393 UriUtil .validateUrl (fileUrl );
384394 HttpClient client = buildHttpClient (fileUrl );
385395
386- HttpResponse <InputStream > response = callRemoteEndpointWithRetry (client , fileUrl );
396+ HttpResponse <InputStream > response = callRemoteEndpointWithRetry (client , fileUrl , userCredentials );
387397 long fileSize = response .headers ()
388398 .firstValueAsLong (Constants .CONTENT_LENGTH )
389399 .orElseThrow (() -> new SLException (Messages .FILE_URL_RESPONSE_DID_NOT_RETURN_CONTENT_LENGTH ));
@@ -411,10 +421,11 @@ private FileEntry doUploadFileFromUrl(String spaceGuid, String namespace, String
411421 }
412422 }
413423
414- private HttpResponse <InputStream > callRemoteEndpointWithRetry (HttpClient client , String decodedUrl ) throws Exception {
424+ public HttpResponse <InputStream > callRemoteEndpointWithRetry (HttpClient client , String decodedUrl , UserCredentials userCredentials )
425+ throws Exception {
415426 return resilientOperationExecutor .execute ((CheckedSupplier <HttpResponse <InputStream >>) () -> {
416- var request = buildFetchFileRequest (decodedUrl );
417- LOGGER .debug (Messages .CALLING_REMOTE_MTAR_ENDPOINT , request . uri ( ));
427+ var request = buildFetchFileRequest (decodedUrl , userCredentials );
428+ LOGGER .debug (Messages .CALLING_REMOTE_MTAR_ENDPOINT , getMaskedUri ( urlDecodeUrl ( decodedUrl ) ));
418429 var response = client .send (request , BodyHandlers .ofInputStream ());
419430 if (response .statusCode () / 100 != 2 ) {
420431 String error = readErrorBodyFromResponse (response );
@@ -424,13 +435,26 @@ private HttpResponse<InputStream> callRemoteEndpointWithRetry(HttpClient client,
424435 UriUtil .stripUserInfo (decodedUrl ));
425436 throw new SLException (errorMessage );
426437 }
427- throw new SLException (MessageFormat .format (Messages .ERROR_FROM_REMOTE_MTAR_ENDPOINT , request . uri (), response . statusCode ( ),
428- error ));
438+ throw new SLException (MessageFormat .format (Messages .ERROR_FROM_REMOTE_MTAR_ENDPOINT , getMaskedUri ( urlDecodeUrl ( decodedUrl ) ),
439+ response . statusCode (), error ));
429440 }
430441 return response ;
431442 });
432443 }
433444
445+ private String getMaskedUri (String url ) {
446+ if (url .contains ("@" )) {
447+ return url .substring (url .lastIndexOf ("@" ))
448+ .replace ("@" , "..." );
449+ } else {
450+ return url ;
451+ }
452+ }
453+
454+ private String urlDecodeUrl (String url ) {
455+ return URLDecoder .decode (url , StandardCharsets .UTF_8 );
456+ }
457+
434458 private void resetCounterOnRetry (AtomicLong counter ) {
435459 counter .set (0 );
436460 }
@@ -443,14 +467,21 @@ protected HttpClient buildHttpClient(String decodedUrl) {
443467 .build ();
444468 }
445469
446- private HttpRequest buildFetchFileRequest (String decodedUrl ) {
470+ private HttpRequest buildFetchFileRequest (String decodedUrl , UserCredentials userCredentials ) {
447471 var builder = HttpRequest .newBuilder ()
448472 .GET ()
449473 .timeout (Duration .ofMinutes (15 ));
450474 var uri = URI .create (decodedUrl );
451475 var userInfo = uri .getUserInfo ();
452- if (userInfo != null ) {
453- builder .uri (URI .create (decodedUrl .replace (userInfo + "@" , "" )));
476+ if (userCredentials != null ) {
477+ builder .uri (uri );
478+ String userCredentialsUrlFormat = MessageFormat .format (USERNAME_PASSWORD_URL_FORMAT , userCredentials .getUsername (),
479+ userCredentials .getPassword ());
480+ String encodedAuth = Base64 .getEncoder ()
481+ .encodeToString (userCredentialsUrlFormat .getBytes ());
482+ builder .header (HttpHeaders .AUTHORIZATION , "Basic " + encodedAuth );
483+ } else if (userInfo != null ) {
484+ builder .uri (URI .create (decodedUrl .replace (uri .getRawUserInfo () + "@" , "" )));
454485 String encodedAuth = Base64 .getEncoder ()
455486 .encodeToString (userInfo .getBytes ());
456487 builder .header (HttpHeaders .AUTHORIZATION , "Basic " + encodedAuth );
0 commit comments