001/* 002 * Copyright 2011 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.web.skin.actions; 017 018import java.io.File; 019import java.io.FileOutputStream; 020import java.io.IOException; 021import java.io.InputStream; 022import java.util.Enumeration; 023import java.util.HashMap; 024import java.util.LinkedHashMap; 025import java.util.Map; 026 027import org.apache.avalon.framework.context.Context; 028import org.apache.avalon.framework.context.ContextException; 029import org.apache.avalon.framework.context.Contextualizable; 030import org.apache.avalon.framework.parameters.Parameters; 031import org.apache.cocoon.Constants; 032import org.apache.cocoon.acting.ServiceableAction; 033import org.apache.cocoon.environment.ObjectModelHelper; 034import org.apache.cocoon.environment.Redirector; 035import org.apache.cocoon.environment.Request; 036import org.apache.cocoon.environment.SourceResolver; 037import org.apache.cocoon.servlet.multipart.Part; 038import org.apache.cocoon.servlet.multipart.PartOnDisk; 039import org.apache.cocoon.servlet.multipart.RejectedPart; 040import org.apache.commons.compress.archivers.zip.ZipArchiveEntry; 041import org.apache.commons.compress.archivers.zip.ZipFile; 042import org.apache.commons.io.FileUtils; 043import org.apache.commons.io.IOUtils; 044 045import org.ametys.core.cocoon.JSonReader; 046import org.ametys.runtime.util.AmetysHomeHelper; 047 048/** 049 * This action receive a form with the "importfile" zip file as an exported skin. 050 * Replace existing skin 051 */ 052public class UploadSkinAction extends ServiceableAction implements Contextualizable 053{ 054 /** The cocoon context */ 055 protected org.apache.cocoon.environment.Context _cocoonContext; 056 057 @Override 058 public void contextualize(Context context) throws ContextException 059 { 060 _cocoonContext = (org.apache.cocoon.environment.Context) context.get(Constants.CONTEXT_ENVIRONMENT_CONTEXT); 061 } 062 063 @Override 064 public Map act(Redirector redirector, SourceResolver resolver, Map objectModel, String source, Parameters parameters) throws Exception 065 { 066 Request request = ObjectModelHelper.getRequest(objectModel); 067 068 Map<String, Object> result = new LinkedHashMap<>(); 069 070 boolean isModel = parameters.getParameterAsBoolean("model", false); 071 try 072 { 073 // Checking file 074 Part part = (Part) request.get("importfile"); 075 if (part instanceof RejectedPart || part == null) 076 { 077 result.put("success", false); 078 result.put("error", "rejected"); 079 } 080 else 081 { 082 // Getting file 083 PartOnDisk uploadedFilePart = (PartOnDisk) part; 084 File uploadedFile = uploadedFilePart.getFile(); 085 086 String filename = (String) uploadedFilePart.getHeaders().get("filename"); 087 String skinName = filename.substring(0, filename.length() - 4); 088 089 if (getLogger().isDebugEnabled()) 090 { 091 getLogger().debug("Uploading skin " + skinName); 092 } 093 094 // Unzipping in tmp 095 File dir = _unzip(uploadedFile); 096 097 // Filter files 098 _filter(dir, dir.getAbsolutePath(), isModel); 099 100 // Test if the configuration model file is present. 101 File configFile = new File(dir, "stylesheets" + File.separator + "config" + File.separator + "config-model.xml"); 102 103 result.put("skinDir", dir.getName()); 104 result.put("tempDir", dir.getParentFile().getName()); 105 result.put("hasConfig", configFile.isFile()); 106 result.put("skinName", skinName); 107 result.put("success", "true"); 108 } 109 } 110 catch (Exception e) 111 { 112 getLogger().error("Unable to add skin", e); 113 result.put("success", false); 114 115 Map<String, String> ex = new HashMap<>(); 116 ex.put("message", e.getMessage()); 117 118 result.put("error", ex); 119 } 120 121 request.setAttribute(JSonReader.OBJECT_TO_READ, result); 122 123 return result; 124 } 125 126 /** 127 * Unzip file in temp directory 128 * @param uploadedFile the uploaded file 129 * @return the unzipped file 130 * @throws IOException if an I/O exception occurs 131 */ 132 protected File _unzip(File uploadedFile) throws IOException 133 { 134 File tmpFile = new File(AmetysHomeHelper.getAmetysHomeTmp(), Long.toString(Math.round(Math.random() * 1000000.0))); 135 136 getLogger().debug("Importing skin in tmp dir " + tmpFile.getAbsolutePath()); 137 ZipFile zipFile = new ZipFile(uploadedFile, "cp437"); 138 139 try 140 { 141 Enumeration<ZipArchiveEntry> entries = zipFile.getEntries(); 142 while (entries.hasMoreElements()) 143 { 144 ZipArchiveEntry zipEntry = entries.nextElement(); 145 146 String zipName = zipEntry.getName(); 147 148 if (zipEntry.isDirectory()) 149 { 150 File newDir = new File(tmpFile, zipName); 151 newDir.mkdirs(); 152 } 153 else 154 { 155 try (InputStream is = zipFile.getInputStream(zipEntry)) 156 { 157 File newFile = new File(tmpFile, zipName); 158 newFile.getParentFile().mkdirs(); 159 160 try (FileOutputStream fos = new FileOutputStream(newFile)) 161 { 162 IOUtils.copy(is, fos); 163 } 164 } 165 } 166 } 167 } 168 finally 169 { 170 ZipFile.closeQuietly(zipFile); 171 } 172 173 File[] files = tmpFile.listFiles(); 174 if (files.length != 1 || !files[0].isDirectory()) 175 { 176 throw new IllegalArgumentException("An imported skin or model must be a zip with a single root directory"); 177 } 178 179 return files[0]; 180 } 181 182 /** 183 * Filter skin directory 184 * @param skinDir the skin directory 185 * @param absoluteSkinPath the absolute path of tmp skin directory 186 * @param isModel true if the uploaded skin is the model of skin. 187 * @throws IOException if an error occurred 188 */ 189 protected void _filter(File skinDir, String absoluteSkinPath, boolean isModel) throws IOException 190 { 191 File[] files = skinDir.listFiles(); 192 for (File file : files) 193 { 194 if (file.isDirectory()) 195 { 196 if ("CVS".equals(file.getName()) 197 || ".svn".equals(file.getName()) 198 || !isModel && "model".equals(file.getName())) 199 { 200 FileUtils.deleteQuietly(file); 201 } 202 else 203 { 204 _filter(file, absoluteSkinPath, isModel); 205 } 206 } 207 } 208 } 209}