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     * <del>ファイルåã?æŒ?®šã«ã€Content-disposition 属æ?を使用ã—ã¦ã?¾ã™ãŒã€Internet Explorer ã®
046     * 仕様ã§ã€attachment ã§æŒ?®šã™ã‚‹ã¨é–‹ãボタンãŒä½¿ãˆã¾ã›ã‚“ã€?"ファイルå? ãŒè¦‹ã¤ã‹ã‚Šã¾ã›ã‚“ã€?
047     * ã“ã?実è£?§ã¯ã€inline を使用ã™ã‚‹ãŸã‚ã€æ–°ãŸãªç”»é¢ã§é–‹ããŸã„å ´åˆã?ã€ãƒªãƒ³ã‚¯ã«ã‚¿ãƒ¼ã‚²ãƒ?ƒˆå±žæ?ã‚?
048     * æŒ?®šã—ã¦ãã ã•ã„ã€?
049     * http://support.microsoft.com/?scid=kb;ja;436605&spid=2073&sid=269</del>
050     * 基本çš?«ã¯Content-disposition属æ?ã¨ã—ã¦"attachment"ãŒæŒ‡å®šã•れã¾ã™ã?
051     * ä½?—ã€å¼•æ•°ã« inline=true を指定ã™ã‚‹ã“ã¨ã§ã€Content-disposition属æ?ã«"inline"ãŒæŒ‡å®šã•れã¾ã™ã?
052     * ã¾ãŸã?シスãƒ?ƒ ãƒªã‚½ãƒ¼ã‚¹ã®USE_FILEDOWNLOAD_CHECKKEYã‚’trueã«æŒ?®šã™ã‚‹ã“ã¨ã§ã€ç°¡æ˜“çš„ãªãƒã‚§ãƒ?‚¯ã‚?
053     * 行ã†ã“ã¨ãŒã§ãã¾ã™ã?
054     * 具体的ã«ã¯ã€ã“れを有効ã«ã™ã‚‹ã¨ã€file属æ?ã®å€¤ã‹ã‚‰è¨ˆç®—ã•れるMD5ãƒã‚§ãƒ?‚¯ã‚µãƒ?¨ã€?key"ã¨ã?†
055     * ãƒ‘ãƒ©ãƒ¡ãƒ¼ã‚¿ãƒ¼ã«æŒ?®šã•れãŸå€¤ãŒä¸??ã—ãŸå ´åˆã?ã¿ãƒ?‚¦ãƒ³ãƒ­ãƒ¼ãƒ‰ãŒè¨±å¯ã•れã€keyãŒæŒ‡å®šã•れã¦ã?ªã??
056     * ã¾ãŸã?値ãŒç•°ãªã‚‹å?åˆã?ãƒ?‚¦ãƒ³ãƒ­ãƒ¼ãƒ‰ã‚¨ãƒ©ãƒ¼ã¨ãªã‚Šã¾ã™ã?
057     *
058     * ä¸?ˆ¬çš?ªã‚µãƒ¼ãƒ–レãƒ?ƒˆã¨åŒæ§˜ã«ã€ãƒ‡ãƒ—ロイメントã?ãƒ?‚£ã‚¹ã‚¯ãƒªãƒ—ã‚¿ WEB-INF/web.xml ã«ã€?
059     * servlet è¦ç´?¨ ãã?マッピング(servlet-mapping)を定義ã™ã‚‹å¿?¦ãŒã‚りã¾ã™ã?
060     *
061     *     &lt;servlet&gt;
062     *         &lt;servlet-name&gt;fileDownload&lt;/servlet-name&gt;
063     *         &lt;servlet-class&gt;org.opengion.hayabusa.servlet.FileDownload&lt;/servlet-class&gt;
064     *     &lt;/servlet&gt;
065     *
066     *     &lt;servlet-mapping&gt;
067     *         &lt;servlet-name&gt;fileDownload&lt;/servlet-name&gt;
068     *         &lt;url-pattern&gt;/jsp/fileDownload&lt;/url-pattern&gt;
069     *     &lt;/servlet-mapping&gt;
070     *
071     * ä¸?ˆ¬ã«ã¯ã€http://:ãƒã?ãƒ?シスãƒ?ƒ ID/jsp/fileDownload?file=サーãƒã?物ç?ƒ•ァイル&name=ファイルå?
072     * å½¢å¼ã?URL ã§ã‚¢ã‚¯ã‚»ã‚¹ã—ã¾ã™ã?
073     *
074     * @og.rev 3.8.1.1 (2005/11/21) æ–°è¦è¿½åŠ?
075     * @og.group ãã?他機è?
076     *
077     * @version  0.9.0  2000/10/17
078     * @author   Kazuhiko Hasegawa
079     * @since    JDK1.1,
080     */
081    public class FileDownload extends HttpServlet {
082            private static final long serialVersionUID = 539020110901L ;
083    
084            // æ‹¡å¼µå­contentType対応テーブル
085            private static final String CONTENT_TYPE_TABLE[][] = {
086                    {"jpg", "image/pjpeg"   },
087                    {"gif", "image/gif"             },
088                    {"txt", "text/plain"    },
089    //              {"xls", "application/vnd.ms-excel"}
090                    // OpenDocument追�
091                    {"xls", "application/vnd.ms-excel"},
092                    {"odp", "application/vnd.oasis.opendocument.presentation"}, // 4.3.5.5 (2008/03/08)
093                    {"ods", "application/vnd.oasis.opendocument.spreadsheet"}, // 4.3.5.5 (2008/03/08)
094                    {"odt", "application/vnd.oasis.opendocument.text"} // 4.3.5.5 (2008/03/08)
095            };
096            private static final int EXTENTION       = 0;
097            private static final int CONTENT_TYPE= 1;
098    
099            /**
100             * GET メソãƒ?ƒ‰ãŒå‘¼ã°ã‚ŒãŸã¨ãã«å®Ÿè¡Œã—ã¾ã™ã?
101             *
102             * 処ç??ã€doPost ã¸æŒ¯ã‚ŠãªãŠã—ã¦ã?¾ã™ã?
103             *
104             * @param       request HttpServletRequestオブジェク�
105             * @param       response        HttpServletResponseオブジェク�
106             *
107             * @og.rev 3.8.1.2 (2005/12/19) åŠè§’ã‚«ãƒ?å…¨è§’ã‚«ãƒŠå¤‰æ›æ©Ÿè?ã®è¿½åŠ?
108             *
109             * @throws ServletException
110             * @throws IOException
111             */
112            @Override
113            public void doGet( final HttpServletRequest request, final HttpServletResponse response )
114                                                            throws ServletException, IOException {
115                    doPost( request,response );
116            }
117    
118            /**
119             * POST メソãƒ?ƒ‰ãŒå‘¼ã°ã‚ŒãŸã¨ãã«å®Ÿè¡Œã—ã¾ã™ã?
120             *
121             * file 引数㮠サーãƒã?物ç?ƒ•ァイルをã?クライアントã«ã‚¹ãƒˆãƒªãƒ¼ãƒ?Œ–ã—ã¦è¿”ã—ã¾ã™ã?
122             * name 引数ãŒã‚れã?ã€ãã®åå‰ã®ãƒ•ァイルåã§ã‚¯ãƒ©ã‚¤ã‚¢ãƒ³ãƒˆãŒãƒ•ァイルセーブã§ãるよã†ã«
123             * ã—ã¾ã™ã?name 引数ãŒãªã‘れã°ã€ãã®ã¾ã¾ç‰©ç?ƒ•ァイルåãŒä½¿ç”¨ã•れã¾ã™ã?
124             * サーãƒã?物ç?ƒ•ァイルåãŒã€ç›¸å¯¾ãƒ‘スã®å ´åˆã?コンãƒ?‚­ã‚¹ãƒˆãƒ«ãƒ¼ãƒˆã«å¯¾ã™ã‚‹ç›¸å¯¾ãƒ‘スã«ãªã‚Šã¾ã™ã?
125             * (例:G:\webapps\dbdef2\ ãªã©)
126             *
127             * @og.rev 5.3.2.0 (2011/02/01) 日本語ファイルåãŒæ­£ã—ã処ç?§ããªã?ƒã‚°ã‚’修正
128             * @og.rev 5.3.4.0 (2011/04/01) IEã§ãƒ•ã‚¡ã‚¤ãƒ«ãŒæ­£ã—ããƒ?‚¦ãƒ³ãƒ­ãƒ¼ãƒ‰ã§ããªã?ƒã‚°ã‚’修正
129             * @og.rev 5.3.5.0 (2011/05/01) ファイルãƒ?‚¦ãƒ³ãƒ­ãƒ¼ãƒ‰ãƒã‚§ãƒ?‚¯ã‚­ãƒ¼å¯¾å¿?
130             * @og.rev 5.3.6.0 (2011/06/01) ファイルãƒ?‚¦ãƒ³ãƒ­ãƒ¼ãƒ‰ã?attachmentã«å¤‰æ›´(ãƒ?‚¦ãƒ³ãƒ­ãƒ¼ãƒ‰ãƒ€ã‚¤ã‚¢ãƒ­ã‚°ã‚’å?ã?
131             * @og.rev 5.3.8.0 (2011/08/01) ãƒ•ã‚¡ã‚¤ãƒ«åæŒ‡å®šã§IEã®å ´åˆã?URLエンコードã™ã‚‹ã¨é€”中ã§åˆ?‚Œã‚‹ãŸã‚?IE7ã®ãƒã‚°)ã€Shift_JIS(WIndows-31J)ã§ç›´æŽ¥æŒ?®šã™ã‚‹ã?
132             * @og.rev 5.3.9.0 (2011/09/01) 引数ã«inline=trueを指定ã™ã‚‹ã“ã¨ã§ã€ã‚¤ãƒ³ãƒ©ã‚¤ãƒ³è¡¨ç¤ºãŒå?æ¥ã‚‹ã‚ˆã?«å¯¾å¿?
133             * @og.rev 5.7.1.2 (2013/12/20) 日本語ファイルã®IE11対応ï¼?A変更??msg â‡?errMsg 変更
134             *
135             * @param       request HttpServletRequestオブジェク�
136             * @param       response        HttpServletResponseオブジェク�
137             *
138             * @throws ServletException
139             * @throws IOException
140             */
141            @Override
142            public void doPost( final HttpServletRequest request, final HttpServletResponse response )
143                                                            throws ServletException, IOException {
144    
145                    // 3.8.1.2 (2005/12/19) åŠè§’ã‚«ãƒ?å…¨è§’ã‚«ãƒŠå¤‰æ›æ©Ÿè?ã®è¿½åŠ?
146                    boolean hanzenFlag = HybsSystem.sysBool( "USE_FILEDOWNLOAD_HAN_ZEN" );
147    
148                    String reqFilename = request.getParameter( "file" );
149                    String newFilename = request.getParameter( "name" );
150    
151                    // 5.3.9.0 (2011/09/01) 引数ã«inline=trueを指定ã™ã‚‹ã“ã¨ã§ã€ã‚¤ãƒ³ãƒ©ã‚¤ãƒ³è¡¨ç¤ºãŒå?æ¥ã‚‹ã‚ˆã?«å¯¾å¿?
152                    boolean inline = StringUtil.nval( request.getParameter( "inline" ), false );
153                    String dipositionType = inline ? "inline" : "attachment";
154    
155                    // クライアントå?ã®æ–?­—エンコーãƒ?‚£ãƒ³ã‚°ã‚’UTF-8ã«å¤‰æ›
156                    reqFilename = new String( reqFilename.getBytes("ISO-8859-1"), "UTF-8" );
157    
158                    // 5.3.5.0 (2011/05/01) ファイルãƒ?‚¦ãƒ³ãƒ­ãƒ¼ãƒ‰ãƒã‚§ãƒ?‚¯ã‚­ãƒ¼å¯¾å¿?
159                    boolean useCheck = HybsSystem.sysBool( "USE_FILEDOWNLOAD_CHECKKEY" );
160                    if( useCheck ) {
161                            String checkKey = request.getParameter( "key" );
162                            if( checkKey == null || !checkKey.equals( HybsCryptography.getMD5( reqFilename ) ) ) {
163    //                              String msg = "ã‚¢ã‚¯ã‚»ã‚¹ãŒæ‹’å¦ã•れã¾ã—ãŸã€?URLãƒã‚§ãƒ?‚¯)";
164    //                              throw new HybsSystemException( msg );
165                                    String errMsg = "ã‚¢ã‚¯ã‚»ã‚¹ãŒæ‹’å¦ã•れã¾ã—ãŸã€?URLãƒã‚§ãƒ?‚¯)";
166                                    throw new HybsSystemException( errMsg );        // 5.7.1.2 (2013/12/20) msg �errMsg 変更
167                            }
168                    }
169    
170                    // 相対パスを絶対パスã«å¤‰æ›ã€‚ファイルセパレータも正è¦åŒ–ã•れã¦ã?¾ã™ã?
171                    reqFilename = HybsSystem.url2dir( reqFilename );
172    
173                    // æ‹¡å¼µå­ã‹ã‚‰contentTypeã‚’ç²å¾?
174                    String contentType = getContentType( reqFilename );
175                    // contentTypeã‚’å?åŠ?
176                    response.setContentType( contentType );
177    
178                    // 表示ファイルåã?æŒ?®?
179                    if( newFilename == null || newFilename.length() == 0 ) {
180                            newFilename = getFileName( reqFilename );
181                    }
182                    else {
183                            newFilename = new String( newFilename.getBytes("ISO-8859-1"), "UTF-8" );
184                    }
185    
186                    // 3.8.1.2 (2005/12/19) åŠè§’カナを全角カナã«ç½®ãæ›ãˆã¾ã™ã?ファイルãƒ?‚¤ã‚¢ãƒ­ã‚°ã®æ–?­—化ã‘仮対å¿?
187                    if( hanzenFlag ) {
188                            newFilename = KanaFilter.han2zen( newFilename );
189                    }
190    
191                    // 5.3.2.0 (2011/02/01) 日本語ファイルåãŒæ­£ã—ã処ç?§ããªã?ƒã‚°ã‚’修正
192    ////            newFilename = StringUtil.urlEncode( newFilename );
193    //              if( request.getHeader( "User-Agent" ).indexOf( "MSIE" ) == -1 ) {
194    //                      newFilename = MimeUtility.encodeWord( newFilename, "UTF-8", "B" );
195    //              }
196    //              else {
197    //                      // 5.3.8.0 (2011/08/01) IEã®å ´åˆã?URLエンコードã™ã‚‹ã¨é€”中ã§åˆ?‚Œã‚‹ãŸã‚?IE7ã®ãƒã‚°)ã€Shift_JIS(WIndows-31J)ã§ç›´æŽ¥æŒ?®šã™ã‚‹ã?
198    ////                    newFilename = StringUtil.urlEncode( newFilename );
199    //                      newFilename = new String( newFilename.getBytes("Windows-31J"), "ISO-8859-1" );
200    //              }
201                    
202                    // 5.7.1.2 (2013/12/20) æ¡ä»¶ã‚’å転ã•ã›ãŸä¸Šã§IE11対応を行ã†
203                    if( request.getHeader( "User-Agent" ).indexOf( "MSIE" ) >= 0 || request.getHeader( "User-Agent" ).indexOf( "Trident" ) >= 0 ) {
204                            newFilename = new String( newFilename.getBytes("Windows-31J"), "ISO-8859-1" );
205                    }
206                    else {
207                            newFilename = MimeUtility.encodeWord( newFilename, "UTF-8", "B" );
208                    }
209    
210                    // ファイルåã?é€ä¿¡( attachment部åˆ?‚’inlineã«å¤‰æ›´ã™ã‚Œã°ã‚¤ãƒ³ãƒ©ã‚¤ãƒ³è¡¨ç¤º )
211                    // 5.3.9.0 (2011/09/01) 引数ã«inline=trueを指定ã™ã‚‹ã“ã¨ã§ã€ã‚¤ãƒ³ãƒ©ã‚¤ãƒ³è¡¨ç¤ºãŒå?æ¥ã‚‹ã‚ˆã?«å¯¾å¿?
212    //              response.setHeader( "Content-disposition", "inline; filename=\"" + newFilename + "\"" );
213    //              response.setHeader( "Content-disposition", "attachment; filename=\"" + newFilename + "\"" );
214                    response.setHeader( "Content-disposition", dipositionType + "; filename=\"" + newFilename + "\"" );
215    
216                    // 5.3.4.0 (2011/04/01) IEã§ãƒ•ã‚¡ã‚¤ãƒ«ãŒæ­£ã—ããƒ?‚¦ãƒ³ãƒ­ãƒ¼ãƒ‰ã§ããªã?ƒã‚°ã‚’修正
217                    response.setHeader( "Cache-Control", "public" );
218    
219                    // ファイルå†?®¹ã®å‡ºåŠ?
220                    FileInputStream     fin = null;
221                    ServletOutputStream out = null;
222                    try {
223                            fin = new FileInputStream( reqFilename );
224                            out = response.getOutputStream();
225    
226                            // ファイル読ã¿è¾¼ã¿ç”¨ãƒãƒƒãƒ•ã‚¡
227                            byte buffer[]  = new byte[4096];
228                            int size;
229                            while((size = fin.read(buffer))!=-1) {
230                                    out.write(buffer,0, size);
231                                    out.flush();
232                            }
233                    }
234                    finally {
235                            Closer.ioClose( fin );          // 4.0.0 (2006/01/31) close 処ç?™‚ã® IOException ã‚’ç„¡è¦?
236                            Closer.ioClose( out );          // 4.0.0 (2006/01/31) close 処ç?™‚ã® IOException ã‚’ç„¡è¦?
237                    }
238            }
239    
240            /**
241             * アドレスåã‹ã‚‰æ‹¡å¼µå­ã‚’å–り出ã—ã¾ã™ã?
242             *
243             * アドレスåã?後ã‚ã‹ã‚‰ã€?." 以é™ã‚’æ‹¡å¼µå­ã¨ã—ã¦åˆ?‚Šå–りã¾ã™ã?
244             * æ‹¡å¼µå­ãŒå­˜åœ¨ã—ãªã??å?æŒ?®šã?ファイルåã« "." ãŒå«ã¾ã‚Œãªã??å?ã¯
245             * ゼロæ–?­—å?("")ã‚’è¿”ã—ã¾ã™ã?
246             *
247             * @param       fileAddress     アドレスå?
248             *
249             * @return      拡張�
250             */
251            private String getExtention( final String fileAddress ) {
252                    int idx = fileAddress.lastIndexOf('.');
253                    if( idx!=-1 ) { return fileAddress.substring( idx+1 ); }
254                    return "";
255            }
256    
257            /**
258             * アドレスåã‹ã‚‰ãƒ•ァイルåã‚’å–り出ã—ã¾ã™ã?
259             *
260             * アドレスåã?後ã‚ã‹ã‚‰ã€ãƒ•ァイルセパレータ以é™ã‚’ファイルåã¨ã—ã¦åˆ?‚Šå–りã¾ã™ã?
261             * ファイルセパレータãŒå­˜åœ¨ã—ãªã??åˆã?アドレスåã‚’ãã?ã¾ã¾è¿”ã—ã¾ã™ã?
262             * ã“ã“ã§ã¯ã€OS毎ã«ç•°ãªã‚‹ãƒ•ァイルセパレータを統ä¸?¾Œã«å‡¦ç?—ã¦ãã ã•ã„ã€?
263             *
264             * @param       fileAddress     アドレスå?
265             *
266             * @return      ファイルå?
267             */
268            private String getFileName( final String fileAddress ) {
269                    int idx = fileAddress.lastIndexOf( HybsSystem.FS );
270                    if( idx!=-1 ) { return fileAddress.substring( idx+1 ); }
271                    return fileAddress;
272            }
273    
274            /**
275             * アドレスåã‹ã‚‰å¯¾å¿œã™ã‚‹ã‚³ãƒ³ãƒ?ƒ³ãƒ?‚¿ã‚¤ãƒ—ã‚’å–り出ã—ã¾ã™ã?
276             *
277             * アドレスåã‹ã‚‰ã?ファイル拡張å­ã‚’å–り出ã—ã?対応ã™ã‚‹ã‚³ãƒ³ãƒ?ƒ³ãƒ?‚¿ã‚¤ãƒ—ã‚’è¿”ã—ã¾ã™ã?
278             * コンãƒ?ƒ³ãƒ?‚¿ã‚¤ãƒ—ã?ã€CONTENT_TYPE_TABLE é…å?ã«å®šç¾©ã—ã¦ã?‚‹ä¸­ã‹ã‚‰æ¤œç´¢ã—ã¦è¿”ã—ã¾ã™ã?
279             * 存在ã—ãªã??åˆã?ã€?application/octet-stream" ã‚’è¿”ã—ã¾ã™ã?
280             *
281             * @param       fileAddress     アドレスå?
282             *
283             * @return      コンãƒ?ƒ³ãƒ?‚¿ã‚¤ãƒ?
284             */
285            private String getContentType( final String fileAddress ) {
286                    String extention = getExtention( fileAddress );
287                    for( int j=0; j<CONTENT_TYPE_TABLE.length; j++ ) {
288                            if( CONTENT_TYPE_TABLE[j][EXTENTION].equalsIgnoreCase( extention ) ) {
289                                    return CONTENT_TYPE_TABLE[j][CONTENT_TYPE];
290                            }
291                    }
292                    return "application/octet-stream";
293            }
294    }