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