Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

1077 Set 4MB size limit for images #1605

Open
wants to merge 10 commits into
base: main
Choose a base branch
from
4 changes: 3 additions & 1 deletion src/main/java/ai/elimu/util/ImageHelper.java
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,9 @@
public class ImageHelper {

public static final int MINIMUM_WIDTH = 640;


public static final int MAX_BYTE_SIZE = 4194304; // 4MB

private static Logger logger = LogManager.getLogger();

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
package ai.elimu.web.content.multimedia.image;

import ai.elimu.model.content.multimedia.Image;
import ai.elimu.model.v2.enums.content.ImageFormat;
import ai.elimu.util.ImageHelper;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.stereotype.Component;
import org.springframework.validation.BindingResult;
import org.springframework.web.multipart.MultipartFile;

import java.io.IOException;
import java.util.Arrays;

import static ai.elimu.util.ImageHelper.MAX_BYTE_SIZE;

@Component
public class ImageComponent {

private final Logger logger = LogManager.getLogger();

public void validImageTypeAndSize(MultipartFile multipartFile, BindingResult result, Image image) {
try {
byte[] bytes = multipartFile.getBytes();

Check warning on line 24 in src/main/java/ai/elimu/web/content/multimedia/image/ImageComponent.java

View check run for this annotation

Codecov / codecov/patch

src/main/java/ai/elimu/web/content/multimedia/image/ImageComponent.java#L24

Added line #L24 was not covered by tests
if (multipartFile.isEmpty() || (bytes == null) || (bytes.length == 0)) {
result.rejectValue("bytes", "NotNull");

Check warning on line 26 in src/main/java/ai/elimu/web/content/multimedia/image/ImageComponent.java

View check run for this annotation

Codecov / codecov/patch

src/main/java/ai/elimu/web/content/multimedia/image/ImageComponent.java#L26

Added line #L26 was not covered by tests
} else {
String originalFileName = multipartFile.getOriginalFilename();
logger.info("originalFileName: " + originalFileName);

Check warning on line 29 in src/main/java/ai/elimu/web/content/multimedia/image/ImageComponent.java

View check run for this annotation

Codecov / codecov/patch

src/main/java/ai/elimu/web/content/multimedia/image/ImageComponent.java#L28-L29

Added lines #L28 - L29 were not covered by tests

byte[] headerBytes = Arrays.copyOfRange(bytes, 0, 6);
byte[] gifHeader87a = {71, 73, 70, 56, 55, 97}; // "GIF87a"
byte[] gifHeader89a = {71, 73, 70, 56, 57, 97}; // "GIF89a"

Check warning on line 33 in src/main/java/ai/elimu/web/content/multimedia/image/ImageComponent.java

View check run for this annotation

Codecov / codecov/patch

src/main/java/ai/elimu/web/content/multimedia/image/ImageComponent.java#L31-L33

Added lines #L31 - L33 were not covered by tests
if (Arrays.equals(gifHeader87a, headerBytes) || Arrays.equals(gifHeader89a, headerBytes)) {
image.setImageFormat(ImageFormat.GIF);

Check warning on line 35 in src/main/java/ai/elimu/web/content/multimedia/image/ImageComponent.java

View check run for this annotation

Codecov / codecov/patch

src/main/java/ai/elimu/web/content/multimedia/image/ImageComponent.java#L35

Added line #L35 was not covered by tests
} else if (originalFileName.toLowerCase().endsWith(".png")) {
image.setImageFormat(ImageFormat.PNG);

Check warning on line 37 in src/main/java/ai/elimu/web/content/multimedia/image/ImageComponent.java

View check run for this annotation

Codecov / codecov/patch

src/main/java/ai/elimu/web/content/multimedia/image/ImageComponent.java#L37

Added line #L37 was not covered by tests
} else if (originalFileName.toLowerCase().endsWith(".jpg") || originalFileName.toLowerCase().endsWith(".jpeg")) {
image.setImageFormat(ImageFormat.JPG);

Check warning on line 39 in src/main/java/ai/elimu/web/content/multimedia/image/ImageComponent.java

View check run for this annotation

Codecov / codecov/patch

src/main/java/ai/elimu/web/content/multimedia/image/ImageComponent.java#L39

Added line #L39 was not covered by tests
} else if (originalFileName.toLowerCase().endsWith(".gif")) {
image.setImageFormat(ImageFormat.GIF);

Check warning on line 41 in src/main/java/ai/elimu/web/content/multimedia/image/ImageComponent.java

View check run for this annotation

Codecov / codecov/patch

src/main/java/ai/elimu/web/content/multimedia/image/ImageComponent.java#L41

Added line #L41 was not covered by tests
} else {
result.rejectValue("bytes", "typeMismatch");

Check warning on line 43 in src/main/java/ai/elimu/web/content/multimedia/image/ImageComponent.java

View check run for this annotation

Codecov / codecov/patch

src/main/java/ai/elimu/web/content/multimedia/image/ImageComponent.java#L43

Added line #L43 was not covered by tests
}

if (image.getImageFormat() != null) {
String contentType = multipartFile.getContentType();
logger.info("contentType: " + contentType);
image.setContentType(contentType);

Check warning on line 49 in src/main/java/ai/elimu/web/content/multimedia/image/ImageComponent.java

View check run for this annotation

Codecov / codecov/patch

src/main/java/ai/elimu/web/content/multimedia/image/ImageComponent.java#L47-L49

Added lines #L47 - L49 were not covered by tests

image.setBytes(bytes);

Check warning on line 51 in src/main/java/ai/elimu/web/content/multimedia/image/ImageComponent.java

View check run for this annotation

Codecov / codecov/patch

src/main/java/ai/elimu/web/content/multimedia/image/ImageComponent.java#L51

Added line #L51 was not covered by tests

if (image.getImageFormat() != ImageFormat.GIF) {
int width = ImageHelper.getWidth(bytes);
logger.info("width: " + width + "px");

Check warning on line 55 in src/main/java/ai/elimu/web/content/multimedia/image/ImageComponent.java

View check run for this annotation

Codecov / codecov/patch

src/main/java/ai/elimu/web/content/multimedia/image/ImageComponent.java#L54-L55

Added lines #L54 - L55 were not covered by tests

if (width < ImageHelper.MINIMUM_WIDTH) {
result.rejectValue("bytes", "image.too.small");
image.setBytes(null);

Check warning on line 59 in src/main/java/ai/elimu/web/content/multimedia/image/ImageComponent.java

View check run for this annotation

Codecov / codecov/patch

src/main/java/ai/elimu/web/content/multimedia/image/ImageComponent.java#L58-L59

Added lines #L58 - L59 were not covered by tests
} else if (width > ImageHelper.MINIMUM_WIDTH){
bytes = ImageHelper.scaleImage(bytes, ImageHelper.MINIMUM_WIDTH);
image.setBytes(bytes);

Check warning on line 62 in src/main/java/ai/elimu/web/content/multimedia/image/ImageComponent.java

View check run for this annotation

Codecov / codecov/patch

src/main/java/ai/elimu/web/content/multimedia/image/ImageComponent.java#L61-L62

Added lines #L61 - L62 were not covered by tests
}
}
}

if (bytes.length > MAX_BYTE_SIZE) {
result.rejectValue("bytes", "file.size.too.big");

Check warning on line 68 in src/main/java/ai/elimu/web/content/multimedia/image/ImageComponent.java

View check run for this annotation

Codecov / codecov/patch

src/main/java/ai/elimu/web/content/multimedia/image/ImageComponent.java#L68

Added line #L68 was not covered by tests
}
}
} catch (IOException e) {
logger.error(e);
}
}

Check warning on line 74 in src/main/java/ai/elimu/web/content/multimedia/image/ImageComponent.java

View check run for this annotation

Codecov / codecov/patch

src/main/java/ai/elimu/web/content/multimedia/image/ImageComponent.java#L71-L74

Added lines #L71 - L74 were not covered by tests

}
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package ai.elimu.web.content.multimedia.image;

import ai.elimu.dao.ImageContributionEventDao;
import java.io.IOException;

import java.util.Calendar;
import java.util.HashSet;
import java.util.Set;
Expand All @@ -18,14 +18,12 @@
import ai.elimu.model.contributor.ImageContributionEvent;
import ai.elimu.model.enums.ContentLicense;
import ai.elimu.model.enums.Platform;
import ai.elimu.model.v2.enums.content.ImageFormat;
import ai.elimu.model.v2.enums.content.LiteracySkill;
import ai.elimu.model.v2.enums.content.NumeracySkill;
import ai.elimu.util.DiscordHelper;
import ai.elimu.util.ImageColorHelper;
import ai.elimu.util.ImageHelper;
import ai.elimu.web.context.EnvironmentContextLoaderListener;
import java.util.Arrays;

import javax.servlet.http.HttpSession;
import org.apache.logging.log4j.LogManager;
import org.springframework.beans.factory.annotation.Autowired;
Expand Down Expand Up @@ -55,6 +53,9 @@
@Autowired
private WordDao wordDao;

@Autowired
private ImageComponent imageComponent;

@RequestMapping(method = RequestMethod.GET)
public String handleRequest(Model model) {
logger.info("handleRequest");
Expand Down Expand Up @@ -87,57 +88,9 @@
result.rejectValue("title", "NonUnique");
}
}

