diff --git a/src/main/java/de/thm/arsnova/ImageUtils.java b/src/main/java/de/thm/arsnova/ImageUtils.java new file mode 100644 index 0000000000000000000000000000000000000000..0af116547c229c335ee88ca292528365c927cf7c --- /dev/null +++ b/src/main/java/de/thm/arsnova/ImageUtils.java @@ -0,0 +1,134 @@ +/* + * Copyright (C) 2014 THM webMedia + * + * This file is part of ARSnova. + * + * ARSnova is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ARSnova is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +package de.thm.arsnova; + +import java.awt.image.BufferedImage; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.net.MalformedURLException; +import java.net.URL; + +import javax.imageio.ImageIO; + +import org.apache.commons.codec.binary.Base64; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Util class for image operations. + * + * @author Daniel Vogel (daniel.vogel@mni.thm.de) + * + */ +public class ImageUtils { + public static final Logger LOGGER = LoggerFactory.getLogger(ImageUtils.class); + + /** + * Converts an image to an Base64 String. + * + * @param imageUrl The image url as a {@link String} + * @return The Base64 {@link String} of the image on success, otherwise <code>null</code>. + */ + public static String encodeImageToString(String imageUrl) { + + String[] urlParts = imageUrl.split("\\."); + StringBuilder result = new StringBuilder(); + + // get format + // + // The format is read dynamically. We have to take control + // in the frontend that no unsupported formats are transmitted! + if ( urlParts.length > 0 ) { + + String extension = urlParts[urlParts.length-1]; + + result.append("data:image/" + extension + ";base64,"); + result.append(Base64.encodeBase64String(convertFileToByteArray(imageUrl))); + + return result.toString(); + } + + return null; + } + + /** + * Gets the bytestream of an image url. + * s + * @param imageUrl The image url as a {@link String} + * @return The <code>byte[]</code> of the image on success, otherwise <code>null</code>. + */ + public static byte[] convertImageToByteArray(String imageUrl, String extension) { + + try { + URL url = new URL(imageUrl); + BufferedImage image = ImageIO.read(url); + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + + ImageIO.write(image, extension, baos); + + baos.flush(); + baos.close(); + return baos.toByteArray(); + + } catch (MalformedURLException e) { + LOGGER.error(e.getLocalizedMessage()); + } catch (IOException e) { + LOGGER.error(e.getLocalizedMessage()); + } + + return null; + } + + /** + * Gets the bytestream of an image url. + * s + * @param imageUrl The image url as a {@link String} + * @return The <code>byte[]</code> of the image on success, otherwise <code>null</code>. + */ + public static byte[] convertFileToByteArray(String imageUrl) { + + try { + URL url = new URL(imageUrl); + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + + InputStream is = url.openStream(); + byte[] byteChunk = new byte[4096]; // Or whatever size you want to read in at a time. + int n; + + while ( (n = is.read(byteChunk)) > 0 ) { + baos.write(byteChunk, 0, n); + } + + baos.flush(); + baos.close(); + + return baos.toByteArray(); + + } catch (MalformedURLException e) { + LOGGER.error(e.getLocalizedMessage()); + } catch (IOException e) { + LOGGER.error(e.getLocalizedMessage()); + } + + return null; + } +} + diff --git a/src/main/java/de/thm/arsnova/dao/CouchDBDao.java b/src/main/java/de/thm/arsnova/dao/CouchDBDao.java index fec9faff500a66c70c9f66b9458d512ff554607b..1e3915e9c3d0adec08d012b4351c10b5c648c695 100644 --- a/src/main/java/de/thm/arsnova/dao/CouchDBDao.java +++ b/src/main/java/de/thm/arsnova/dao/CouchDBDao.java @@ -288,6 +288,12 @@ public class CouchDBDao implements IDatabaseDao { q.put("showStatistic", question.isShowStatistic()); q.put("showAnswer", question.isShowAnswer()); q.put("abstention", question.isAbstention()); + q.put("image", question.getImage()); + q.put("gridSize", question.getGridSize()); + q.put("offsetX", question.getOffsetX()); + q.put("offsetY", question.getOffsetY()); + q.put("zoomLvl", question.getZoomLvl()); + return q; } @@ -305,6 +311,11 @@ public class CouchDBDao implements IDatabaseDao { q.put("showStatistic", question.isShowStatistic()); q.put("showAnswer", question.isShowAnswer()); q.put("abstention", question.isAbstention()); + q.put("image", question.getImage()); + q.put("gridSize", question.getGridSize()); + q.put("offsetX", question.getOffsetX()); + q.put("offsetY", question.getOffsetY()); + q.put("zoomLvl", question.getZoomLvl()); this.database.saveDocument(q); question.set_rev(q.getRev()); @@ -353,7 +364,8 @@ public class CouchDBDao implements IDatabaseDao { results.getJSONArray("rows").optJSONObject(0).optJSONObject("value"), Question.class ); - JSONArray possibleAnswers = results.getJSONArray("rows").optJSONObject(0).optJSONObject("value") + JSONArray possibleAnswers = new JSONArray(); + possibleAnswers = results.getJSONArray("rows").optJSONObject(0).optJSONObject("value") .getJSONArray("possibleAnswers"); Collection<PossibleAnswer> answers = JSONArray.toCollection( possibleAnswers, diff --git a/src/main/java/de/thm/arsnova/entities/Question.java b/src/main/java/de/thm/arsnova/entities/Question.java index 33c2e59a14ac3385e6f4e44cf695a80a3c0b8d47..8b2d36e30c75c655eec2f7fdc76b95bffe0060fe 100644 --- a/src/main/java/de/thm/arsnova/entities/Question.java +++ b/src/main/java/de/thm/arsnova/entities/Question.java @@ -44,6 +44,12 @@ public class Question { private String _id; private String _rev; + private String image; + private int gridSize; + private int offsetX; + private int offsetY; + private int zoomLvl; + public final String getType() { return type; } @@ -212,6 +218,46 @@ public class Question { this._rev = _rev; } + public String getImage() { + return image; + } + + public void setImage(String image) { + this.image = image; + } + + public int getGridSize() { + return gridSize; + } + + public void setGridSize(int gridSize) { + this.gridSize = gridSize; + } + + public int getOffsetX() { + return offsetX; + } + + public void setOffsetX(int offsetX) { + this.offsetX = offsetX; + } + + public int getOffsetY() { + return offsetY; + } + + public void setOffsetY(int offsetY) { + this.offsetY = offsetY; + } + + public int getZoomLvl() { + return zoomLvl; + } + + public void setZoomLvl(int zoomLvl) { + this.zoomLvl = zoomLvl; + } + @Override public final String toString() { return "Question type '" + this.type + "': " + this.subject + ";\n" + this.text + this.possibleAnswers; diff --git a/src/main/java/de/thm/arsnova/services/QuestionService.java b/src/main/java/de/thm/arsnova/services/QuestionService.java index 0467de78e4aa89783df70616385df70ea61b6aab..f91da75e999fb8f84ae0589072373c8325b2a9a1 100644 --- a/src/main/java/de/thm/arsnova/services/QuestionService.java +++ b/src/main/java/de/thm/arsnova/services/QuestionService.java @@ -24,9 +24,13 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; +import de.thm.arsnova.ImageUtils; import de.thm.arsnova.annotation.Authenticated; import de.thm.arsnova.dao.IDatabaseDao; import de.thm.arsnova.entities.Answer; @@ -35,6 +39,7 @@ import de.thm.arsnova.entities.InterposedReadingCount; import de.thm.arsnova.entities.Question; import de.thm.arsnova.entities.Session; import de.thm.arsnova.entities.User; +import de.thm.arsnova.exceptions.BadRequestException; import de.thm.arsnova.exceptions.ForbiddenException; import de.thm.arsnova.exceptions.NotFoundException; import de.thm.arsnova.exceptions.UnauthorizedException; @@ -51,6 +56,11 @@ public class QuestionService implements IQuestionService { @Autowired private ARSnovaSocketIOServer socketIoServer; + + @Value("${upload.filesize_b}") + private int uploadFileSizeByte; + + public static final Logger LOGGER = LoggerFactory.getLogger(QuestionService.class); public void setDatabaseDao(IDatabaseDao databaseDao) { this.databaseDao = databaseDao; @@ -86,6 +96,25 @@ public class QuestionService implements IQuestionService { } else if (question.getPiRound() < 1 || question.getPiRound() > 2) { question.setPiRound(1); } + + // convert imageurl to base64 if neccessary + if ("grid".equals(question.getQuestionType())) { + org.slf4j.Logger logger = LoggerFactory.getLogger(QuestionService.class); + if (question.getImage().startsWith("http")) { + String base64ImageString = ImageUtils.encodeImageToString(question.getImage()); + if (base64ImageString == null) { + throw new BadRequestException(); + } + question.setImage(base64ImageString); + } + + // base64 adds offset to filesize, formular taken from: http://en.wikipedia.org/wiki/Base64#MIME + int fileSize = (int)((question.getImage().length()-814)/1.37); + if ( fileSize > this.uploadFileSizeByte ) { + LOGGER.error("Could not save file. File is too large with "+ fileSize + " Byte."); + throw new BadRequestException(); + } + } Question result = this.databaseDao.saveQuestion(session, question); socketIoServer.reportLecturerQuestionAvailable(result.getSessionKeyword(), result.get_id()); diff --git a/src/main/webapp/WEB-INF/spring/spring-main.xml b/src/main/webapp/WEB-INF/spring/spring-main.xml index 76d70ca2348a8535c92050ca21ec19bc3c17e178..05ecef8dac199ed50f107ff83064fc8f9edff976 100644 --- a/src/main/webapp/WEB-INF/spring/spring-main.xml +++ b/src/main/webapp/WEB-INF/spring/spring-main.xml @@ -24,14 +24,14 @@ </list> </property> </bean> - + <import resource="spring-security.xml" /> <context:component-scan base-package="de.thm.arsnova.dao,de.thm.arsnova.services,de.thm.arsnova.events" /> <context:annotation-config /> <task:annotation-driven /> - + <aop:aspectj-autoproxy> <aop:include name="authorizationAdviser" /> <aop:include name="userSessionAspect" /> @@ -41,11 +41,11 @@ init-method="startServer" destroy-method="stopServer" scope="singleton" p:portNumber="${socketio.port}" p:hostIp="${socketio.ip}" p:useSSL="${security.ssl}" p:keystore="${security.keystore}" p:storepass="${security.storepass}" /> - + <bean id="authorizationAdviser" class="de.thm.arsnova.aop.AuthorizationAdviser"> <property name="userService" ref="userService" /> </bean> - + <bean id="userSessionAspect" class="de.thm.arsnova.aop.UserSessionAspect" /> <bean id="userService" scope="singleton" class="de.thm.arsnova.services.UserService" /> diff --git a/src/main/webapp/arsnova.properties.example b/src/main/webapp/arsnova.properties.example index 19734f43ca0f6db41eed5b36a6eff5543e035df3..678e41714db1ab3a418171b325721564d19f0b19 100644 --- a/src/main/webapp/arsnova.properties.example +++ b/src/main/webapp/arsnova.properties.example @@ -17,6 +17,9 @@ security.storepass=arsnova # minutes, after which the feedback is deleted feedback.cleanup=10 +# maximal filesize in bytes +upload.filesize_b=1048576 + couchdb.host=localhost couchdb.port=5984 couchdb.name=arsnova diff --git a/src/test/resources/arsnova.properties.example b/src/test/resources/arsnova.properties.example index f6b818d06c9a997d55c0026f526b61887ab6a94c..00c73a8d1f13c8447711d48b080be7cd480e78bb 100644 --- a/src/test/resources/arsnova.properties.example +++ b/src/test/resources/arsnova.properties.example @@ -21,9 +21,12 @@ couchdb.name=arsnova # minutes, after which the feedback is deleted feedback.cleanup=10 +# maximal filesize in bytes +upload.filesize_b=1048576 + socketio.ip=0.0.0.0 socketio.port=10443 connector.uri=http://localhost:8080/connector-service connector.username=test -connector.password=test \ No newline at end of file +connector.password=test