Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

FileTransferringMessageHandler - Jsch Exception if RemoteDir Starts with / and Top-Level Dir Not Present [INT-2954] #6927

Closed
spring-operator opened this issue Mar 6, 2013 · 3 comments
Assignees
Milestone

Comments

@spring-operator
Copy link
Contributor

Brice Dutheil opened INT-2954 and commented

There is an exception thrown by JSCH if the remote-directory starts with a slash "/".

For example if the outbound adapter is configured in the following way, notice the "/target"

<int-sftp:outbound-channel-adapter id="sftpOutboundAdapter"
                                   session-factory="sftpSessionFactory"
                                   channel="outputChannel"
                                   remote-directory="/target"
                                   remote-filename-generator="fileNameGenerator"
                                   remote-file-separator="/"
                                   auto-create-directory="true"
    />

Then we'll have the following stacktrace:

org.junit.ComparisonFailure:  <Click to see difference>
org.junit.ComparisonFailure: expected:<exitCode=[COMPLETED;exitDescription=]> but was:<exitCode=[FAILED;exitDescription=org.springframework.integration.MessageDeliveryException: Failed to transfer file [target/workingDir/Profils-example.zip] from local working directory to remote FTP directory.
	at org.springframework.integration.file.remote.handler.FileTransferringMessageHandler.handleMessageInternal(FileTransferringMessageHandler.java:164)
	at org.springframework.integration.handler.AbstractMessageHandler.handleMessage(AbstractMessageHandler.java:73)
	at org.springframework.integration.dispatcher.UnicastingDispatcher.doDispatch(UnicastingDispatcher.java:115)
	at org.springframework.integration.dispatcher.UnicastingDispatcher.dispatch(UnicastingDispatcher.java:102)
	at org.springframework.integration.channel.AbstractSubscribableChannel.doSend(AbstractSubscribableChannel.java:77)
	at org.springframework.integration.channel.AbstractMessageChannel.send(AbstractMessageChannel.java:157)
	at com.myclient.batch.sftp.SftpTasklet.sendOverSftp(SftpTasklet.java:53)
	at com.myclient.batch.sftp.SftpTasklet.execute(SftpTasklet.java:41)
	at org.springframework.batch.core.step.tasklet.TaskletStep$ChunkTransactionCallback.doInTransaction(TaskletStep.java:386)
	at org.springframework.transaction.support.TransactionTemplate.execute(TransactionTemplate.java:130)
	at org.springframework.batch.core.step.tasklet.TaskletStep$2.doInChunkContext(TaskletStep.java:264)
	at org.springframework.batch.core.scope.context.StepContextRepeatCallback.doInIteration(StepContextRepeatCallback.java:76)
	at org.springframework.batch.repeat.support.RepeatTemplate.getNextResult(RepeatTemplate.java:367)
	at org.springframework.batch.repeat.support.RepeatTemplate.executeInternal(RepeatTemplate.java:214)
	at org.springframework.batch.repeat.support.RepeatTemplate.iterate(RepeatTemplate.java:143)
	at org.springframework.batch.core.step.tasklet.TaskletStep.doExecute(TaskletStep.java:250)
	at org.springframework.batch.core.step.AbstractStep.execute(AbstractStep.java:195)
	at org.springframework.batch.core.job.SimpleStepHandler.handleStep(SimpleStepHandler.java:135)
	at org.springframework.batch.core.job.AbstractJob.handleStep(AbstractJob.java:372)
	at org.springframework.batch.core.job.SimpleJob.doExecute(SimpleJob.java:121)
	at org.springframework.batch.core.job.AbstractJob.execute(AbstractJob.java:293)
	at org.springframework.batch.core.launch.support.SimpleJobLauncher$1.run(SimpleJobLauncher.java:120)
	at org.springframework.core.task.SyncTaskExecutor.execute(SyncTaskExecutor.java:48)
	at org.springframework.batch.core.launch.support.SimpleJobLauncher.run(SimpleJobLauncher.java:114)
	at org.springframework.batch.test.StepRunner.launchJob(StepRunner.java:169)
	at org.springframework.batch.test.StepRunner.launchStep(StepRunner.java:158)
	at org.springframework.batch.test.JobLauncherTestUtils.launchStep(JobLauncherTestUtils.java:239)
	at org.springframework.batch.test.JobLauncherTestUtils.launchStep(JobLauncherTestUtils.java:187)
	at com.myclient.batch.UploadCrmgpFilesIntegrationTest.should_upload_files_on_the_sftp_server(UploadCrmgpFilesIntegrationTest.java:48)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:45)
	at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:15)
	at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:42)
	at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:20)
	at org.springframework.test.context.junit4.statements.RunBeforeTestMethodCallbacks.evaluate(RunBeforeTestMethodCallbacks.java:74)
	at org.springframework.test.context.junit4.statements.RunAfterTestMethodCallbacks.evaluate(RunAfterTestMethodCallbacks.java:83)
	at com.myclient.core.test.rule.SftpRule$StartCloseSftpStatement.evaluate(SftpRule.java:71)
	at org.junit.rules.RunRules.evaluate(RunRules.java:18)
	at org.springframework.test.context.junit4.statements.SpringRepeat.evaluate(SpringRepeat.java:72)
	at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:231)
	at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:47)
	at org.junit.runners.ParentRunner$3.run(ParentRunner.java:231)
	at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:60)
	at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:229)
	at org.junit.runners.ParentRunner.access$000(ParentRunner.java:50)
	at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:222)
	at org.springframework.test.context.junit4.statements.RunBeforeTestClassCallbacks.evaluate(RunBeforeTestClassCallbacks.java:61)
	at org.springframework.test.context.junit4.statements.RunAfterTestClassCallbacks.evaluate(RunAfterTestClassCallbacks.java:71)
	at org.junit.runners.ParentRunner.run(ParentRunner.java:300)
	at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.run(SpringJUnit4ClassRunner.java:174)
	at org.junit.runner.JUnitCore.run(JUnitCore.java:157)
	at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:76)
	at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:195)
	at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:63)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at com.intellij.rt.execution.application.AppMain.main(AppMain.java:120)