try {
byte[] bytes = multipartFile.getBytes();
if (multipartFile.isEmpty() || (bytes == null) || (bytes.length == 0)) {
result.rejectValue("bytes", "NotNull");
} else {
String originalFileName = multipartFile.getOriginalFilename();
logger.info("originalFileName: " + originalFileName);

byte[] headerBytes = Arrays.copyOfRange(bytes, 0, 6);
byte[] gifHeader87a = {71, 73, 70, 56, 55, 97}; // "GIF87a"
byte[] gifHeader89a = {71, 73, 70, 56, 57, 97}; // "GIF89a"
if (Arrays.equals(gifHeader87a, headerBytes) || Arrays.equals(gifHeader89a, headerBytes)) {
image.setImageFormat(ImageFormat.GIF);
} else if (originalFileName.toLowerCase().endsWith(".png")) {
image.setImageFormat(ImageFormat.PNG);
} else if (originalFileName.toLowerCase().endsWith(".jpg") || originalFileName.toLowerCase().endsWith(".jpeg")) {
image.setImageFormat(ImageFormat.JPG);
} else if (originalFileName.toLowerCase().endsWith(".gif")) {
image.setImageFormat(ImageFormat.GIF);
} else {
result.rejectValue("bytes", "typeMismatch");
}

if (image.getImageFormat() != null) {
String contentType = multipartFile.getContentType();
logger.info("contentType: " + contentType);
image.setContentType(contentType);
imageComponent.validImageTypeAndSize(multipartFile, result, image);

Check warning on line 92 in src/main/java/ai/elimu/web/content/multimedia/image/ImageCreateController.java

View check run for this annotation

Codecov / codecov/patch

src/main/java/ai/elimu/web/content/multimedia/image/ImageCreateController.java#L92

Added line #L92 was not covered by tests

image.setBytes(bytes);

if (image.getImageFormat() != ImageFormat.GIF) {
int width = ImageHelper.getWidth(bytes);
logger.info("width: " + width + "px");

if (width < ImageHelper.MINIMUM_WIDTH) {
result.rejectValue("bytes", "image.too.small");
image.setBytes(null);
} else {
if (width > ImageHelper.MINIMUM_WIDTH) {
bytes = ImageHelper.scaleImage(bytes, ImageHelper.MINIMUM_WIDTH);
image.setBytes(bytes);
}
}
}
}
}
} catch (IOException e) {
logger.error(e);
}

