|
|
@@ -14,13 +14,14 @@ import java.io.ByteArrayInputStream;
|
|
|
import java.util.Iterator;
|
|
|
import java.util.UUID;
|
|
|
|
|
|
-/** 身份附件不进入公开 profile 映射;调用方必须先完成身份/权限校验。 */
|
|
|
+/** 新材料复用商品公共上传路径;旧私有附件保留鉴权读取,申请操作仍校验权限。 */
|
|
|
@Service
|
|
|
public class StallQualificationFileService {
|
|
|
private static final int MAX_BYTES = 5 * 1024 * 1024;
|
|
|
private final Path root;
|
|
|
+ private final Path publicRoot;
|
|
|
public StallQualificationFileService(@Value("${ruoyi.profile}") String profile) {
|
|
|
- Path publicRoot = Paths.get(profile).toAbsolutePath().normalize();
|
|
|
+ publicRoot = Paths.get(profile).toAbsolutePath().normalize();
|
|
|
root = publicRoot.resolveSibling("stall-qualification");
|
|
|
if (root.startsWith(publicRoot)) { throw new IllegalArgumentException("Private qualification directory overlaps profile"); }
|
|
|
}
|
|
|
@@ -52,7 +53,37 @@ public class StallQualificationFileService {
|
|
|
} catch (ServiceException exception) { throw exception; }
|
|
|
catch (Exception exception) { throw new ServiceException(MessageUtils.message("stall.qualification.file.failed")); }
|
|
|
}
|
|
|
- public void requireReference(Long storeId, String reference) { checkedPath(storeId, reference); }
|
|
|
+ public void requireReference(Long storeId, String reference) {
|
|
|
+ // 用户已确认材料按商品图片公开访问;只接受本系统实际上传的图片路径,不接收外部 URL。
|
|
|
+ if (reference != null && reference.startsWith("/profile/upload/")) {
|
|
|
+ requirePublicImage(storeId, reference);
|
|
|
+ } else {
|
|
|
+ checkedPath(storeId, reference);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ private void requirePublicImage(Long storeId, String reference) {
|
|
|
+ if (storeId == null || storeId <= 0 || !reference.matches("/profile/upload/\\d{4}/\\d{2}/\\d{2}/[^/\\\\%?#\\p{Cntrl}]+\\.(?i:jpg|jpeg|png)")) { throw invalid(); }
|
|
|
+ Path path = publicRoot.resolve(reference.substring("/profile/".length())).normalize();
|
|
|
+ try {
|
|
|
+ Path uploadRoot = publicRoot.resolve("upload");
|
|
|
+ // 公共目录也必须防止目录穿越/软链接逃逸,并继续限制材料大小和真实图片格式。
|
|
|
+ if (!path.startsWith(uploadRoot) || !Files.isRegularFile(path, LinkOption.NOFOLLOW_LINKS)
|
|
|
+ || !path.toRealPath().startsWith(uploadRoot.toRealPath()) || Files.size(path) > MAX_BYTES) { throw invalid(); }
|
|
|
+ try (ImageInputStream input = ImageIO.createImageInputStream(path.toFile())) {
|
|
|
+ Iterator<ImageReader> readers = ImageIO.getImageReaders(input);
|
|
|
+ if (!readers.hasNext()) { throw invalid(); }
|
|
|
+ ImageReader reader = readers.next();
|
|
|
+ try {
|
|
|
+ reader.setInput(input);
|
|
|
+ String format = reader.getFormatName().toLowerCase(java.util.Locale.ROOT);
|
|
|
+ if (!("jpeg".equals(format) || "jpg".equals(format) || "png".equals(format))
|
|
|
+ || (long) reader.getWidth(0) * reader.getHeight(0) > 20_000_000L) { throw invalid(); }
|
|
|
+ reader.read(0);
|
|
|
+ } finally { reader.dispose(); }
|
|
|
+ }
|
|
|
+ } catch (ServiceException exception) { throw exception; }
|
|
|
+ catch (Exception exception) { throw invalid(); }
|
|
|
+ }
|
|
|
public ByteArrayResource read(Long storeId, String reference) {
|
|
|
Path path = checkedPath(storeId, reference);
|
|
|
try { return new ByteArrayResource(Files.readAllBytes(path)); }
|