2626
2727import edu .umd .cs .findbugs .annotations .SuppressFBWarnings ;
2828import hudson .EnvVars ;
29+ import hudson .Extension ;
2930import hudson .FilePath ;
3031import hudson .Launcher ;
3132import hudson .Util ;
33+ import hudson .model .queue .QueueListener ;
3234import hudson .tasks .BuildWrapper ;
3335import hudson .util .VariableResolver ;
3436import java .io .File ;
3941import java .nio .charset .Charset ;
4042import java .nio .file .Files ;
4143import java .nio .file .Path ;
44+ import java .util .List ;
45+ import java .util .logging .Level ;
46+ import java .util .logging .Logger ;
4247import java .util .regex .Pattern ;
48+ import jenkins .model .Jenkins ;
4349import jenkins .util .SystemProperties ;
4450import org .apache .commons .fileupload2 .core .FileItem ;
4551import org .apache .commons .fileupload2 .core .FileItemFactory ;
4652import org .apache .commons .fileupload2 .core .FileItemHeaders ;
4753import org .apache .commons .fileupload2 .core .FileItemHeadersProvider ;
54+ import org .apache .commons .io .FileUtils ;
4855import org .apache .commons .io .FilenameUtils ;
4956import org .kohsuke .accmod .Restricted ;
5057import org .kohsuke .accmod .restrictions .NoExternalUse ;
5865 * @author Kohsuke Kawaguchi
5966 */
6067public class FileParameterValue extends ParameterValue {
68+
69+ private static final Logger LOGGER = Logger .getLogger (FileParameterValue .class .getName ());
70+
6171 private static final String FOLDER_NAME = "fileParameters" ;
6272 private static final Pattern PROHIBITED_DOUBLE_DOT = Pattern .compile (".*[\\ \\ /]\\ .\\ .[\\ \\ /].*" );
6373 private static final long serialVersionUID = -143427023159076073L ;
@@ -71,13 +81,16 @@ public class FileParameterValue extends ParameterValue {
7181 public static /* Script Console modifiable */ boolean ALLOW_FOLDER_TRAVERSAL_OUTSIDE_WORKSPACE =
7282 SystemProperties .getBoolean (FileParameterValue .class .getName () + ".allowFolderTraversalOutsideWorkspace" );
7383
74- private final transient FileItem file ;
84+ private transient FileItem file ;
7585
7686 /**
7787 * The name of the originally uploaded file.
7888 */
7989 private final String originalFileName ;
8090
91+
92+ private String tmpFileName ;
93+
8194 /**
8295 * Overrides the location in the build to place this file. Initially set to {@link #getName()}
8396 * The location could be directly the filename or also a hierarchical path.
@@ -106,6 +119,16 @@ public FileParameterValue(String name, File file, String originalFileName) {
106119
107120 protected FileParameterValue (String name , FileItem file , String originalFileName ) {
108121 super (name );
122+ try {
123+ File dir = new File (Jenkins .get ().getRootDir (), "fileParameterValueFiles" );
124+ Files .createDirectories (dir .toPath ());
125+ File tmp = Files .createTempFile (dir .toPath (), "jenkins" , ".tmp" ).toFile ();
126+ FileUtils .copyInputStreamToFile (file .getInputStream (), tmp );
127+ tmpFileName = tmp .getAbsolutePath ();
128+ } catch (IOException e ) {
129+ throw new RuntimeException (e );
130+ }
131+
109132 this .file = file ;
110133 this .originalFileName = originalFileName ;
111134 setLocation (name );
@@ -149,7 +172,17 @@ public String getOriginalFileName() {
149172 return originalFileName ;
150173 }
151174
175+ private void createFile () {
176+ if (file == null && tmpFileName != null ) {
177+ File tmp = new File (tmpFileName );
178+ if (tmp .exists ()) {
179+ file = new FileItemImpl2 (tmp );
180+ }
181+ }
182+ }
183+
152184 public FileItem getFile2 () {
185+ createFile ();
153186 return file ;
154187 }
155188
@@ -163,31 +196,44 @@ public org.apache.commons.fileupload.FileItem getFile() {
163196
164197 @ Override
165198 public BuildWrapper createBuildWrapper (AbstractBuild <?, ?> build ) {
199+ createFile ();
166200 return new BuildWrapper () {
167- @ SuppressFBWarnings (value = "NP_NULL_ON_SOME_PATH_FROM_RETURN_VALUE" , justification = "TODO needs triage" )
201+ @ SuppressFBWarnings (value = { "NP_NULL_ON_SOME_PATH_FROM_RETURN_VALUE" , "PATH_TRAVERSAL_IN" }, justification = "TODO needs triage, False positive, the path is a temporary file " )
168202 @ Override
169203 public Environment setUp (AbstractBuild build , Launcher launcher , BuildListener listener ) throws IOException , InterruptedException {
170- if (location != null && !location .isEmpty () && file .getName () != null && !file .getName ().isEmpty ()) {
171- listener .getLogger ().println ("Copying file to " + location );
172- FilePath ws = build .getWorkspace ();
173- if (ws == null ) {
174- throw new IllegalStateException ("The workspace should be created when setUp method is called" );
175- }
176- if (!ALLOW_FOLDER_TRAVERSAL_OUTSIDE_WORKSPACE && (PROHIBITED_DOUBLE_DOT .matcher (location ).matches () || !ws .isDescendant (location ))) {
177- listener .error ("Rejecting file path escaping base directory with relative path: " + location );
178- // force the build to fail
179- return null ;
180- }
181- FilePath locationFilePath = ws .child (location );
182- locationFilePath .getParent ().mkdirs ();
183-
184- // TODO Remove this workaround after FILEUPLOAD-293 is resolved.
185- if (locationFilePath .exists () && !locationFilePath .isDirectory ()) {
186- locationFilePath .delete ();
204+ if (location != null && !location .isBlank () && file != null && file .getName () != null && !file .getName ().isBlank ()) {
205+ try {
206+ listener .getLogger ().println ("Copying file to " + location );
207+ FilePath ws = build .getWorkspace ();
208+ if (ws == null ) {
209+ throw new IllegalStateException ("The workspace should be created when setUp method is called" );
210+ }
211+ if (!ALLOW_FOLDER_TRAVERSAL_OUTSIDE_WORKSPACE && (PROHIBITED_DOUBLE_DOT .matcher (location ).matches () || !ws .isDescendant (location ))) {
212+ listener .error ("Rejecting file path escaping base directory with relative path: " + location );
213+ // force the build to fail
214+ return null ;
215+ }
216+ FilePath locationFilePath = ws .child (location );
217+ locationFilePath .getParent ().mkdirs ();
218+
219+ // TODO Remove this workaround after FILEUPLOAD-293 is resolved.
220+ if (locationFilePath .exists () && !locationFilePath .isDirectory ()) {
221+ locationFilePath .delete ();
222+ }
223+ locationFilePath .copyFrom (file );
224+ locationFilePath .copyTo (new FilePath (getLocationUnderBuild (build )));
225+ } finally {
226+ if (tmpFileName != null ) {
227+ File tmp = new File (tmpFileName );
228+ try {
229+ Files .deleteIfExists (tmp .toPath ());
230+ } catch (IOException e ) {
231+ LOGGER .log (Level .WARNING , "Unable to delete temporary file {0} for parameter {1} of job {2}" ,
232+ new Object []{tmp .getAbsolutePath (), getName (), build .getParent ().getName ()});
233+ }
234+ }
235+ tmpFileName = null ;
187236 }
188-
189- locationFilePath .copyFrom (file );
190- locationFilePath .copyTo (new FilePath (getLocationUnderBuild (build )));
191237 }
192238 return new Environment () {};
193239 }
@@ -257,6 +303,36 @@ private File getFileParameterFolderUnderBuild(AbstractBuild<?, ?> build) {
257303 return new File (build .getRootDir (), FOLDER_NAME );
258304 }
259305
306+ @ Extension
307+ public static class CancelledQueueListener extends QueueListener {
308+
309+ @ Override
310+ public void onLeft (Queue .LeftItem li ) {
311+ if (li .isCancelled ()) {
312+ List <ParametersAction > actions = li .getActions (ParametersAction .class );
313+ actions .forEach (a -> {
314+ a .getAllParameters ().stream ()
315+ .filter (p -> p instanceof FileParameterValue )
316+ .map (p -> (FileParameterValue ) p )
317+ .forEach (this ::deleteTmpFile );
318+ });
319+ }
320+ }
321+
322+ @ SuppressFBWarnings (value = "PATH_TRAVERSAL_IN" , justification = "False positive, the path is a temporary file" )
323+ private void deleteTmpFile (FileParameterValue p ) {
324+ if (p .tmpFileName != null ) {
325+ File tmp = new File (p .tmpFileName );
326+ try {
327+ Files .deleteIfExists (tmp .toPath ());
328+ } catch (IOException e ) {
329+ LOGGER .log (Level .WARNING , "Unable to delete temporary file {0} for parameter {1}" ,
330+ new Object []{tmp .getAbsolutePath (), p .getName ()});
331+ }
332+ }
333+ }
334+ }
335+
260336 /**
261337 * Default implementation from {@link File}.
262338 *
0 commit comments