if (result.hasErrors()) {
model.addAttribute("contentLicenses", ContentLicense.values());
model.addAttribute("literacySkills", LiteracySkill.values());
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package ai.elimu.web.content.multimedia.image;

import java.io.IOException;
import java.util.Calendar;
import java.util.Iterator;
import java.util.Set;
Expand All @@ -26,13 +25,11 @@
import ai.elimu.model.contributor.ImageContributionEvent;
import ai.elimu.model.enums.ContentLicense;
import ai.elimu.model.enums.Platform;
import ai.elimu.model.v2.enums.content.ImageFormat;
import ai.elimu.model.v2.enums.content.LiteracySkill;
import ai.elimu.model.v2.enums.content.NumeracySkill;
import ai.elimu.util.DiscordHelper;
import ai.elimu.util.ImageHelper;
import ai.elimu.web.context.EnvironmentContextLoaderListener;
import java.util.Arrays;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
Expand Down Expand Up @@ -79,6 +76,9 @@
@Autowired
private AudioDao audioDao;

@Autowired
private ImageComponent imageComponent;

@RequestMapping(value = "/{id}", method = RequestMethod.GET)
public String handleRequest(
Model model,
Expand Down Expand Up @@ -123,57 +123,9 @@
result.rejectValue("title", "NonUnique");
}
}

