Skip to content

Commit fe9f2da

Browse files
release: v1.4.0 (#49)
* release: v1.4.0 * fix code scanner issues * fix: bundle file line endings * fix: restrict publish workflow to read * fix: enhance upload security with file validation and size limits * refactor: replace custom rate limiter with express-rate-limit --------- Co-authored-by: timothy-dynamsoft <[email protected]>
1 parent f2486a1 commit fe9f2da

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

41 files changed

+16121
-8772
lines changed

.github/workflows/publish.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
name: Publish Package
2+
permissions:
3+
contents: read
24

35
on:
46
release:

README.md

Lines changed: 239 additions & 96 deletions
Large diffs are not rendered by default.

dev-server/index.js

Lines changed: 67 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
1-
import formidable from "formidable";
1+
import cors from "cors";
22
import express from "express";
3+
import rateLimit from "express-rate-limit";
4+
import formidable from "formidable";
35
import fs from "fs";
46
import http from "http";
57
import https from "https";
6-
import cors from "cors";
7-
import path from "path";
88
import os from "os";
9+
import path from "path";
910
import { fileURLToPath } from "url";
1011

1112
const __filename = fileURLToPath(import.meta.url);
@@ -23,6 +24,15 @@ if (!fs.existsSync(distPath)) {
2324

2425
const app = express();
2526

27+
// Rate limiting
28+
const limiter = rateLimit({
29+
windowMs: 15 * 60 * 1000, // 15 minutes
30+
max: 100, // Limit each IP to 100 requests per windowMs
31+
message: "Too many requests from this IP, please try again later.",
32+
});
33+
34+
app.use(limiter);
35+
2636
app.use(
2737
cors({
2838
origin: (origin, callback) => {
@@ -36,6 +46,7 @@ app.use("/dist", express.static(distPath));
3646
app.use("/assets", express.static(path.join(__dirname, "../samples/demo/assets")));
3747
app.use("/css", express.static(path.join(__dirname, "../samples/demo/css")));
3848
app.use("/font", express.static(path.join(__dirname, "../samples/demo/font")));
49+
app.use("/samples", express.static(path.join(__dirname, "../samples")));
3950

4051
// Routes
4152
app.get("/", (req, res) => {
@@ -50,8 +61,16 @@ app.get("/hello-world", (req, res) => {
5061
res.sendFile(path.join(__dirname, "../samples/hello-world.html"));
5162
});
5263

53-
app.get("/scenarios/use-file-input", (req, res) => {
54-
res.sendFile(path.join(__dirname, "../samples/scenarios/use-file-input.html"));
64+
app.get("/multi-page-scanning", (req, res) => {
65+
res.sendFile(path.join(__dirname, "../samples/scenarios/multi-page-scanning.html"));
66+
});
67+
68+
app.get("/scanning-to-pdf", (req, res) => {
69+
res.sendFile(path.join(__dirname, "../samples/scenarios/scanning-to-pdf.html"));
70+
});
71+
72+
app.get("/image-file-scanning", (req, res) => {
73+
res.sendFile(path.join(__dirname, "../samples/scenarios/image-file-scanning.html"));
5574
});
5675

5776
// Allow upload feature
@@ -61,6 +80,8 @@ app.post("/upload", function (req, res) {
6180
const form = formidable({
6281
multiples: false,
6382
keepExtensions: true,
83+
maxFileSize: 25 * 1024 * 1024, // 25MB limit
84+
maxFiles: 1,
6485
});
6586

6687
form.parse(req, (err, fields, files) => {
@@ -74,12 +95,27 @@ app.post("/upload", function (req, res) {
7495
return res.status(400).json({ success: false, message: "No file uploaded" });
7596
}
7697

77-
// Get current timestamp
78-
let dt = new Date();
98+
// Sanitize filename to prevent path traversal
99+
const newFileName = path.basename(uploadedFile.originalFilename);
100+
101+
// Validate file extension (whitelist for document scanner)
102+
const allowedExtensions = ['.jpg', '.jpeg', '.png', '.pdf', '.bmp', '.tiff', '.tif'];
103+
const fileExt = path.extname(newFileName).toLowerCase();
104+
if (!allowedExtensions.includes(fileExt)) {
105+
return res.status(400).json({ success: false, message: "File type not allowed. Allowed types: jpg, jpeg, png, pdf, bmp, tiff" });
106+
}
107+
108+
// Sanitize filename to remove potentially dangerous characters
109+
const sanitizedName = newFileName.replace(/[^a-zA-Z0-9._-]/g, '_');
110+
111+
const fileSavePath = __dirname;
112+
const newFilePath = path.resolve(fileSavePath, sanitizedName);
79113

80-
const fileSavePath = path.join(__dirname, "\\");
81-
const newFileName = uploadedFile.originalFilename;
82-
const newFilePath = path.join(fileSavePath, newFileName);
114+
// Verify path is within allowed directory (robust check using relative path)
115+
const relativePath = path.relative(fileSavePath, newFilePath);
116+
if (relativePath.startsWith('..') || path.isAbsolute(relativePath)) {
117+
return res.status(400).json({ success: false, message: "Invalid filename - path traversal detected" });
118+
}
83119

84120
// Move the uploaded file to the desired directory
85121
fs.rename(uploadedFile.filepath, newFilePath, (err) => {
@@ -88,11 +124,11 @@ app.post("/upload", function (req, res) {
88124
return res.status(500).send("Error saving the file.");
89125
}
90126
console.log(`\x1b[33m ${newFileName} \x1b[0m uploaded successfully!`);
91-
});
92-
res.status(200).json({
93-
success: true,
94-
message: `${newFileName} uploaded successfully`,
95-
filename: newFileName,
127+
res.status(200).json({
128+
success: true,
129+
message: `${newFileName} uploaded successfully`,
130+
filename: newFileName,
131+
});
96132
});
97133
});
98134
} catch (error) {
@@ -151,7 +187,7 @@ httpsServer.on("error", (error) => {
151187
console.log(`2. Close any other applications using port ${httpsPort}`);
152188
console.log(`3. Wait a few moments and try again - the port might be in a cleanup state\n`);
153189
} else {
154-
console.error("\x1b[31mHTTP Server error:\x1b[0m", error);
190+
console.error("\x1b[31mHTTPS Server error:\x1b[0m", error);
155191
}
156192
process.exit(1);
157193
});
@@ -161,8 +197,12 @@ httpServer.listen(httpPort, () => {
161197
console.log("\n\x1b[1m Dynamsoft Document Scanner Samples\x1b[0m\n");
162198
console.log("\x1b[36m HTTP URLs:\x1b[0m");
163199
console.log("\x1b[90m-------------------\x1b[0m");
164-
console.log("\x1b[33m Hello World:\x1b[0m http://localhost:" + httpPort + "/hello-world");
165-
console.log("\x1b[33m Demo:\x1b[0m http://localhost:" + httpPort + "/demo");
200+
console.log("\x1b[1m\x1b[35m → Samples Index:\x1b[0m \x1b[1mhttp://localhost:" + httpPort + "/samples\x1b[0m");
201+
console.log("\x1b[33m Hello World:\x1b[0m http://localhost:" + httpPort + "/hello-world");
202+
console.log("\x1b[33m Scanning to PDF:\x1b[0m http://localhost:" + httpPort + "/scanning-to-pdf");
203+
console.log("\x1b[33m Demo:\x1b[0m http://localhost:" + httpPort + "/demo");
204+
console.log("\x1b[33m Multi-Page Scanning:\x1b[0m http://localhost:" + httpPort + "/multi-page-scanning");
205+
console.log("\x1b[33m Image File Scanning:\x1b[0m http://localhost:" + httpPort + "/image-file-scanning");
166206
});
167207

168208
httpsServer.listen(httpsPort, "0.0.0.0", () => {
@@ -179,9 +219,15 @@ httpsServer.listen(httpsPort, "0.0.0.0", () => {
179219
console.log("\n");
180220
console.log("\x1b[36m HTTPS URLs:\x1b[0m");
181221
console.log("\x1b[90m-------------------\x1b[0m");
182-
ipv4Addresses.forEach((localIP) => {
183-
console.log("\x1b[32m Hello World:\x1b[0m https://" + localIP + ":" + httpsPort + "/hello-world");
184-
console.log("\x1b[32m Demo:\x1b[0m https://" + localIP + ":" + httpsPort + "/demo");
222+
ipv4Addresses.forEach((localIP, index) => {
223+
if (index > 0) console.log(""); // Add spacing between different IPs
224+
console.log("\x1b[32m----IP[" + index + "]: " + localIP + "----\x1b");
225+
console.log("\x1b[1m\x1b[35m → Samples Index:\x1b[0m \x1b[1mhttps://" + localIP + ":" + httpsPort + "/samples\x1b[0m");
226+
console.log("\x1b[32m Hello World:\x1b[0m https://" + localIP + ":" + httpsPort + "/hello-world");
227+
console.log("\x1b[32m Scanning to PDF:\x1b[0m https://" + localIP + ":" + httpsPort + "/scanning-to-pdf");
228+
console.log("\x1b[32m Demo:\x1b[0m https://" + localIP + ":" + httpsPort + "/demo");
229+
console.log("\x1b[32m Multi-Page Scanning:\x1b[0m https://" + localIP + ":" + httpsPort + "/multi-page-scanning");
230+
console.log("\x1b[32m Image File Scanning:\x1b[0m https://" + localIP + ":" + httpsPort + "/image-file-scanning");
185231
});
186232
console.log("\n");
187233
console.log("\x1b[90mPress Ctrl+C to stop the server\x1b[0m\n");

dev-server/pem/cert.pem

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,12 @@
11
-----BEGIN CERTIFICATE-----
2-
MIICDDCCAXUCFGlprxUW7YsQSmqXwS3fjySQwexCMA0GCSqGSIb3DQEBCwUAMEUx
3-
CzAJBgNVBAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRl
4-
cm5ldCBXaWRnaXRzIFB0eSBMdGQwHhcNMjAwMTE3MDE0OTM0WhcNMjAwMjE2MDE0
5-
OTM0WjBFMQswCQYDVQQGEwJBVTETMBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UE
6-
CgwYSW50ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMIGfMA0GCSqGSIb3DQEBAQUAA4GN
7-
ADCBiQKBgQCt3L/syEyB8B9O8Xhf3/SJOfTsoSs+3+/ELvFd07QEP0mySRjh9hUL
8-
BjB1bWJXBshn9JBzlfGUjRtNkc54VF1JfjFgi7UzqqyAlAwfEMBbp8jUX1Hh9iU7
9-
ctTAHxcAicTWTkRmToXJBUhbgTH+eF/GfQTdnByrncprQfuqdPg2KwIDAQABMA0G
10-
CSqGSIb3DQEBCwUAA4GBAKRRbXBhTS95IimKoIZq3RtVrjXpcsBn5ncyvFULc6Y5
11-
OkOxum5TO++XHVOJyalqyWpAQuz6i348hxTW6wqt5Js0UPGLGIb4Kq965QKKT+yJ
12-
WnHOnzZzJxiTs/1uGFjPAKgdvuDhcx36YsvSQ/UnJvF0rttjLKOGI5SkFMgz1Ufz
2+
MIIBrjCCAVOgAwIBAgIUCp1iPS8I82nAYLY6urLJIebD6v0wCgYIKoZIzj0EAwIw
3+
RTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoMGElu
4+
dGVybmV0IFdpZGdpdHMgUHR5IEx0ZDAeFw0yNTEwMjAyMTMwMjFaFw0yNTExMTky
5+
MTMwMjFaMEUxCzAJBgNVBAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYD
6+
VQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQwWTATBgcqhkjOPQIBBggqhkjO
7+
PQMBBwNCAAQSioVTYrJS1oJNQhaj5gr6M63uppPpaB5biwHIRkBBL4b/q/SDVDjR
8+
/YP2Ed8RrU2WrFyGsh5yhDJAZSWWUHAHoyEwHzAdBgNVHQ4EFgQUNrng2xZm0ouA
9+
NfGi8k96E6Mhb1IwCgYIKoZIzj0EAwIDSQAwRgIhALmnSAtb4UbcyFkWRZOlnkpk
10+
9QcukssrIyzFNElPM9jPAiEA9uJRNWFE/elq/P8cBB+Avx6bj3mDoww7DMZxiaeS
11+
fZQ=
1312
-----END CERTIFICATE-----

dev-server/pem/csr.pem

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,8 @@
11
-----BEGIN CERTIFICATE REQUEST-----
2-
MIIBhDCB7gIBADBFMQswCQYDVQQGEwJBVTETMBEGA1UECAwKU29tZS1TdGF0ZTEh
3-
MB8GA1UECgwYSW50ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMIGfMA0GCSqGSIb3DQEB
4-
AQUAA4GNADCBiQKBgQCt3L/syEyB8B9O8Xhf3/SJOfTsoSs+3+/ELvFd07QEP0my
5-
SRjh9hULBjB1bWJXBshn9JBzlfGUjRtNkc54VF1JfjFgi7UzqqyAlAwfEMBbp8jU
6-
X1Hh9iU7ctTAHxcAicTWTkRmToXJBUhbgTH+eF/GfQTdnByrncprQfuqdPg2KwID
7-
AQABoAAwDQYJKoZIhvcNAQELBQADgYEAgwEY90gQQzxIonWEgDxGRBHxSk0h3UE4
8-
rTP3JggV6h0vXMndOrDXC2qrh20fJaWIHqbBtmfOF4NmPhQTSZOZ2fIjPBeHZqLq
9-
8+K9iZPeyjnVIRyWkXfCPacoddTw2FcykRobgL6Wi/RoldutOnIDlTawo5Y/eXvm
10-
JI0428mqYU4=
2+
MIIBADCBpwIBADBFMQswCQYDVQQGEwJBVTETMBEGA1UECAwKU29tZS1TdGF0ZTEh
3+
MB8GA1UECgwYSW50ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMFkwEwYHKoZIzj0CAQYI
4+
KoZIzj0DAQcDQgAEEoqFU2KyUtaCTUIWo+YK+jOt7qaT6WgeW4sByEZAQS+G/6v0
5+
g1Q40f2D9hHfEa1NlqxchrIecoQyQGUlllBwB6AAMAoGCCqGSM49BAMCA0gAMEUC
6+
IBUmLzT9IpVjQ4R5IYGakzwyXUjg2HI8E7jvfdcxpCFfAiEAnvHcm0pSnpIjESHc
7+
gfs4yMDzyJr0HJs1sH2MVr+X070=
118
-----END CERTIFICATE REQUEST-----

dev-server/pem/key.pem

Lines changed: 5 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,5 @@
1-
-----BEGIN RSA PRIVATE KEY-----
2-
MIICXAIBAAKBgQCt3L/syEyB8B9O8Xhf3/SJOfTsoSs+3+/ELvFd07QEP0mySRjh
3-
9hULBjB1bWJXBshn9JBzlfGUjRtNkc54VF1JfjFgi7UzqqyAlAwfEMBbp8jUX1Hh
4-
9iU7ctTAHxcAicTWTkRmToXJBUhbgTH+eF/GfQTdnByrncprQfuqdPg2KwIDAQAB
5-
AoGAO6O6zm2TGQuWoczhPvoi9yPDaZyLqiDFLaXws//YA5D2Jcs/VtvEMijoXI+u
6-
KS4xdr+FAbFQ0mVpFT3L9qjx6p/lSVKzJ1tlVlp7klJzK0VOWmMojLrhsstp44ah
7-
jZQdxcnlEDgeBwXj5m09fr7YFfIiyHef+r9ORqn00F7K+xkCQQDhy5k00dsfL5MY
8-
oy70Ikb70n90qktnFXrgsgEeojG0j0OmJUdNLV6gXbkD4lEeh6iK5XdAEuso+Qw1
9-
5Ksa3d11AkEAxR6yMXPIbl+4y24TbIGAZwb44Lyn9DAnLm5qgFvMgJARz+kqlYyr
10-
tpZ6cD1JY3fuF+umDlNPYzxGxy3kz/sxHwJBAJNiLDzYBmmSyjc4vPtKLH9PZTan
11-
udQtpylnx2dRg5RSN1wJ1ULBLJUM2Cl63mxJLHCNW4uNTcZO2fOLsUw2KckCQBFp
12-
dboSjSjawbsOfR6/jbUME53ebEOQoVVjoXq3IShWEYy4/u743w4g2q3hbAMiS+DH
13-
CwMG7uNIJsRfVG/es2cCQD7R6ebztt858vYZzfLMLMsJTF2YQs1YG91x76lZLhNp
14-
tcTTENHD4g9v/Q5MV+fhN0UuJ2ikrXULAgDmJMvAVyk=
15-
-----END RSA PRIVATE KEY-----
1+
-----BEGIN EC PRIVATE KEY-----
2+
MHcCAQEEILIGf02Od9J9c/16X11u7XOplP0lBfoLwmlf0QOnF1jroAoGCCqGSM49
3+
AwEHoUQDQgAEEoqFU2KyUtaCTUIWo+YK+jOt7qaT6WgeW4sByEZAQS+G/6v0g1Q4
4+
0f2D9hHfEa1NlqxchrIecoQyQGUlllBwBw==
5+
-----END EC PRIVATE KEY-----

0 commit comments

Comments
 (0)