001/* 002 * Copyright 2017 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.linkdirectory.link; 017 018import java.io.IOException; 019import java.io.InputStream; 020import java.net.URL; 021import java.util.ArrayList; 022import java.util.HashMap; 023import java.util.List; 024import java.util.Map; 025 026import org.apache.avalon.framework.parameters.Parameters; 027import org.apache.avalon.framework.service.ServiceException; 028import org.apache.avalon.framework.service.ServiceManager; 029import org.apache.cocoon.acting.ServiceableAction; 030import org.apache.cocoon.environment.ObjectModelHelper; 031import org.apache.cocoon.environment.Redirector; 032import org.apache.cocoon.environment.Request; 033import org.apache.cocoon.environment.Response; 034import org.apache.cocoon.environment.SourceResolver; 035import org.apache.commons.lang3.StringUtils; 036import org.apache.http.HttpEntity; 037import org.apache.http.client.config.RequestConfig; 038import org.apache.http.client.methods.CloseableHttpResponse; 039import org.apache.http.client.methods.HttpGet; 040import org.apache.http.entity.ContentType; 041import org.apache.http.impl.client.CloseableHttpClient; 042import org.apache.http.impl.client.HttpClientBuilder; 043 044import org.ametys.core.cocoon.JSonReader; 045import org.ametys.core.upload.Upload; 046import org.ametys.core.upload.UploadManager; 047import org.ametys.core.user.CurrentUserProvider; 048import org.ametys.core.user.UserIdentity; 049import org.ametys.core.util.JSONUtils; 050import org.ametys.plugins.linkdirectory.DirectoryHelper; 051import org.ametys.plugins.linkdirectory.Link; 052import org.ametys.plugins.linkdirectory.Link.LinkType; 053import org.ametys.web.repository.site.SiteManager; 054import org.ametys.web.url.UrlPreviewComponent; 055 056/** 057 * Adds a new user link in Ametys. 058 */ 059public class AddUserLinkAction extends ServiceableAction 060{ 061 private static int _ICO_REQUEST_TIMEOUT = 3000; 062 063 private static final List<String> __ICO_SUPPORTED_MIMETYPES = List.of("image/jpeg", "image/jpg", "image/png", "image/svg+xml"); 064 private static final List<String> __ICO_SUPPORTED_EXTENSIONS = List.of(".jpeg", ".jpg", ".png", ".svg"); 065 066 /** The DAO for {@link Link}s */ 067 protected LinkDAO _linkDAO; 068 /** The current user provider */ 069 protected CurrentUserProvider _currentUserProvider; 070 /** The directory helper */ 071 protected DirectoryHelper _directoryHelper; 072 /** The site manager */ 073 protected SiteManager _siteManager; 074 /** The url preview component */ 075 protected UrlPreviewComponent _urlPreviewComponent; 076 /** The upload manager */ 077 protected UploadManager _uploadManager; 078 /** The JSON utils */ 079 protected JSONUtils _jsonUtils; 080 081 @Override 082 public void service(ServiceManager smanager) throws ServiceException 083 { 084 super.service(smanager); 085 _currentUserProvider = (CurrentUserProvider) smanager.lookup(CurrentUserProvider.ROLE); 086 _linkDAO = (LinkDAO) smanager.lookup(LinkDAO.ROLE); 087 _directoryHelper = (DirectoryHelper) smanager.lookup(DirectoryHelper.ROLE); 088 _siteManager = (SiteManager) smanager.lookup(SiteManager.ROLE); 089 _urlPreviewComponent = (UrlPreviewComponent) smanager.lookup(UrlPreviewComponent.ROLE); 090 _uploadManager = (UploadManager) smanager.lookup(UploadManager.ROLE); 091 _jsonUtils = (JSONUtils) smanager.lookup(JSONUtils.ROLE); 092 } 093 094 @Override 095 public Map act(Redirector redirector, SourceResolver resolver, Map objectModel, String source, Parameters parameters) throws Exception 096 { 097 Request request = ObjectModelHelper.getRequest(objectModel); 098 Map<String, Object> result = new HashMap<>(); 099 100 UserIdentity user = _currentUserProvider.getUser(); 101 102 // Check user authenticated 103 if (user != null) 104 { 105 // Create link 106 Map<String, Object> createParameters = new HashMap<>(); 107 String siteName = request.getParameter("siteName"); 108 String lang = request.getParameter("lang"); 109 String url = request.getParameter("url"); 110 //The color key, default color will be used if it is not present or empty 111 String color = request.getParameter("color"); 112 113 createParameters.put("siteName", siteName); 114 createParameters.put("lang", lang); 115 createParameters.put("url-type", LinkType.URL.toString()); 116 createParameters.put("url", url); 117 createParameters.put("title", request.getParameter("title")); 118 createParameters.put("color", color); 119 120 String userAgent = request.getHeader("User-Agent"); 121 Upload upload = _getLinkFavicon(url, user, userAgent); 122 if (upload != null) 123 { 124 Map<String, Object> linkMap = new HashMap<>(); 125 linkMap.put("id", upload.getId()); 126 linkMap.put("type", "external"); 127 createParameters.put("picture", _jsonUtils.convertObjectToJson(linkMap)); 128 } 129 130 _handleTheme(request, createParameters, siteName, lang); 131 132 Map<String, Object> createdLinkresult = _linkDAO.createUserLink(createParameters); 133 134 if (createdLinkresult.containsKey("already-exists")) 135 { 136 result.put("error", "already-exists"); 137 } 138 else 139 { 140 result.put("error", "none"); 141 result.putAll(createdLinkresult); 142 } 143 } 144 else 145 { 146 result.put("error", "unauthenticated-user"); 147 } 148 149 Response response = ObjectModelHelper.getResponse(objectModel); 150 response.setHeader("Access-Control-Allow-Origin", request.getHeader("Origin")); 151 response.setHeader("Access-Control-Allow-Credentials", "true"); 152 153 request.setAttribute(JSonReader.OBJECT_TO_READ, result); 154 return EMPTY_MAP; 155 } 156 157 private void _handleTheme(Request request, Map<String, Object> createParameters, String siteName, String lang) 158 { 159 String themeName = request.getParameter("themes"); 160 161 List<String> themes = new ArrayList<>(); 162 // We have a theme name ... 163 if (StringUtils.isNotBlank(themeName)) 164 { 165 // ... retrieve JCR id from theme name 166 if (_directoryHelper.themeExists(themeName, siteName, lang)) 167 { 168 themes.add(themeName); 169 } 170 } 171 // if not, just send an empty list for the user link creation 172 createParameters.put("themes", themes); 173 } 174 175 /** 176 * Set the favicon of the link url 177 * @param url the url of the favicon 178 * @param user the user 179 * @param userAgent The user agent 180 * @return the favicon upload map 181 */ 182 protected Upload _getLinkFavicon(String url, UserIdentity user, String userAgent) 183 { 184 Upload upload = null; 185 try 186 { 187 String favIconLink = _urlPreviewComponent.getFavicon(url); 188 189 if (StringUtils.isBlank(favIconLink)) 190 { 191 return null; 192 } 193 194 URL favIconURL = new URL(favIconLink); 195 String favIconName = StringUtils.substringAfterLast(favIconURL.getPath(), "/"); 196 197 RequestConfig requestConfig = RequestConfig.custom() 198 .setConnectTimeout(_ICO_REQUEST_TIMEOUT) 199 .setConnectionRequestTimeout(_ICO_REQUEST_TIMEOUT) 200 .setSocketTimeout(_ICO_REQUEST_TIMEOUT) 201 .build(); 202 203 try (CloseableHttpClient httpclient = HttpClientBuilder.create() 204 .setDefaultRequestConfig(requestConfig) 205 .useSystemProperties() 206 .setUserAgent(userAgent) 207 .build()) 208 { 209 // Prepare a request object 210 HttpGet get = new HttpGet(favIconLink); 211 try (CloseableHttpResponse httpResponse = httpclient.execute(get)) 212 { 213 int statusCode = httpResponse.getStatusLine().getStatusCode(); 214 if (statusCode != 200) 215 { 216 getLogger().warn("Error " + statusCode + ". Can't set the favicon with url " + favIconLink + " to the link " + url); 217 return upload; 218 } 219 220 try (InputStream is = httpResponse.getEntity().getContent()) 221 { 222 HttpEntity entity = httpResponse.getEntity(); 223 String mimeType = ContentType.get(entity).getMimeType(); 224 if (mimeType.equals("image/x-icon") || mimeType.equals("image/vnd.microsoft.icon") || favIconName.endsWith(".ico")) 225 { 226 // If the image is .ico, convert it in .png 227 InputStream convertedIcoToPngIS = _urlPreviewComponent.convertIcoToPng(is); 228 upload = _uploadManager.storeUpload(user, StringUtils.replace(favIconName, ".ico", ".png"), convertedIcoToPngIS); 229 } 230 else if (_checkFavico(favIconName, mimeType)) 231 { 232 upload = _uploadManager.storeUpload(user, favIconName, is); 233 } 234 else 235 { 236 getLogger().warn("Unsupported mime-type " + mimeType + " for favicon from url " + favIconLink + " to the link " + url); 237 } 238 } 239 } 240 } 241 catch (IOException e) 242 { 243 getLogger().warn("Unable to download favicon from url " + favIconLink + " to the link " + url, e); 244 } 245 } 246 catch (Exception e) 247 { 248 getLogger().warn("Unable to get favicon from link url " + url, e); 249 } 250 251 return upload; 252 } 253 254 private boolean _checkFavico(String favicoName, String mimeType) 255 { 256 return __ICO_SUPPORTED_MIMETYPES.stream().anyMatch(m -> StringUtils.equalsIgnoreCase(m, mimeType)) 257 || __ICO_SUPPORTED_EXTENSIONS.stream().anyMatch(ext -> favicoName.toLowerCase().endsWith(ext)); 258 259 } 260}