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 private int _cropWidth; 079 private int _cropHeight; 080 081 @Override 082 public void service(ServiceManager sManager) throws ServiceException 083 { 084 super.service(sManager); 085 _resolver = (AmetysObjectResolver) sManager.lookup(AmetysObjectResolver.ROLE); 086 _rightManager = (RightManager) sManager.lookup(RightManager.ROLE); 087 _currentUserProvider = (CurrentUserProvider) sManager.lookup(CurrentUserProvider.ROLE); 088 } 089 090 @Override 091 public void setup(SourceResolver sResolver, Map objModel, String src, Parameters par) throws ProcessingException, SAXException, IOException 092 { 093 super.setup(sResolver, objModel, src, par); 094 095 String id = par.getParameter("id", null); 096 String path = par.getParameter("path", null); 097 String version = par.getParameter("version", null); 098 099 // parameters for image resizing 100 _width = par.getParameterAsInteger("width", 0); 101 _height = par.getParameterAsInteger("height", 0); 102 _maxWidth = par.getParameterAsInteger("maxWidth", 0); 103 _maxHeight = par.getParameterAsInteger("maxHeight", 0); 104 _cropWidth = par.getParameterAsInteger("cropWidth", 0); 105 _cropHeight = par.getParameterAsInteger("cropHeight", 0); 106 107 _readForDownload = par.getParameterAsBoolean("download", false); 108 Response response = ObjectModelHelper.getResponse(objectModel); 109 110 try 111 { 112 if (id != null) 113 { 114 _object = _resolver.resolveById(id); 115 } 116 else 117 { 118 _object = _resolver.resolveByPath(_decodePath(path.substring(1))); 119 } 120 121 // Check user access 122 checkUserAccess(); 123 124 if (!StringUtils.isEmpty(version) && _object instanceof VersionableAmetysObject) 125 { 126 ((VersionableAmetysObject) _object).switchToRevision(version); 127 } 128 129 if (_readForDownload) 130 { 131 String name = _object.getName(); 132 String encodedName = null; 133 try 134 { 135 URI uri = new URI(null, null, name, null); 136 encodedName = uri.toASCIIString(); 137 // EXPLORER-358 : Fix for Chrome which does not support comma in filename 138 encodedName = encodedName.replaceAll(",", "%2C"); 139 } 140 catch (URISyntaxException e) 141 { 142 // do nothing and send no encoded name 143 } 144 145 name = name.replaceAll("\\\\", "\\\\\\\\"); 146 name = name.replaceAll("\\\"", "\\\\\\\""); 147 response.setHeader("Content-Disposition", "attachment; filename=\"" + (encodedName != null ? encodedName : name) + "\"" + (encodedName != null ? ";filename*=UTF-8''" + encodedName : "")); 148 } 149 } 150 catch (UnknownAmetysObjectException e) 151 { 152 if (id != null) 153 { 154 throw new ResourceNotFoundException(String.format("The resource with id '%s' does not exist", id)); 155 } 156 else 157 { 158 throw new ResourceNotFoundException(String.format("The resource at path '%s' does not exist", path)); 159 } 160 161 } 162 } 163 164 /** 165 * Check the user access 166 * @throws AuthorizationRequiredException if authorization is required 167 * @throws AccessDeniedException if user has no access 168 */ 169 protected void checkUserAccess() throws AuthorizationRequiredException, AccessDeniedException 170 { 171 UserIdentity user = _currentUserProvider.getUser(); 172 173 if (user == null) 174 { 175 // Check anonymous access 176 if (!_rightManager.hasAnonymousReadAccess(_object.getParent())) 177 { 178 throw new AuthorizationRequiredException(null); 179 } 180 } 181 else if (!_rightManager.hasReadAccess(user, _object.getParent())) 182 { 183 throw new AccessDeniedException("User " + user + " has no right to access the resource " + _object.getId()); 184 } 185 } 186 187 /** 188 * Decode the resource path 189 * @param path the resource path 190 * @return the decoded resource path 191 * @throws UnsupportedEncodingException if UTF-8 encoding is not supported 192 */ 193 protected String _decodePath (String path) throws UnsupportedEncodingException 194 { 195 StringBuffer sb = new StringBuffer(); 196 197 String[] parts = path.split("/"); 198 for (String part : parts) 199 { 200 sb.append("/"); 201 sb.append(URLDecoder.decode(part, "utf-8")); 202 } 203 return sb.toString(); 204 } 205 206 @Override 207 public Serializable getKey() 208 { 209 return _object.getId() + "#" + _height + "#" + _width + "#" + _maxHeight + "#" + _maxWidth + "#" + _cropHeight + "#" + _cropWidth; 210 } 211 212 @Override 213 public SourceValidity getValidity() 214 { 215 return new TimeStampValidity(getLastModified()); 216 } 217 218 @Override 219 public long getLastModified() 220 { 221 return _object.getLastModified().getTime(); 222 } 223 224 @Override 225 public String getMimeType() 226 { 227 return _object.getMimeType(); 228 } 229 230 @Override 231 public void generate() throws IOException, SAXException, ProcessingException 232 { 233 String name = _object.getName(); 234 name = name.replaceAll("\\\\", "\\\\\\\\"); 235 name = name.replaceAll("\\\"", "\\\\\\\""); 236 237 try (InputStream is = _object.getInputStream()) 238 { 239 int i = name.lastIndexOf('.'); 240 String format = i != -1 ? name.substring(i + 1) : "png"; 241 format = _allowedFormats.contains(format) ? format : "png"; 242 243 if (_isImage()) 244 { 245 ImageHelper.generateThumbnail(is, out, format, _height, _width, _maxHeight, _maxWidth, _cropHeight, _cropWidth); 246 } 247 else 248 { 249 IOUtils.copy(is, out); 250 } 251 } 252 catch (Exception e) 253 { 254 throw new ProcessingException("Unable to download file of id " + _object.getId(), e); 255 } 256 finally 257 { 258 IOUtils.closeQuietly(out); 259 } 260 } 261 262 /** 263 * Determines if the file is an image 264 * @return <code>true</code> if file is a image 265 */ 266 protected boolean _isImage() 267 { 268 if (_width > 0 || _height > 0 || _maxHeight > 0 || _maxWidth > 0 || _cropHeight > 0 || _cropWidth > 0) 269 { 270 // resize is required, assume this is a image 271 return true; 272 } 273 else 274 { 275 return getMimeType() != null && getMimeType().startsWith("image/"); 276 } 277 } 278 279 @Override 280 public void recycle() 281 { 282 super.recycle(); 283 _object = null; 284 } 285}