try {
byte[] bytes = multipartFile.getBytes();
if (multipartFile.isEmpty() || (bytes == null) || (bytes.length == 0)) {
result.rejectValue("bytes", "NotNull");
} else {
String originalFileName = multipartFile.getOriginalFilename();
logger.info("originalFileName: " + originalFileName);

byte[] headerBytes = Arrays.copyOfRange(bytes, 0, 6);
byte[] gifHeader87a = {71, 73, 70, 56, 55, 97}; // "GIF87a"
byte[] gifHeader89a = {71, 73, 70, 56, 57, 97}; // "GIF89a"
if (Arrays.equals(gifHeader87a, headerBytes) || Arrays.equals(gifHeader89a, headerBytes)) {
image.setImageFormat(ImageFormat.GIF);
} else if (originalFileName.toLowerCase().endsWith(".png")) {
image.setImageFormat(ImageFormat.PNG);
} else if (originalFileName.toLowerCase().endsWith(".jpg") || originalFileName.toLowerCase().endsWith(".jpeg")) {
image.setImageFormat(ImageFormat.JPG);
} else if (originalFileName.toLowerCase().endsWith(".gif")) {
image.setImageFormat(ImageFormat.GIF);
} else {
result.rejectValue("bytes", "typeMismatch");
}

if (image.getImageFormat() != null) {
String contentType = multipartFile.getContentType();
logger.info("contentType: " + contentType);
image.setContentType(contentType);

image.setBytes(bytes);
imageComponent.validImageTypeAndSize(multipartFile, result, image);

Check warning on line 127 in src/main/java/ai/elimu/web/content/multimedia/image/ImageEditController.java

View check run for this annotation

Codecov / codecov/patch

src/main/java/ai/elimu/web/content/multimedia/image/ImageEditController.java#L127

Added line #L127 was not covered by tests

if (image.getImageFormat() != ImageFormat.GIF) {
int width = ImageHelper.getWidth(bytes);
logger.info("width: " + width + "px");

if (width < ImageHelper.MINIMUM_WIDTH) {
result.rejectValue("bytes", "image.too.small");
image.setBytes(null);
} else {
if (width > ImageHelper.MINIMUM_WIDTH) {
bytes = ImageHelper.scaleImage(bytes, ImageHelper.MINIMUM_WIDTH);
image.setBytes(bytes);
}
}
}
}
}
} catch (IOException e) {
logger.error(e);
}

if (result.hasErrors()) {
model.addAttribute("image", image);
model.addAttribute("contentLicenses", ContentLicense.values());
Expand Down
1 change: 1 addition & 0 deletions src/main/webapp/WEB-INF/i18n/errors_en.properties
Original file line number Diff line number Diff line change
Expand Up @@ -30,5 +30,6 @@ typeMismatch.thumbnail=Valid thumbnail format: PNG
typeMismatch.java.lang.Integer={0} must be a number
formatHint.Integer=Number (e.g. "5000")
image.too.small=The image width must be at least 640px
file.size.too.big=The file size is too big (max 4MB)
emoji.unicode.version=Only emojis up to Unicode version 9
WordSpace=Spaces are not allowed
Loading