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 java.io.FileInputStream;
019    import java.io.IOException;
020    
021    import javax.mail.internet.MimeUtility;
022    import javax.servlet.ServletException;
023    import javax.servlet.ServletOutputStream;
024    import javax.servlet.http.HttpServlet;
025    import javax.servlet.http.HttpServletRequest;
026    import javax.servlet.http.HttpServletResponse;
027    
028    import org.opengion.fukurou.security.HybsCryptography;
029    import org.opengion.fukurou.util.Closer;
030    import org.opengion.fukurou.util.KanaFilter;
031    import org.opengion.fukurou.util.StringUtil;
032    import org.opengion.hayabusa.common.HybsSystem;
033    import org.opengion.hayabusa.common.HybsSystemException;
034    
035    /**
036     * サーバã?管ç�?ƒ•ァイルをダウンロードするå?合に使用する、サーブレãƒ?ƒˆですã?
037     *
038     * 引数(URL)にæŒ?®šã?ファイルをサーバã?からクライアントにãƒ?‚¦ンロードさせますã?
039     * file には、サーバã?ファイルの物ç�?‚¢ドレスを指定しますã?相対パスを使用する場合ã?ã€?
040     * コンãƒ?‚­ストルーãƒ?通常、Tomcatでは、G:\webapps\dbdef2\ など)からのパスと判断しますã?
041     * name には、クライアントに送信するファイル名をæŒ?®šしますã?ファイル名をæŒ?®šしなã�??合ã?ã€?
042     * サーバã?の物ç�?ƒ•ァイルのファイル名が代わりに使用されますã?
043     * 日本語ファイル名ã?、すべて UTF-8化して処ç�?�—ますã?æŒ?®šするファイルに日本語が含まれる
044     * 場合ã?、URLエンコードを行ってくださいã€?
045     * 基本çš?�«はContent-disposition属æ?として"attachment"が指定されますã?
046     * ä½?�—、引数に inline=true を指定することで、Content-disposition属æ?に"inline"が指定されますã?
047     * またã?シスãƒ?ƒ リソースのUSE_FILEDOWNLOAD_CHECKKEYをtrueにæŒ?®šすることで、簡易的なチェãƒ?‚¯ã‚?
048     * 行うことができますã?
049     * 具体的には、これを有効にすると、file属æ?の値から計算されるMD5チェãƒ?‚¯サãƒ?�¨ã€?key"とã�?�†
050     * パラメーターにæŒ?®šされた値がä¸??した場合ã?みãƒ?‚¦ンロードが許可され、keyが指定されてã�?�ªã�??
051     * またã?値が異なるå?合ã?ãƒ?‚¦ンロードエラーとなりますã?
052     *
053     * ä¸?ˆ¬çš?�ªサーブレãƒ?ƒˆと同様に、デプロイメントã?ãƒ?‚£スクリプタ WEB-INF/web.xml にã€?
054     * servlet 要ç´?�¨ そã?マッピング(servlet-mapping)を定義するå¿?¦�がありますã?
055     *
056     *     <servlet>
057     *         <servlet-name>fileDownload</servlet-name>
058     *         <servlet-class>org.opengion.hayabusa.servlet.FileDownload</servlet-class>
059     *     </servlet>
060     *
061     *     <servlet-mapping>
062     *         <servlet-name>fileDownload</servlet-name>
063     *         <url-pattern>/jsp/fileDownload</url-pattern>
064     *     </servlet-mapping>
065     *
066     * ä¸?ˆ¬には、http://:ポã?ãƒ?シスãƒ?ƒ ID/jsp/fileDownload?file=サーバã?物ç�?ƒ•ァイル&name=ファイルå�?
067     * 形式ã?URL でアクセスしますã?
068     *
069     * @og.rev 3.8.1.1 (2005/11/21) 新規追åŠ?
070     * @og.group そã?他機è?
071     *
072     * @version  0.9.0  2000/10/17
073     * @author   Kazuhiko Hasegawa
074     * @since    JDK1.1,
075     */
076    public class FileDownload extends HttpServlet {
077            private static final long serialVersionUID = 539020110901L ;
078    
079            // 拡張子contentType対応テーブル
080            private static final String CONTENT_TYPE_TABLE[][] = {
081                    {"jpg", "image/pjpeg"   },
082                    {"gif", "image/gif"             },
083                    {"txt", "text/plain"    },
084    //              {"xls", "application/vnd.ms-excel"}
085                    // OpenDocument追åŠ?
086                    {"xls", "application/vnd.ms-excel"},
087                    {"odp", "application/vnd.oasis.opendocument.presentation"}, // 4.3.5.5 (2008/03/08)
088                    {"ods", "application/vnd.oasis.opendocument.spreadsheet"}, // 4.3.5.5 (2008/03/08)
089                    {"odt", "application/vnd.oasis.opendocument.text"} // 4.3.5.5 (2008/03/08)
090            };
091            private static final int EXTENTION       = 0;
092            private static final int CONTENT_TYPE= 1;
093    
094            /**
095             * GET メソãƒ?ƒ‰が呼ばれたときに実行しますã?
096             *
097             * 処ç�??、doPost へ振りなおしてã�?�¾すã?
098             *
099             * @param       request HttpServletRequestオブジェクãƒ?
100             * @param       response        HttpServletResponseオブジェクãƒ?
101             *
102             * @og.rev 3.8.1.2 (2005/12/19) 半角カãƒ?全角カナ変換機è?の追åŠ?
103             *
104             * @throws ServletException サーブレãƒ?ƒˆ関係ã?エラーが発生したå?合ã?throw されますã?
105             * @throws IOException 入出力エラーが発生したとã�?
106             */
107            @Override
108            public void doGet( final HttpServletRequest request, final HttpServletResponse response )
109                                                            throws ServletException, IOException {
110                    doPost( request,response );
111            }
112    
113            /**
114             * POST メソãƒ?ƒ‰が呼ばれたときに実行しますã?
115             *
116             * file 引数の サーバã?物ç�?ƒ•ァイルをã?クライアントにストリーãƒ?Œ–して返しますã?
117             * name 引数があれã?、その名前のファイル名でクライアントがファイルセーブできるように
118             * しますã?name 引数がなければ、そのまま物ç�?ƒ•ァイル名が使用されますã?
119             * サーバã?物ç�?ƒ•ァイル名が、相対パスの場合ã?コンãƒ?‚­ストルートに対する相対パスになりますã?
120             * (例:G:\webapps\dbdef2\ など)
121             *
122             * @og.rev 5.3.2.0 (2011/02/01) 日本語ファイル名が正しく処ç�?�§きなã�?ƒ�グを修正
123             * @og.rev 5.3.4.0 (2011/04/01) IEでファイルが正しくãƒ?‚¦ンロードできなã�?ƒ�グを修正
124             * @og.rev 5.3.5.0 (2011/05/01) ファイルãƒ?‚¦ンロードチェãƒ?‚¯キー対å¿?
125             * @og.rev 5.3.6.0 (2011/06/01) ファイルãƒ?‚¦ンロードã?attachmentに変更(ãƒ?‚¦ンロードダイアログをå?ã�?
126             * @og.rev 5.3.8.0 (2011/08/01) ファイル名指定でIEの場合ã?URLエンコードすると途中でåˆ?‚Œるたã‚?IE7のバグ)、Shift_JIS(WIndows-31J)で直接æŒ?®šするã?
127             * @og.rev 5.3.9.0 (2011/09/01) 引数にinline=trueを指定することで、インライン表示がå?来るよã�?�«対å¿?
128             * @og.rev 5.7.1.2 (2013/12/20) 日本語ファイルのIE11対応ï¼?A変更??msg â‡?errMsg 変更
129             *
130             * @param       request HttpServletRequestオブジェクãƒ?
131             * @param       response        HttpServletResponseオブジェクãƒ?
132             *
133             * @throws ServletException サーブレãƒ?ƒˆ関係ã?エラーが発生したå?合ã?throw されますã?
134             * @throws IOException 入出力エラーが発生したとã�?
135             */
136            @Override
137            public void doPost( final HttpServletRequest request, final HttpServletResponse response )
138                                                            throws ServletException, IOException {
139    
140                    // 3.8.1.2 (2005/12/19) 半角カãƒ?全角カナ変換機è?の追åŠ?
141                    boolean hanzenFlag = HybsSystem.sysBool( "USE_FILEDOWNLOAD_HAN_ZEN" );
142    
143                    String reqFilename = request.getParameter( "file" );
144                    String newFilename = request.getParameter( "name" );
145    
146                    // 5.3.9.0 (2011/09/01) 引数にinline=trueを指定することで、インライン表示がå?来るよã�?�«対å¿?
147                    boolean inline = StringUtil.nval( request.getParameter( "inline" ), false );
148                    String dipositionType = inline ? "inline" : "attachment";
149    
150                    // クライアントå?のæ–?­—エンコーãƒ?‚£ングをUTF-8に変換
151                    reqFilename = new String( reqFilename.getBytes("ISO-8859-1"), "UTF-8" );
152    
153                    // 5.3.5.0 (2011/05/01) ファイルãƒ?‚¦ンロードチェãƒ?‚¯キー対å¿?
154                    boolean useCheck = HybsSystem.sysBool( "USE_FILEDOWNLOAD_CHECKKEY" );
155                    if( useCheck ) {
156                            String checkKey = request.getParameter( "key" );
157                            if( checkKey == null || !checkKey.equals( HybsCryptography.getMD5( reqFilename ) ) ) {
158    //                              String msg = "アクセスが拒否されましたã€?URLチェãƒ?‚¯)";
159    //                              throw new HybsSystemException( msg );
160                                    String errMsg = "アクセスが拒否されましたã€?URLチェãƒ?‚¯)";
161                                    throw new HybsSystemException( errMsg );        // 5.7.1.2 (2013/12/20) msg â‡?errMsg 変更
162                            }
163                    }
164    
165                    // 相対パスを絶対パスに変換。ファイルセパレータも正規化されてã�?�¾すã?
166                    reqFilename = HybsSystem.url2dir( reqFilename );
167    
168                    // 拡張子からcontentTypeを獲å¾?
169                    String contentType = getContentType( reqFilename );
170                    // contentTypeをå?åŠ?
171                    response.setContentType( contentType );
172    
173                    // 表示ファイル名ã?æŒ?®?
174                    if( newFilename == null || newFilename.length() == 0 ) {
175                            newFilename = getFileName( reqFilename );
176                    }
177                    else {
178                            newFilename = new String( newFilename.getBytes("ISO-8859-1"), "UTF-8" );
179                    }
180    
181                    // 3.8.1.2 (2005/12/19) 半角カナを全角カナに置き換えますã?ファイルãƒ?‚¤アログのæ–?­—化け仮対å¿?
182                    if( hanzenFlag ) {
183                            newFilename = KanaFilter.han2zen( newFilename );
184                    }
185    
186                    // 5.3.2.0 (2011/02/01) 日本語ファイル名が正しく処ç�?�§きなã�?ƒ�グを修正
187    ////            newFilename = StringUtil.urlEncode( newFilename );
188    //              if( request.getHeader( "User-Agent" ).indexOf( "MSIE" ) == -1 ) {
189    //                      newFilename = MimeUtility.encodeWord( newFilename, "UTF-8", "B" );
190    //              }
191    //              else {
192    //                      // 5.3.8.0 (2011/08/01) IEの場合ã?URLエンコードすると途中でåˆ?‚Œるたã‚?IE7のバグ)、Shift_JIS(WIndows-31J)で直接æŒ?®šするã?
193    ////                    newFilename = StringUtil.urlEncode( newFilename );
194    //                      newFilename = new String( newFilename.getBytes("Windows-31J"), "ISO-8859-1" );
195    //              }
196                    
197                    // 5.7.1.2 (2013/12/20) 条件を反転させた上でIE11対応を行う
198                    if( request.getHeader( "User-Agent" ).indexOf( "MSIE" ) >= 0 || request.getHeader( "User-Agent" ).indexOf( "Trident" ) >= 0 ) {
199                            newFilename = new String( newFilename.getBytes("Windows-31J"), "ISO-8859-1" );
200                    }
201                    else {
202                            newFilename = MimeUtility.encodeWord( newFilename, "UTF-8", "B" );
203                    }
204    
205                    // ファイル名ã?送信( attachment部åˆ?‚’inlineに変更すればインライン表示 )
206                    // 5.3.9.0 (2011/09/01) 引数にinline=trueを指定することで、インライン表示がå?来るよã�?�«対å¿?
207    //              response.setHeader( "Content-disposition", "inline; filename=\"" + newFilename + "\"" );
208    //              response.setHeader( "Content-disposition", "attachment; filename=\"" + newFilename + "\"" );
209                    response.setHeader( "Content-disposition", dipositionType + "; filename=\"" + newFilename + "\"" );
210    
211                    // 5.3.4.0 (2011/04/01) IEでファイルが正しくãƒ?‚¦ンロードできなã�?ƒ�グを修正
212                    response.setHeader( "Cache-Control", "public" );
213    
214                    // ファイルå†?®¹の出åŠ?
215                    FileInputStream     fin = null;
216                    ServletOutputStream out = null;
217                    try {
218                            fin = new FileInputStream( reqFilename );
219                            out = response.getOutputStream();
220    
221                            // ファイル読み込み用バッファ
222                            byte buffer[]  = new byte[4096];
223                            int size;
224                            while((size = fin.read(buffer))!=-1) {
225                                    out.write(buffer,0, size);
226                                    out.flush();
227                            }
228                    }
229                    finally {
230                            Closer.ioClose( fin );          // 4.0.0 (2006/01/31) close 処ç�?™‚の IOException を無è¦?
231                            Closer.ioClose( out );          // 4.0.0 (2006/01/31) close 処ç�?™‚の IOException を無è¦?
232                    }
233            }
234    
235            /**
236             * アドレス名から拡張子を取り出しますã?
237             *
238             * アドレス名ã?後ろからã€?." 以降を拡張子としてåˆ?‚Š取りますã?
239             * 拡張子が存在しなã�??å�?æŒ?®šã?ファイル名に "." が含まれなã�??å�?は
240             * ゼロæ–?­—å?("")を返しますã?
241             *
242             * @param       fileAddress     アドレスå�?
243             *
244             * @return      拡張å­?
245             */
246            private String getExtention( final String fileAddress ) {
247                    int idx = fileAddress.lastIndexOf('.');
248                    if( idx!=-1 ) { return fileAddress.substring( idx+1 ); }
249                    return "";
250            }
251    
252            /**
253             * アドレス名からファイル名を取り出しますã?
254             *
255             * アドレス名ã?後ろから、ファイルセパレータ以降をファイル名としてåˆ?‚Š取りますã?
256             * ファイルセパレータが存在しなã�??合ã?アドレス名をそã?まま返しますã?
257             * ここでは、OS毎に異なるファイルセパレータを統ä¸?¾Œに処ç�?�—てくださいã€?
258             *
259             * @param       fileAddress     アドレスå�?
260             *
261             * @return      ファイルå�?
262             */
263            private String getFileName( final String fileAddress ) {
264                    int idx = fileAddress.lastIndexOf( HybsSystem.FS );
265                    if( idx!=-1 ) { return fileAddress.substring( idx+1 ); }
266                    return fileAddress;
267            }
268    
269            /**
270             * アドレス名から対応するコンãƒ?ƒ³ãƒ?‚¿イプを取り出しますã?
271             *
272             * アドレス名からã?ファイル拡張子を取り出しã?対応するコンãƒ?ƒ³ãƒ?‚¿イプを返しますã?
273             * コンãƒ?ƒ³ãƒ?‚¿イプã?、CONTENT_TYPE_TABLE 配å?に定義してã�?‚‹中から検索して返しますã?
274             * 存在しなã�??合ã?ã€?application/octet-stream" を返しますã?
275             *
276             * @param       fileAddress     アドレスå�?
277             *
278             * @return      コンãƒ?ƒ³ãƒ?‚¿イãƒ?
279             */
280            private String getContentType( final String fileAddress ) {
281                    String extention = getExtention( fileAddress );
282                    for( int j=0; j<CONTENT_TYPE_TABLE.length; j++ ) {
283                            if( CONTENT_TYPE_TABLE[j][EXTENTION].equalsIgnoreCase( extention ) ) {
284                                    return CONTENT_TYPE_TABLE[j][CONTENT_TYPE];
285                            }
286                    }
287                    return "application/octet-stream";
288            }
289    }