001    /*
002     * Copyright (c) 2009 The openGion Project.
003     *
004     * Licensed under the Apache License, Version 2.0 (the "License");
005     * you may not use this file except in compliance with the License.
006     * You may obtain a copy of the License at
007     *
008     *     http://www.apache.org/licenses/LICENSE-2.0
009     *
010     * Unless required by applicable law or agreed to in writing, software
011     * distributed under the License is distributed on an "AS IS" BASIS,
012     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
013     * either express or implied. See the License for the specific language
014     * governing permissions and limitations under the License.
015     */
016    package org.opengion.hayabusa.servlet;
017    
018    import org.opengion.hayabusa.common.HybsSystem;
019    import org.opengion.hayabusa.servlet.multipart.MultipartParser;
020    import org.opengion.hayabusa.servlet.multipart.Part;
021    import org.opengion.hayabusa.servlet.multipart.FilePart;
022    import org.opengion.hayabusa.servlet.multipart.ParamPart;
023    
024    import java.io.File;
025    import java.io.IOException;
026    import java.util.Map;
027    // import java.util.HashMap;
028    import java.util.TreeMap;
029    import java.util.List;
030    import java.util.ArrayList;
031    import java.util.Set;
032    import java.util.Random ;
033    import java.util.concurrent.atomic.AtomicInteger;       // 5.5.2.6 (2012/05/25) findbugs対å¿?
034    import javax.servlet.http.HttpServletRequest;
035    
036    /**
037     * ファイルをサーバã?にアãƒ??ロードするå?合に使用されるã?ルチパートå?ç�?‚µーブレãƒ?ƒˆですã?
038     *
039     * 通常のファイルアãƒ??ロード時の、form で使用する、enctype="multipart/form-data"
040     * を指定したå?合ã?、他ã?リクエスト情報もã?取り出すことが可能ですã?
041     *
042     * ファイルをアãƒ??ロード後に、指定ã?ファイル名に変更する機è?がありますã?
043     * file 登録ãƒ?‚¤アログでæŒ?®šしã�?name にã€?_NEW" とã�?�†名称を付けたリクエストå?ã‚?
044     * ファイルのアãƒ??ロードと同時に送信することで、この名前にファイルを付け替えますã?
045     * またã?アãƒ??ロード後ã?ファイル名ã?、name æŒ?®šã?名称で、取りå?せますã?
046     * クライアントから登録したオリジナルのファイル名ã?、name にã€?_ORG" とã�?�†名称
047     * で取り出すことが可能ですã?
048     *
049     * maxPostSize : æœ?¤§転送サイズ(Byte)を指定しますã? 0,またã?マイナスで無制限ですã?
050     * useBackup   : ファイルアãƒ??ロード時に、すでに同名のファイルが存在した場合にã€?
051     *               バックアãƒ??処ç�?renameTo)するかどã�?�‹[true/false]を指定しまã�?初期値:false)
052     *
053     * ファイルアãƒ??ロード時に、アãƒ??ロードå?に、同名ã?ファイルが存在した場合ã?、既存機è?は、そのまま
054     * 置き換えてã�?�¾したがã?簡易バージョンアãƒ??機è?として、useBackup="true" を指定すると、既存ã?ファイルã‚?
055     * リネã?ãƒ?�—て、バãƒ?‚¯アãƒ??ファイルを作æ?しますã?
056     * バックアãƒ??ファイルは、アãƒ??ロードフォルãƒ?‚’基準として、_backup/ファイルå�?拡張子_処ç�?™‚刻のlong値.拡張å­?になりますã?
057     * オリジナルのファイル名(拡張子付)を残したままã€?_処ç�?™‚刻のlong値" を追åŠ?�—、さらに、オリジナルの拡張子を追åŠ?�—ますã?
058     * バックアãƒ??ファイルの形式ã?æŒ?®šできませんã€?
059     *
060     * @og.group そã?他機è?
061     *
062     * @version  4.0
063     * @author       Kazuhiko Hasegawa
064     * @since    JDK5.0,
065     */
066    public final class MultipartRequest {
067    //      private static volatile int dumyNewFileCnt = 1 ;        // 3.8.1.4 (2006/03/17)
068            private static AtomicInteger dumyNewFileCnt = new AtomicInteger(1);             // 5.5.2.6 (2012/05/25) findbugs対å¿?
069    
070            private static String RANDOM_KEY = new Random().nextInt() + "_" ;               // 5.6.5.3 (2013/06/28) アãƒ??ロード時のãƒ?ƒŸーファイル名をもう少しã�?�‘ランãƒ?ƒ にするã€?
071    
072    //      private final Map<String,List<String>> parameters   = new HashMap<String,List<String>>();
073    //      private final Map<String,UploadedFile> files              = new HashMap<String,UploadedFile>();
074            private final Map<String,List<String>> parameters   = new TreeMap<String,List<String>>();               // 5.6.5.2 (2013/06/21) ソートしますã?
075            private final Map<String,UploadedFile> files              = new TreeMap<String,UploadedFile>();             // 5.6.5.2 (2013/06/21) ソートしますã?
076    
077            /**
078             * MultipartRequest オブジェクトを構築しますã?
079             *
080             * 引数として、ファイルアãƒ??ロード時の保存フォルãƒ??æœ?¤§サイズ、エンコードã?
081             * 新しいファイル名などを指定できますã?新しいファイル名ã?、アãƒ??ロードされる
082             * ファイルがä¸?�¤ã�?�‘の場合に使用できますã?è¤?•°のファイルを同時に変更したã�?
083             * 場合ã?、アãƒ??ロードルールにのっとりã?リクエストパラメータでæŒ?®šしてくださいã€?
084             *
085             * @og.rev 3.8.1.3A (2006/01/30) 新ファイル名にオリジナルファイル名ã?拡張子をセãƒ?ƒˆしまã�?
086             * @og.rev 4.0.0.0 (2007/11/28) メソãƒ?ƒ‰の戻りå?をチェãƒ?‚¯しますã?
087             * @og.rev 5.5.2.6 (2012/05/25) findbugs対応ã?staticフィールドへの書き込みに、AtomicInteger を利用しますã?
088             * @og.rev 5.6.5.3 (2013/06/28) useBackup引数追åŠ?
089             *
090             * @param       request HttpServletRequestオブジェクãƒ?
091             * @param       saveDirectory   ファイルアãƒ??ロードがあったå?合ã?保存フォルãƒ?��
092             * @param       maxPostSize     ファイルアãƒ??ロード時のæœ?¤§ファイルサイズ(Byte)0,またã?マイナスで無制é™?
093             * @param       encoding        ファイルのエンコーãƒ?
094             * @param       inputFilename   アãƒ??ロードされたファイルの新しい名前
095             * @param       useBackup               ファイルアãƒ??ロード時に、バãƒ?‚¯アãƒ??処ç�?�™るかどã�?�‹[true/false]を指å®?
096             * @throws IOException
097             */
098            public MultipartRequest(final HttpServletRequest request,
099                                                            final String saveDirectory,
100                                                            final int maxPostSize,
101                                                            final String  encoding,
102    //                                                      final String  inputFilename ) throws IOException {
103                                                            final String  inputFilename,
104                                                            final boolean useBackup ) throws IOException {                  // 5.6.5.3 (2013/06/28) 追åŠ?
105    
106                    if(request == null) {
107                            throw new IllegalArgumentException("request cannot be null");
108                    }
109                    if(saveDirectory == null) {
110                            throw new IllegalArgumentException("saveDirectory cannot be null");
111                    }
112                    // 5.5.2.6 (2012/05/25) 0,またã?マイナスで無制é™?
113    //              if(maxPostSize <= 0) {
114    //                      throw new IllegalArgumentException("maxPostSize must be positive");
115    //              }
116    
117                    // Save the dir
118                    File dir = new File(saveDirectory);
119    
120                    // Check saveDirectory is truly a directory
121                    if(!dir.isDirectory()) {
122                            throw new IllegalArgumentException("Not a directory: " + saveDirectory);
123                    }
124    
125                    // Check saveDirectory is writable
126                    if(!dir.canWrite()) {
127                            throw new IllegalArgumentException("Not writable: " + saveDirectory);
128                    }
129    
130                    // Parse the incoming multipart, storing files in the dir provided,
131                    // and populate the meta objects which describe what we found
132                    MultipartParser parser = new MultipartParser(request, maxPostSize);
133                    if(encoding != null) {
134                            parser.setEncoding(encoding);
135                    }
136    
137                    List<String> list = new ArrayList<String>();
138    
139                    Part part;
140                    while ((part = parser.readNextPart()) != null) {
141                            String name = part.getName();
142                            if( part.isParam() && part instanceof ParamPart ) {
143                                    ParamPart paramPart = (ParamPart)part;
144                                    String value = paramPart.getStringValue();
145                                    List<String> existingValues = parameters.get(name);
146                                    if(existingValues == null) {
147                                            existingValues = new ArrayList<String>();
148                                            parameters.put(name, existingValues);
149                                    }
150                                    existingValues.add(value);
151                            }
152                            else if( part.isFile() && part instanceof FilePart ) {
153                                    FilePart filePart = (FilePart)part;
154                                    String fileName = filePart.getFilename();
155                                    if(fileName != null) {
156                                            list.add( name );               // 3.5.6.5 (2004/08/09)
157                                            // 3.8.1.2 (2005/12/19) 仮ファイルでセーブするã?
158    //                                      String newName = String.valueOf( dumyNewFileCnt++ ) ;   // 3.8.1.4 (2006/03/17)
159    //                                      String newName = String.valueOf( dumyNewFileCnt.getAndIncrement() ) ;   // 5.5.2.6 (2012/05/25) findbugs対å¿?
160                                            String tempName = RANDOM_KEY + dumyNewFileCnt.getAndIncrement() ;               // 5.6.5.3 (2013/06/28) アãƒ??ロード時のãƒ?ƒŸーファイル名をもう少しã�?�‘ランãƒ?ƒ にするã€?
161    //                                      filePart.setFilename( newName );                        // 5.6.5.3 (2013/06/28) newName はã‚?‚„こしã�??で、変更
162                                            filePart.setFilename( tempName );
163                                            filePart.writeTo(dir);
164                                            files.put(name,
165                                                              new UploadedFile( dir.toString(),
166    //                                                                                              newName,                // 3.8.1.2 (2005/12/19)
167                                                                                                    tempName,               // 3.8.1.2 (2005/12/19)
168                                                                                                    fileName,
169                                                                                                    filePart.getContentType()));
170                                    }
171                                    else {
172                                            files.put(name, new UploadedFile(null, null, null, null));
173                                    }
174                            }
175                            else {
176                                    String errMsg = "Partオブジェクトが、ParamPartでã‚?ilePartでもありませんã€?
177                                                            + " class=[" + part.getClass() + "]";
178                                    throw new RuntimeException( errMsg );
179                            }
180                    }
181    
182                    // 3.5.6.5 (2004/08/09) 登録後にファイルをリネã?ãƒ?�—ますã?
183                    int size = list.size();
184                    for( int i=0; i<size; i++ ) {
185                            String name = list.get(i);
186                            File file = getFile( name );
187                            String newName = inputFilename;
188                            if( newName == null && name != null ) {
189                                    int adrs = name.lastIndexOf( HybsSystem.JOINT_STRING ); // カラãƒ?_行番号 の __ の位置
190                                    if( adrs < 0 ) {
191                                            newName = getParameter( name + "_NEW" );
192                                    }
193                                    else {
194                                            newName = getParameter( name.substring( 0,adrs ) + "_NEW" + name.substring( adrs ) );
195                                    }
196                            }
197    
198                            // 3.8.1.3 (2006/02/06) 新ファイル名に拡張子がなã�?�¨ã�?
199                            // 旧ファイル名から拡張子取得し新ファイル名にæ–?­—å?連çµ?
200                            if( newName != null && newName.length() > 0 ) {
201                                    // 新ファイル名から拡張子取å¾?
202                                    String newExt = getExtension( newName );
203                                    if( newExt == null || newExt.length() == 0 ) {
204                                            String oldExt = getExtension( getOriginalFileName( name ) );
205                                            newName = new StringBuilder().append( newName ).append( "." ).append( oldExt ).toString();
206                                    }
207                            }
208                            else {
209                                    newName = getOriginalFileName( name );
210                            }
211    
212                            // 3.8.1.2 (2005/12/19) 基本çš?�«はすべてのケースでファイル名変更が発生するã?
213                            if( file != null && newName != null && newName.length() > 0 ) {
214                                    File newFile = new File( dir,newName );
215    
216                                    // 5.6.5.3 (2013/06/28) useBackup ファイルアãƒ??ロード時に、バãƒ?‚¯アãƒ??処ç�?�™るかどã�?�‹[true/false]を指å®?
217    //                              if( newFile.exists() && !newFile.delete() ) {
218    //                                      String errMsg = "既存ã?ファイル[" + newName + "]が削除できませんでしたã€?;
219    //                                      throw new RuntimeException( errMsg );
220    //                              }
221                                    if( newFile.exists() ) {
222                                            if( useBackup ) {
223                                                    // newName にフォルãƒ?šŽ層を含ã‚??合に、そなえてã€?
224                                                    File parent = newFile.getParentFile();                  // バックアãƒ??すべきファイルのフォルãƒ?
225                                                    File backup = new File( parent , "_backup" );   // そã?直下にã€?_backup" フォルãƒ?‚’作æ?
226                                                    if( backup != null && !backup.exists() && !backup.mkdirs() ) {
227                                                            String errMsg = "バックアãƒ??処ç�?�§backupフォルãƒ??作æ?に失敗しましたã€?" + backup + "]";
228                                                            throw new RuntimeException( errMsg );
229                                                    }
230                                                    // バックアãƒ??ファイル名ã?、å?のファイルå�?拡張子含ã‚? ??"_" + 現在時刻のlong値 + "." + å…??ファイルの拡張å­?
231                                                    String bkupName = newFile.getName() + "_" + System.currentTimeMillis() + "."  + getExtension( newName ) ;
232                                                    File fromFile = new File( dir,newName );                // オリジナルの newFile をrename するとまずいので、同名ã?Fileオブジェクトを作æ?
233                                                    File bkupFile = new File( backup,bkupName );
234    
235                                                    if( !fromFile.renameTo( bkupFile ) ) {
236                                                            String errMsg = "バックアãƒ??処ç�?�§バックアãƒ??ファイルをリネã?ãƒ?�§きませんでしたã€?" + bkupFile + "]" ;
237                                                            throw new RuntimeException( errMsg );
238                                                    }
239                                            }
240                                            else if( !newFile.delete() ) {
241                                                    String errMsg = "既存ã?ファイル[" + newName + "]が削除できませんでしたã€?;
242                                                    throw new RuntimeException( errMsg );
243                                            }
244                                    }
245    
246    //                              file.renameTo( newFile );
247                                    if( !file.renameTo( newFile ) ) {
248                                            String errMsg = "æ‰?®šã?ファイルをリネã?ãƒ?�§きませんでしたã€?" + file + "]" ;
249                                            throw new RuntimeException( errMsg );
250                                    }
251                                    UploadedFile fup = files.get( name );
252                                    fup.setFilesystemName( newName );
253                            }
254                    }
255            }
256    
257            /**
258             * リクエストパラメータの名前配å?を取得しますã?
259             *
260             * @return      リクエストパラメータの名前配å?
261             */
262            public String[] getParameterNames() {
263                    Set<String> keyset = parameters.keySet();
264                    return keyset.toArray( new String[keyset.size()] );
265            }
266    
267            /**
268             * ファイルアãƒ??ロードされたファイル群のファイル名é?列を取得しますã?
269             *
270             * @return      アãƒ??ロードされたファイル名é?åˆ?
271             */
272            public String[] getFilenames() {
273                    Set<String> keyset = files.keySet();
274                    return keyset.toArray( new String[keyset.size()] );
275            }
276    
277            /**
278             * æŒ?®šã?名前のリクエストパラメータの値を取得しますã?
279             *
280             * @param       name    リクエストパラメータå�?
281             *
282             * @return      パラメータの値
283             */
284            public String getParameter( final String name ) {
285                    List<String> values = parameters.get(name);
286                    if( values == null || values.isEmpty() ) {
287                            return null;
288                    }
289                    return values.get(values.size() - 1);
290            }
291    
292            /**
293             * æŒ?®šã?名前のリクエストパラメータの値をé?列型式で取得しますã?
294             *
295             * @og.rev 5.3.2.0 (2011/02/01) 新規作æ?
296             *
297             * @param       name    リクエストパラメータå�?
298             *
299             * @return      パラメータの値配å?
300             */
301            public String[] getParameters( final String name ) {
302                    List<String> values = parameters.get(name);
303                    if( values == null || values.isEmpty() ) {
304                            return null;
305                    }
306    //              return values.toArray( new String[0] );
307                    return values.toArray( new String[values.size()] );
308            }
309    
310            /**
311             * æŒ?®šã?名前のリクエストパラメータの値をé?åˆ?int)型式で取得しますã?
312             *
313             * @og.rev 5.3.2.0 (2011/02/01) 新規作æ?
314             * @og.rev 5.3.6.0 (2011/06/01) 配å?値ã�?"の場合にNumberFormatExceptionが発生するバグを修正
315             *
316             * @param       name    リクエストパラメータå�?
317             *
318             * @return      パラメータの値配å?
319             */
320            public int[] getIntParameters( final String name ) {
321                    List<String> values = parameters.get(name);
322                    if( values == null || values.isEmpty() ) {
323                            return null;
324                    }
325    
326    //              int[] rtn = new int[values.size()];
327    //              for( int i=0; i<values.size(); i++ ) {
328    //                      rtn[i] = Integer.valueOf( values.get(i) );
329    //              }
330    
331                    // 5.3.6.0 (2011/06/01) ゼロストリング("")はint変換対象から予め除外すã‚?
332                    List<Integer> intVals = new ArrayList<Integer>();
333                    for( int i=0; i<values.size(); i++ ) {
334                            String str = values.get(i);
335                            if( str != null && str.length() > 0 ) {
336                                    intVals.add( Integer.parseInt( str ) );
337                            }
338                    }
339                    if( intVals.isEmpty() ) {
340                            return null;
341                    }
342    
343                    int[] rtn = new int[intVals.size()];
344                    for( int i=0; i<intVals.size(); i++ ) {
345                            rtn[i] = intVals.get(i).intValue();
346                    }
347    
348                    return rtn;
349            }
350    
351            /**
352             * æŒ?®šã?名前の UploadedFile オブジェクトかã‚?登録されるファイル名を取得しますã?
353             *
354             * 登録されるファイル名とは、新たに書き換えられたファイル名ã?ことですã?
355             *
356             * @og.rev 5.6.6.1 (2013/07/12) null 対ç­?
357             *
358             * @param       name    キーæƒ??
359             *
360             * @return      新たに書き換えられたファイルå�?
361             */
362            public String getFilesystemName( final String name ) {
363                    UploadedFile file = files.get(name);
364    //              return file.getFilesystemName();  // may be null
365                    return (file == null) ? null : file.getFilesystemName();  // may be null
366            }
367    
368            /**
369             * æŒ?®šã?名前の UploadedFile オブジェクトかã‚?アãƒ??ロードされたファイル名を取得しますã?
370             *
371             * アãƒ??ロードされたファイル名とは、オリジナルのファイル名ã?ことですã?
372             *
373             * @og.rev 5.6.6.1 (2013/07/12) null 対ç­?
374             *
375             * @param       name    キーæƒ??
376             *
377             * @return      オリジナルのファイルå�?
378             */
379            public String getOriginalFileName( final String name ) {
380                    UploadedFile file = files.get(name);
381    //              return file.getOriginalFileName();  // may be null
382                    return (file == null) ? null : file.getOriginalFileName();  // may be null
383            }
384    
385            /**
386             * æŒ?®šã?名前の UploadedFile オブジェクトかã‚?File オブジェクトを取得しますã?
387             *
388             * @og.rev 5.6.6.1 (2013/07/12) null 対ç­?
389             *
390             * @param       name    キーæƒ??
391             *
392             * @return      Fileオブジェクãƒ?
393             */
394            public File getFile( final String name ) {
395                    UploadedFile file = files.get(name);
396    //              return file.getFile();  // may be null
397                    return (file == null) ? null : file.getFile();  // may be null
398            }
399    
400            /**
401             * ファイル名かã‚?拡張子を取得しますã?
402             *
403             * @param       fileName        ファイルå�?
404             *
405             * @return      拡張å­?
406             */
407            private String getExtension( final String fileName ) {
408                    int index = fileName.lastIndexOf('.');
409                    if(index!=-1) {
410                            return fileName.substring(index + 1, fileName.length());
411                    }
412                    return "";
413            }
414    }
415    
416    /**
417     * ファイルをサーバã?にアãƒ??ロードするå?合に使用されるファイル管ç�??部クラスですã?
418     *
419     * @og.group そã?他機è?
420     *
421     * @version  4.0
422     * @author       Kazuhiko Hasegawa
423     * @since    JDK5.0,
424     */
425    final class UploadedFile {
426    
427            private String filename;
428            private final String dir;
429            private final String original;
430            private final String type;
431    
432            /**
433             * アãƒ??ロードファイルの管ç�?‚ªブジェクトを作æ?しますã?
434             *
435             * @param       dir     ファイルを保管するフォルãƒ?
436             * @param       filename        ファイルå�?置き換えå¾?
437             * @param       original        ファイルå�?オリジナル)
438             * @param       type    コンãƒ?ƒ³トタイãƒ?
439             */
440            UploadedFile( final String dir, final String filename, final String original, final String type) {
441                    this.dir = dir;
442                    this.filename = filename;
443                    this.original = original;
444                    this.type = type;
445            }
446    
447            /**
448             * コンãƒ?ƒ³トタイプを取得しますã?
449             *
450             * @return      コンãƒ?ƒ³トタイãƒ?
451             */
452            public String getContentType() {
453                    return type;
454            }
455    
456            /**
457             * ファイルå�?置き換えå¾?を取得しますã?
458             *
459             * @return      ファイルå�?置き換えå¾?
460             */
461            public String getFilesystemName() {
462                    return filename;
463            }
464    
465            /**
466             * ファイルå�?置き換えå¾?をセãƒ?ƒˆしますã?
467             *
468             * @param       name    ファイルå�?置き換えå¾?
469             */
470            public void setFilesystemName( final String name ) {
471                    filename = name;
472            }
473    
474            /**
475             * ファイルå�?オリジナル)を取得しますã?
476             *
477             * @return      ファイルå�?オリジナル)
478             */
479            public String getOriginalFileName() {
480                    return original;
481            }
482    
483            /**
484             * ファイルå�?置き換えå¾?の File オブジェクトを取得しますã?
485             *
486             * @return File File オブジェクãƒ?
487             */
488            public File getFile() {
489                    if(dir == null || filename == null) {
490                            return null;
491                    }
492                    else {
493                            return new File(dir + File.separator + filename);
494                    }
495            }
496    }