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 }