001/* 002 * Copyright 2010 Anyware Services 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, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016package org.ametys.plugins.explorer.resources.readers; 017 018import java.io.IOException; 019import java.io.InputStream; 020import java.io.Serializable; 021import java.io.UnsupportedEncodingException; 022import java.net.URI; 023import java.net.URISyntaxException; 024import java.net.URLDecoder; 025import java.util.Arrays; 026import java.util.Collection; 027import java.util.Map; 028 029import org.apache.avalon.framework.parameters.Parameters; 030import org.apache.avalon.framework.service.ServiceException; 031import org.apache.avalon.framework.service.ServiceManager; 032import org.apache.cocoon.ProcessingException; 033import org.apache.cocoon.ResourceNotFoundException; 034import org.apache.cocoon.caching.CacheableProcessingComponent; 035import org.apache.cocoon.environment.ObjectModelHelper; 036import org.apache.cocoon.environment.Response; 037import org.apache.cocoon.environment.SourceResolver; 038import org.apache.cocoon.reading.ServiceableReader; 039import org.apache.commons.io.IOUtils; 040import org.apache.commons.lang.StringUtils; 041import org.apache.excalibur.source.SourceValidity; 042import org.apache.excalibur.source.impl.validity.TimeStampValidity; 043import org.xml.sax.SAXException; 044 045import org.ametys.core.right.RightManager; 046import org.ametys.core.user.CurrentUserProvider; 047import org.ametys.core.user.UserIdentity; 048import org.ametys.core.util.ImageHelper; 049import org.ametys.plugins.explorer.resources.Resource; 050import org.ametys.plugins.repository.AmetysObjectResolver; 051import org.ametys.plugins.repository.UnknownAmetysObjectException; 052import org.ametys.plugins.repository.version.VersionableAmetysObject; 053import org.ametys.runtime.authentication.AccessDeniedException; 054import org.ametys.runtime.authentication.AuthorizationRequiredException; 055 056/** 057 * Reader for {@link Resource} 058 */ 059public class AmetysResourceReader extends ServiceableReader implements CacheableProcessingComponent 060{ 061 /** The Ametys object resolver */ 062 protected AmetysObjectResolver _resolver; 063 /** The resource */ 064 protected Resource _object; 065 /** The right manager */ 066 protected RightManager _rightManager; 067 /** The current user provider */ 068 protected CurrentUserProvider _currentUserProvider; 069 070 private boolean _readForDownload; 071 072 private Collection<String> _allowedFormats = Arrays.asList(new String[]{"png", "gif", "jpg", "jpeg"}); 073 074 private int _width; 075 private int _height; 076 private int _maxWidth; 077 private int _maxHeight; 078 079 @Override 080 public void service(ServiceManager sManager) throws ServiceException 081 { 082 super.service(sManager); 083 _resolver = (AmetysObjectResolver) sManager.lookup(AmetysObjectResolver.ROLE); 084 _rightManager = (RightManager) sManager.lookup(RightManager.ROLE); 085 _currentUserProvider = (CurrentUserProvider) sManager.lookup(CurrentUserProvider.ROLE); 086 } 087 088 @Override 089 public void setup(SourceResolver sResolver, Map objModel, String src, Parameters par) throws ProcessingException, SAXException, IOException 090 { 091 super.setup(sResolver, objModel, src, par); 092 093 String id = par.getParameter("id", null); 094 String path = par.getParameter("path", null); 095 String version = par.getParameter("version", null); 096 097 // parameters for image resizing 098 _width = par.getParameterAsInteger("width", 0); 099 _height = par.getParameterAsInteger("height", 0); 100 _maxWidth = par.getParameterAsInteger("maxWidth", 0); 101 _maxHeight = par.getParameterAsInteger("maxHeight", 0); 102 103 _readForDownload = par.getParameterAsBoolean("download", false); 104 Response response = ObjectModelHelper.getResponse(objectModel); 105 106 try 107 { 108 if (id != null) 109 { 110 _object = _resolver.resolveById(id); 111 } 112 else 113 { 114 _object = _resolver.resolveByPath(_decodePath(path.substring(1))); 115 } 116 117 // Check user access 118 checkUserAccess(); 119 120 if (!StringUtils.isEmpty(version) && _object instanceof VersionableAmetysObject) 121 { 122 ((VersionableAmetysObject) _object).switchToRevision(version); 123 } 124 125 if (_readForDownload) 126 { 127 String name = _object.getName(); 128 String encodedName = null; 129 try 130 { 131 URI uri = new URI(null, null, name, null); 132 encodedName = uri.toASCIIString(); 133 // EXPLORER-358 : Fix for Chrome which does not support comma in filename 134 encodedName = encodedName.replaceAll(",", "%2C"); 135 } 136 catch (URISyntaxException e) 137 { 138 // do nothing and send no encoded name 139 } 140 141 name = name.replaceAll("\\\\", "\\\\\\\\"); 142 name = name.replaceAll("\\\"", "\\\\\\\""); 143 response.setHeader("Content-Disposition", "attachment; filename=\"" + (encodedName != null ? encodedName : name) + "\"" + (encodedName != null ? ";filename*=UTF-8''" + encodedName : "")); 144 } 145 } 146 catch (UnknownAmetysObjectException e) 147 { 148 if (id != null) 149 { 150 throw new ResourceNotFoundException(String.format("The resource with id '%s' does not exist", id)); 151 } 152 else 153 { 154 throw new ResourceNotFoundException(String.format("The resource at path '%s' does not exist", path)); 155 } 156 157 } 158 } 159 160 /** 161 * Check the user access 162 * @throws AuthorizationRequiredException if authorization is required 163 * @throws AccessDeniedException if user has no access 164 */ 165 protected void checkUserAccess() throws AuthorizationRequiredException, AccessDeniedException 166 { 167 UserIdentity user = _currentUserProvider.getUser(); 168 169 if (user == null) 170 { 171 // Check anonymous access 172 if (!_rightManager.hasAnonymousReadAccess(_object.getParent())) 173 { 174 throw new AuthorizationRequiredException(null); 175 } 176 } 177 else if (!_rightManager.hasReadAccess(user, _object.getParent())) 178 { 179 throw new AccessDeniedException("User " + user + " has no right to acesss the resource " + _object.getId()); 180 } 181 } 182 183 /** 184 * Decode the resource path 185 * @param path the resource path 186 * @return the decoded resource path 187 * @throws UnsupportedEncodingException if UTF-8 encoding is not supported 188 */ 189 protected String _decodePath (String path) throws UnsupportedEncodingException 190 { 191 StringBuffer sb = new StringBuffer(); 192 193 String[] parts = path.split("/"); 194 for (String part : parts) 195 { 196 sb.append("/"); 197 sb.append(URLDecoder.decode(part, "utf-8")); 198 } 199 return sb.toString(); 200 } 201 202 @Override 203 public Serializable getKey() 204 { 205 return _object.getId() + "#" + _height + "#" + _width + "#" + _maxHeight + "#" + _maxWidth; 206 } 207 208 @Override 209 public SourceValidity getValidity() 210 { 211 return new TimeStampValidity(getLastModified()); 212 } 213 214 @Override 215 public long getLastModified() 216 { 217 return _object.getLastModified().getTime(); 218 } 219 220 @Override 221 public String getMimeType() 222 { 223 return _object.getMimeType(); 224 } 225 226 @Override 227 public void generate() throws IOException, SAXException, ProcessingException 228 { 229 String name = _object.getName(); 230 name = name.replaceAll("\\\\", "\\\\\\\\"); 231 name = name.replaceAll("\\\"", "\\\\\\\""); 232 233 try (InputStream is = _object.getInputStream()) 234 { 235 int i = name.lastIndexOf('.'); 236 String format = i != -1 ? name.substring(i + 1) : "png"; 237 format = _allowedFormats.contains(format) ? format : "png"; 238 239 if (_isImage()) 240 { 241 ImageHelper.generateThumbnail(is, out, format, _height, _width, _maxHeight, _maxWidth); 242 } 243 else 244 { 245 IOUtils.copy(is, out); 246 } 247 } 248 catch (Exception e) 249 { 250 throw new ProcessingException("Unable to download file of id " + _object.getId(), e); 251 } 252 finally 253 { 254 IOUtils.closeQuietly(out); 255 } 256 } 257 258 /** 259 * Determines if the file is an image 260 * @return <code>true</code> if file is a image 261 */ 262 protected boolean _isImage() 263 { 264 if (_width > 0 || _height > 0 || _maxHeight > 0 || _maxWidth > 0) 265 { 266 // resize is required, assume this is a image 267 return true; 268 } 269 else 270 { 271 return getMimeType() != null && getMimeType().startsWith("image/"); 272 } 273 } 274 275 @Override 276 public void recycle() 277 { 278 super.recycle(); 279 _object = null; 280 } 281}