Caused by: org.springframework.core.NestedIOException: failed to create remote directory ''.; nested exception is 4: 
	at org.springframework.integration.sftp.session.SftpSession.mkdir(SftpSession.java:177)
	at org.springframework.integration.file.remote.session.CachingSessionFactory$CachedSession.mkdir(CachingSessionFactory.java:141)
	at org.springframework.integration.file.remote.handler.FileTransferringMessageHandler.makeDirectories(FileTransferringMessageHandler.java:291)
	at org.springframework.integration.file.remote.handler.FileTransferringMessageHandler.sendFileToRemoteDirectory(FileTransferringMessageHandler.java:234)
	at org.springframework.integration.file.remote.handler.FileTransferringMessageHandler.handleMessageInternal(FileTransferringMessageHandler.java:157)
	... 61 more
Caused by: 4: 
	at com.jcraft.jsch.ChannelSftp.mkdir(ChannelSftp.java:1902)
	at org.springframework.integration.sftp.session.SftpSession.mkdir(SftpSession.java:174)
	... 65 more
Caused by: java.lang.StringIndexOutOfBoundsException: String index out of range: 0
	at java.lang.String.charAt(String.java:658)
	at com.jcraft.jsch.ChannelSftp.remoteAbsolutePath(ChannelSftp.java:2561)
	at com.jcraft.jsch.ChannelSftp.mkdir(ChannelSftp.java:1880)
	... 66 more
]>
	at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
	at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:57)
	at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
	at com.myclient.batch.UploadCrmgpFilesIntegrationTest.should_upload_files_on_the_sftp_server(UploadCrmgpFilesIntegrationTest.java:51)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:45)
	at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:15)
	at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:42)
	at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:20)
	at org.springframework.test.context.junit4.statements.RunBeforeTestMethodCallbacks.evaluate(RunBeforeTestMethodCallbacks.java:74)
	at org.springframework.test.context.junit4.statements.RunAfterTestMethodCallbacks.evaluate(RunAfterTestMethodCallbacks.java:83)
	at com.myclient.core.test.rule.SftpRule$StartCloseSftpStatement.evaluate(SftpRule.java:71)
	at org.junit.rules.RunRules.evaluate(RunRules.java:18)
	at org.springframework.test.context.junit4.statements.SpringRepeat.evaluate(SpringRepeat.java:72)
	at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:231)
	at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:47)
	at org.junit.runners.ParentRunner$3.run(ParentRunner.java:231)
	at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:60)
	at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:229)
	at org.junit.runners.ParentRunner.access$000(ParentRunner.java:50)
	at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:222)
	at org.springframework.test.context.junit4.statements.RunBeforeTestClassCallbacks.evaluate(RunBeforeTestClassCallbacks.java:61)
	at org.springframework.test.context.junit4.statements.RunAfterTestClassCallbacks.evaluate(RunAfterTestClassCallbacks.java:71)
	at org.junit.runners.ParentRunner.run(ParentRunner.java:300)
	at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.run(SpringJUnit4ClassRunner.java:174)
	at org.junit.runner.JUnitCore.run(JUnitCore.java:157)
	at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:76)
	at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:195)
	at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:63)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
	at com.intellij.rt.execution.application.AppMain.main(AppMain.java:120)

I've tracked the "culprit" code to this method org.springframework.integration.file.remote.handler.FileTransferringMessageHandler#makeDirectories. For path "/target", the file will be appended with the separator "target/", that will processed then be processed, splitted in pathsToCreate, this list will contain two string "" and "target".

While this could be an improvement in JSCH, I think Spring should handle this case, as the XSD documentation says it is possible to have directories starting with a slash "/". Plus the fix is fairly easy in removing empty string from the list.


Affects: 2.2. GA

2 votes, 4 watchers

@spring-operator
Copy link
Contributor Author

Gary Russell commented

FYI, this is only an issue if the top level directory does not exist on the server. The path can start with '/' if the top directory exists.

Also, most admins typically chroot sftp users so they can't browse the entire file system. In this case, the chrooted directory must be owned by root and not writable by the user so the top-level directory will always exist (so the user has a writeable directory).

When you are NOT chrooted, you typically wouldn't want to (and likely won't have permission to) create a top level directory in the root (/) of the file system.

So remote-directory="/home/ftptest/foobar" works fine (because /home/ftptest already exists) and remote-directory="foobar" works too because foobar is relative to /home/ftptest. Whereas remote-directory="/foobar" would fail due to permissions (even if we apply your suggested fix to avoid the exception).

I am not saying this isn't a bug, I just want to explain why this has not been discovered/reported sooner because, in practice, the top level directory usually already exists.

@spring-operator
Copy link
Contributor Author

Gary Russell commented

Pull Request Submitted: #773

Needs cherry-pick to 2.2.x.

@spring-operator
Copy link
Contributor Author

Brice Dutheil commented

Cool thx Gary for explaining the real issue and fixing it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants