1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 package org.codelibs.fess.app.web.admin.design;
17
18 import java.io.File;
19 import java.io.FileInputStream;
20 import java.io.UnsupportedEncodingException;
21 import java.net.URLDecoder;
22 import java.util.ArrayList;
23 import java.util.List;
24 import java.util.Locale;
25
26 import org.apache.commons.io.FileUtils;
27 import org.apache.logging.log4j.LogManager;
28 import org.apache.logging.log4j.Logger;
29 import org.codelibs.core.io.FileUtil;
30 import org.codelibs.core.io.ResourceUtil;
31 import org.codelibs.core.lang.StringUtil;
32 import org.codelibs.core.misc.Pair;
33 import org.codelibs.fess.Constants;
34 import org.codelibs.fess.annotation.Secured;
35 import org.codelibs.fess.app.web.base.FessAdminAction;
36 import org.codelibs.fess.exception.FessSystemException;
37 import org.codelibs.fess.util.ComponentUtil;
38 import org.dbflute.optional.OptionalEntity;
39 import org.lastaflute.web.Execute;
40 import org.lastaflute.web.response.HtmlResponse;
41 import org.lastaflute.web.response.StreamResponse;
42 import org.lastaflute.web.ruts.process.ActionRuntime;
43
44
45
46
47
48 public class AdminDesignAction extends FessAdminAction {
49
50 public static final String ROLE = "admin-design";
51
52 private static final Logger logger = LogManager.getLogger(AdminDesignAction.class);
53
54
55
56
57
58
59
60
61
62 @Override
63 protected void setupHtmlData(final ActionRuntime runtime) {
64 super.setupHtmlData(runtime);
65 runtime.registerData("fileNameItems", loadFileNameItems());
66 runtime.registerData("jspFileNameItems", loadJspFileNameItems());
67 runtime.registerData("helpLink", systemHelper.getHelpLink(fessConfig.getOnlineHelpNameDesign()));
68 }
69
70 @Override
71 protected String getActionRole() {
72 return ROLE;
73 }
74
75 private List<Pair<String, String>> loadJspFileNameItems() {
76 final List<Pair<String, String>> jspItems = new ArrayList<>();
77 for (final Pair<String, String> p : systemHelper.getDesignJspFileNames()) {
78 jspItems.add(new Pair<>(":" + p.getFirst(), "/" + p.getSecond()));
79 }
80 for (String key : ComponentUtil.getVirtualHostHelper().getVirtualHostPaths()) {
81 if (StringUtil.isBlank(key)) {
82 key = "/";
83 }
84 for (final Pair<String, String> p : systemHelper.getDesignJspFileNames()) {
85 jspItems.add(new Pair<>(key + ":" + p.getFirst(), key + "/" + p.getSecond()));
86 }
87 }
88 return jspItems;
89 }
90
91 private List<String> loadFileNameItems() {
92 final File baseDir = new File(getServletContext().getRealPath("/"));
93 final List<String> fileNameItems = new ArrayList<>();
94 final List<File> fileList = getAccessibleFileList(baseDir);
95 final int length = baseDir.getAbsolutePath().length();
96 for (final File file : fileList) {
97 fileNameItems.add(file.getAbsolutePath().substring(length));
98 }
99 return fileNameItems;
100 }
101
102
103
104
105 @Execute
106 @Secured({ ROLE, ROLE + VIEW })
107 public HtmlResponse index() {
108 saveToken();
109 return asHtml(path_AdminDesign_AdminDesignJsp).useForm(DesignForm.class);
110 }
111
112 @Execute
113 @Secured({ ROLE, ROLE + VIEW })
114 public HtmlResponse back() {
115 saveToken();
116 return asHtml(path_AdminDesign_AdminDesignJsp).useForm(DesignForm.class);
117 }
118
119 @Execute
120 @Secured({ ROLE })
121 public HtmlResponse upload(final UploadForm form) {
122 validate(form, messages -> {}, () -> asListHtml(form));
123 verifyToken(this::asListHtml);
124 final String uploadedFileName = form.designFile.getFileName();
125 String fileName = form.designFileName;
126 if (StringUtil.isBlank(fileName)) {
127 fileName = uploadedFileName;
128 try {
129 int pos = fileName.indexOf('/');
130 if (pos >= 0) {
131 fileName = fileName.substring(pos + 1);
132 }
133 pos = fileName.indexOf('\\');
134 if (pos >= 0) {
135 fileName = fileName.substring(pos + 1);
136 }
137 } catch (final Exception e) {
138 throwValidationError(messages -> messages.addErrorsDesignFileNameIsInvalid("designFile"), this::asListHtml);
139 }
140 }
141 if (StringUtil.isBlank(fileName)) {
142 throwValidationError(messages -> messages.addErrorsDesignFileNameIsNotFound("designFile"), this::asListHtml);
143 }
144
145 File uploadFile;
146
147 if (checkFileType(fileName, fessConfig.getSupportedUploadedMediaExtentionsAsArray())
148 && checkFileType(uploadedFileName, fessConfig.getSupportedUploadedMediaExtentionsAsArray())) {
149 uploadFile = new File(getServletContext().getRealPath("/images/" + fileName));
150 } else if (checkFileType(fileName, fessConfig.getSupportedUploadedCssExtentionsAsArray())
151 && checkFileType(uploadedFileName, fessConfig.getSupportedUploadedCssExtentionsAsArray())) {
152 uploadFile = new File(getServletContext().getRealPath("/css/" + fileName));
153 } else if (checkFileType(fileName, fessConfig.getSupportedUploadedJsExtentionsAsArray())
154 && checkFileType(uploadedFileName, fessConfig.getSupportedUploadedJsExtentionsAsArray())) {
155 uploadFile = new File(getServletContext().getRealPath("/js/" + fileName));
156 } else if (fessConfig.isSupportedUploadedFile(fileName) || fessConfig.isSupportedUploadedFile(uploadedFileName)) {
157 uploadFile = ResourceUtil.getResourceAsFileNoException(fileName);
158 if (uploadFile == null) {
159 throwValidationError(messages -> messages.addErrorsDesignFileNameIsNotFound("designFileName"), this::asListHtml);
160 return null;
161 }
162 } else {
163 throwValidationError(messages -> messages.addErrorsDesignFileIsUnsupportedType("designFileName"), this::asListHtml);
164 return null;
165 }
166
167 final File parentFile = uploadFile.getParentFile();
168 if (!parentFile.exists() && !parentFile.mkdirs()) {
169 logger.warn("Could not create {}", parentFile.getAbsolutePath());
170 }
171
172 try {
173 write(uploadFile.getAbsolutePath(), form.designFile.getFileData());
174 final String currentFileName = fileName;
175 saveInfo(messages -> messages.addSuccessUploadDesignFile(GLOBAL, currentFileName));
176 } catch (final Exception e) {
177 logger.error("Failed to write an image file: {}", fileName, e);
178 throwValidationError(messages -> messages.addErrorsFailedToWriteDesignImageFile(GLOBAL), this::asListHtml);
179 }
180 return redirect(getClass());
181 }
182
183 private boolean checkFileType(final String fileName, final String[] exts) {
184 if (fileName == null) {
185 return false;
186 }
187 final String lFileName = fileName.toLowerCase(Locale.ENGLISH);
188 for (final String ext : exts) {
189 if (lFileName.endsWith("." + ext)) {
190 return true;
191 }
192 }
193 return false;
194 }
195
196 @Execute
197 @Secured({ ROLE, ROLE + VIEW })
198 public StreamResponse download(final FileAccessForm form) {
199 final File file = getTargetFile(form.fileName).get();
200 if (file == null) {
201 throwValidationError(messages -> messages.addErrorsTargetFileDoesNotExist(GLOBAL, form.fileName), this::asListHtml);
202 return null;
203 }
204 validate(form, messages -> {}, this::asListHtml);
205 verifyTokenKeep(this::asListHtml);
206 return asStream(file.getName()).contentTypeOctetStream().stream(out -> {
207 try (FileInputStream fis = new FileInputStream(file)) {
208 out.write(fis);
209 }
210 });
211 }
212
213 @Execute
214 @Secured({ ROLE })
215 public HtmlResponse delete(final FileAccessForm form) {
216 getTargetFile(form.fileName).ifPresent(file -> {
217 if (!file.delete()) {
218 logger.error("Failed to delete {}", file.getAbsolutePath());
219 throwValidationError(messages -> messages.addErrorsFailedToDeleteFile(GLOBAL, form.fileName), this::asListHtml);
220 }
221 }).orElse(() -> {
222 throwValidationError(messages -> messages.addErrorsTargetFileDoesNotExist(GLOBAL, form.fileName), this::asListHtml);
223 });
224 saveInfo(messages -> messages.addSuccessDeleteFile(GLOBAL, form.fileName));
225 validate(form, messages -> {}, this::asListHtml);
226 verifyToken(this::asListHtml);
227 return redirect(getClass());
228 }
229
230
231
232
233 @Execute
234 @Secured({ ROLE })
235 public HtmlResponse edit(final EditForm form) {
236 final String jspType = "view";
237 final File jspFile = getJspFile(form.fileName, jspType);
238 try {
239 form.content = new String(FileUtil.readBytes(jspFile), Constants.UTF_8);
240 } catch (final UnsupportedEncodingException e) {
241 throw new FessSystemException("Invalid encoding", e);
242 }
243 saveToken();
244 return asEditHtml(form);
245 }
246
247 @Execute
248 @Secured({ ROLE })
249 public HtmlResponse editAsUseDefault(final EditForm form) {
250 final String jspType = "orig/view";
251 final File jspFile = getJspFile(form.fileName, jspType);
252 try {
253 form.content = new String(FileUtil.readBytes(jspFile), Constants.UTF_8);
254 } catch (final UnsupportedEncodingException e) {
255 throw new FessSystemException("Invalid encoding", e);
256 }
257 saveToken();
258 return asEditHtml(form);
259 }
260
261 @Execute
262 @Secured({ ROLE })
263 public HtmlResponse update(final EditForm form) {
264 final String jspType = "view";
265 final File jspFile = getJspFile(form.fileName, jspType);
266
267 if (form.content == null) {
268 form.content = StringUtil.EMPTY;
269 }
270
271 validate(form, messages -> {}, () -> asEditHtml(form));
272 verifyToken(() -> asEditHtml(form));
273 try {
274 write(jspFile.getAbsolutePath(), form.content.getBytes(Constants.UTF_8));
275 saveInfo(messages -> messages.addSuccessUpdateDesignJspFile(GLOBAL, jspFile.getAbsolutePath()));
276 } catch (final Exception e) {
277 logger.error("Failed to update {}", form.fileName, e);
278 throwValidationError(messages -> messages.addErrorsFailedToUpdateJspFile(GLOBAL), this::asListHtml);
279 }
280 return redirect(getClass());
281 }
282
283
284
285
286 private OptionalEntity<File> getTargetFile(final String fileName) {
287 final File baseDir = new File(getServletContext().getRealPath("/"));
288 final File targetFile = new File(getServletContext().getRealPath(fileName));
289 final List<File> fileList = getAccessibleFileList(baseDir);
290 for (final File file : fileList) {
291 if (targetFile.equals(file)) {
292 return OptionalEntity.of(targetFile);
293 }
294 }
295 return OptionalEntity.empty();
296 }
297
298 private List<File> getAccessibleFileList(final File baseDir) {
299 final List<File> fileList = new ArrayList<>(
300 FileUtils.listFiles(new File(baseDir, "images"), fessConfig.getSupportedUploadedMediaExtentionsAsArray(), true));
301 fileList.addAll(FileUtils.listFiles(new File(baseDir, "css"), fessConfig.getSupportedUploadedCssExtentionsAsArray(), true));
302 fileList.addAll(FileUtils.listFiles(new File(baseDir, "js"), fessConfig.getSupportedUploadedJsExtentionsAsArray(), true));
303 return fileList;
304 }
305
306 private File getJspFile(final String fileName, final String jspType) {
307 try {
308 final String[] values = URLDecoder.decode(fileName, Constants.UTF_8).split(":");
309 if (values.length != 2) {
310 throwValidationError(messages -> messages.addErrorsInvalidDesignJspFileName(GLOBAL), this::asListHtml);
311 }
312 final String jspFileName = systemHelper.getDesignJspFileName(values[1]);
313 if (jspFileName == null) {
314 throwValidationError(messages -> messages.addErrorsInvalidDesignJspFileName(GLOBAL), this::asListHtml);
315 }
316 String path;
317 if ("view".equals(jspType)) {
318 path = "/WEB-INF/" + jspType + values[0] + "/" + jspFileName;
319 } else {
320 path = "/WEB-INF/" + jspType + "/" + jspFileName;
321 }
322 final File jspFile = new File(getServletContext().getRealPath(path));
323 if (!jspFile.exists()) {
324 throwValidationError(messages -> messages.addErrorsDesignJspFileDoesNotExist(GLOBAL), this::asListHtml);
325 }
326 return jspFile;
327 } catch (final UnsupportedEncodingException e) {
328 throw new FessSystemException("Failed to decode " + fileName, e);
329 }
330 }
331
332
333
334
335
336 private HtmlResponse asListHtml() {
337 return asHtml(path_AdminDesign_AdminDesignJsp).useForm(DesignForm.class);
338 }
339
340 private HtmlResponse asListHtml(final UploadForm uploadForm) {
341 return asHtml(path_AdminDesign_AdminDesignJsp).useForm(DesignForm.class, setup -> {
342 setup.setup(form -> {
343 copyBeanToBean(uploadForm, form, op -> op.include("designFile", "designFileName"));
344 });
345 });
346 }
347
348 private HtmlResponse asEditHtml(final EditForm form) {
349 return asHtml(path_AdminDesign_AdminDesignEditJsp).renderWith(data -> {
350 data.register("displayFileName", getJspFile(form.fileName, "view").getAbsolutePath());
351 });
352 }
353 }