├── .dict ├── resources ├── .htaccess ├── favicon.ico ├── ext │ ├── favicon.png │ ├── 3p │ │ └── IBM-Plex-Mono.ttf │ ├── js │ │ ├── abuse.js │ │ ├── keyboard.js │ │ ├── nonprinting.js │ │ ├── admin.js │ │ ├── types.js │ │ ├── bulk.js │ │ ├── type.js │ │ ├── session.js │ │ ├── limits.js │ │ └── decode.js │ └── css │ │ └── multi.css ├── robots.txt ├── multi.html ├── abuse.html ├── nonprinting.html ├── license.html ├── types.html ├── admin.html ├── support.html ├── type.html └── limits.html ├── src ├── main │ ├── resources │ │ ├── app.version │ │ └── strings │ │ │ └── codes.en_US.properties │ └── java │ │ └── org │ │ └── barcodeapi │ │ ├── server │ │ ├── core │ │ │ ├── RateLimitException.java │ │ │ ├── BackgroundTask.java │ │ │ ├── GenerationException.java │ │ │ ├── TypeSelector.java │ │ │ ├── GeneratorPoolController.java │ │ │ ├── CodeTypes.java │ │ │ └── CodeGenerators.java │ │ ├── gen │ │ │ ├── types │ │ │ │ ├── Ean8Generator.java │ │ │ │ ├── UPCAGenerator.java │ │ │ │ ├── UPCEGenerator.java │ │ │ │ ├── Ean13Generator.java │ │ │ │ ├── ITF14Generator.java │ │ │ │ ├── Code39Generator.java │ │ │ │ ├── PDF417Generator.java │ │ │ │ ├── CodabarGenerator.java │ │ │ │ ├── Code128Generator.java │ │ │ │ ├── RoyalMailGenerator.java │ │ │ │ ├── USPSMailGenerator.java │ │ │ │ ├── AztecGenerator.java │ │ │ │ ├── QRCodeGenerator.java │ │ │ │ ├── DataMatrixGenerator.java │ │ │ │ └── AprilTagGenerator.java │ │ │ ├── CodeGenerator.java │ │ │ ├── impl │ │ │ │ ├── DefaultZXingProvider.java │ │ │ │ └── DefaultBarcode4JProvider.java │ │ │ └── BarcodeCanvasProvider.java │ │ ├── api │ │ │ ├── LimiterHandler.java │ │ │ ├── PlansHandler.java │ │ │ ├── InfoHandler.java │ │ │ ├── SessionHandler.java │ │ │ ├── TypeHandler.java │ │ │ └── StaticHandler.java │ │ ├── tasks │ │ │ ├── ShareCleanupTask.java │ │ │ ├── LimiterCleanupTask.java │ │ │ ├── SessionCleanupTask.java │ │ │ ├── BarcodeCleanupTask.java │ │ │ ├── LimiterMintingTask.java │ │ │ └── StatsDumpTask.java │ │ ├── admin │ │ │ ├── SubscriberReloadHandler.java │ │ │ ├── SessionFlushHandler.java │ │ │ ├── ServerStatsHandler.java │ │ │ ├── ShareListHandler.java │ │ │ ├── SessionListHandler.java │ │ │ ├── CacheFlushHandler.java │ │ │ ├── LimiterListHandler.java │ │ │ ├── LimiterFlushHandler.java │ │ │ └── CacheDumpHandler.java │ │ └── cache │ │ │ ├── LimiterCache.java │ │ │ ├── CachedShare.java │ │ │ ├── Subscriber.java │ │ │ ├── CachedSession.java │ │ │ └── CachedBarcode.java │ │ ├── core │ │ ├── utils │ │ │ └── AuthUtils.java │ │ ├── Config.java │ │ └── ServerRuntime.java │ │ └── Launcher.java └── test │ └── java │ └── org │ └── barcodeapi │ └── test │ ├── gen │ ├── types │ │ ├── TestAprilTag.java │ │ ├── TestEan8.java │ │ ├── TestUPCE.java │ │ ├── TestUPCA.java │ │ └── TestEan13.java │ ├── TestBlacklist.java │ ├── TestSpecial.java │ ├── TestOutputFormat.java │ └── TestURLs.java │ ├── api │ ├── TestServerStatsHandler.java │ ├── TestServerTypeHandler.java │ ├── TestPlansHandler.java │ ├── TestInfoHandler.java │ ├── TestServerRoot.java │ ├── TestServerHeaders.java │ ├── TestSessionHandler.java │ └── TestLimiterHandler.java │ ├── core │ ├── TestAuthUtils.java │ └── TestBarcodeOptions.java │ └── cust │ └── TestMiscCustomer.java ├── config ├── community │ ├── admins.json │ ├── blacklist.json │ ├── subscribers.json │ ├── plans.json │ └── app.json ├── apptest │ ├── blacklist.json │ ├── admins.json │ ├── subscribers.json │ ├── plans.json │ └── app.json └── types │ ├── AprilTag.json │ ├── Aztec.json │ ├── QRCode.json │ ├── DataMatrix.json │ ├── PDF417.json │ ├── RoyalMail.json │ ├── USPSMail.json │ ├── CODABAR.json │ ├── EAN8.json │ ├── ITF14.json │ ├── Code39.json │ ├── EAN13.json │ ├── UPC_E.json │ ├── UPC_A.json │ └── Code128.json ├── .env ├── .gitignore ├── Dockerfile ├── ThirdParty.md ├── README.md ├── .gitea └── workflows │ └── build.yaml └── .github └── workflows └── docker-publish.yml /.dict: -------------------------------------------------------------------------------- 1 | barcode 2 | -------------------------------------------------------------------------------- /resources/.htaccess: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/main/resources/app.version: -------------------------------------------------------------------------------- 1 | 0 -------------------------------------------------------------------------------- /config/community/admins.json: -------------------------------------------------------------------------------- 1 | { 2 | "admin": "changeme" 3 | } 4 | -------------------------------------------------------------------------------- /config/community/blacklist.json: -------------------------------------------------------------------------------- 1 | { 2 | "blacklist": [ 3 | ] 4 | } -------------------------------------------------------------------------------- /config/community/subscribers.json: -------------------------------------------------------------------------------- 1 | { 2 | "subscribers": [ 3 | ] 4 | } -------------------------------------------------------------------------------- /config/apptest/blacklist.json: -------------------------------------------------------------------------------- 1 | { 2 | "blacklist": [ 3 | "^_tstblk_$" 4 | ] 5 | } -------------------------------------------------------------------------------- /resources/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BarcodeAPI/server/HEAD/resources/favicon.ico -------------------------------------------------------------------------------- /resources/ext/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BarcodeAPI/server/HEAD/resources/ext/favicon.png -------------------------------------------------------------------------------- /config/apptest/admins.json: -------------------------------------------------------------------------------- 1 | { 2 | "admin": "5E884898DA28047151D0E56F8DC6292773603D0D6AABBDD62A11EF721D1542D8" 3 | } 4 | -------------------------------------------------------------------------------- /resources/ext/3p/IBM-Plex-Mono.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BarcodeAPI/server/HEAD/resources/ext/3p/IBM-Plex-Mono.ttf -------------------------------------------------------------------------------- /.env: -------------------------------------------------------------------------------- 1 | # Override default log name (server) 2 | #LOG_NAME=server 3 | 4 | # List of log streams 5 | LOG_STREAMS=console:/;file:/ 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .classpath 2 | .project 3 | .settings/ 4 | /bin/ 5 | /target/ 6 | /cache/ 7 | /logs/ 8 | /uploads/ 9 | /cache/ 10 | /config/production/ 11 | *.snap 12 | -------------------------------------------------------------------------------- /config/community/plans.json: -------------------------------------------------------------------------------- 1 | { 2 | "free": { 3 | "limit": -1, 4 | "enforce": false, 5 | "batch": 5000, 6 | "description": "Self hosted community server.", 7 | "support": "Contact server admin for details." 8 | }, 9 | "paid": [] 10 | } -------------------------------------------------------------------------------- /resources/ext/js/abuse.js: -------------------------------------------------------------------------------- 1 | // 2 | // BarcodeAPI.org, 2017-2025 3 | // abuse.js // abuse.html 4 | // 5 | 6 | window.addEventListener("load", init); 7 | 8 | function init() { 9 | trackingEvent("AppEvents", "AppLoad", "Abuse", setupMillis); 10 | } 11 | -------------------------------------------------------------------------------- /src/main/java/org/barcodeapi/server/core/RateLimitException.java: -------------------------------------------------------------------------------- 1 | package org.barcodeapi.server.core; 2 | 3 | /** 4 | * RateLimitException.java 5 | * 6 | * @author Matthew R. Clark (BarcodeAPI.org, 2017-2024) 7 | */ 8 | public class RateLimitException extends RuntimeException { 9 | private static final long serialVersionUID = 20241123L; 10 | } 11 | -------------------------------------------------------------------------------- /resources/robots.txt: -------------------------------------------------------------------------------- 1 | User-agent: * 2 | Allow: /about.html 3 | Allow: /api.html 4 | Allow: /bulk.html 5 | Allow: /decode.html 6 | Allow: /index.html 7 | Allow: /license.html 8 | Allow: /limits.html 9 | Allow: /nonprinting.html 10 | Allow: /support.html 11 | Allow: /types.html 12 | Disallow: /multi.html 13 | Disallow: /type.html 14 | Disallow: /session.html 15 | Disallow: /api/ 16 | -------------------------------------------------------------------------------- /src/test/java/org/barcodeapi/test/gen/types/TestAprilTag.java: -------------------------------------------------------------------------------- 1 | package org.barcodeapi.test.gen.types; 2 | 3 | import org.barcodeapi.server.ServerTestBase; 4 | import org.barcodeapi.server.core.GenerationException.ExceptionType; 5 | import org.junit.Assert; 6 | import org.junit.Test; 7 | 8 | public class TestAprilTag extends ServerTestBase { 9 | 10 | @Test 11 | public void testAprilTag_UnsupportedFamily() { 12 | 13 | apiGet("april/tagWoah:1"); 14 | 15 | Assert.assertEquals("Response Code", // 16 | ExceptionType.INVALID.getStatusCode(), getResponseCode()); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/org/barcodeapi/server/gen/types/Ean8Generator.java: -------------------------------------------------------------------------------- 1 | package org.barcodeapi.server.gen.types; 2 | 3 | import org.barcodeapi.server.core.CodeType; 4 | import org.barcodeapi.server.gen.impl.DefaultBarcode4JProvider; 5 | import org.krysalis.barcode4j.impl.upcean.EAN8Bean; 6 | 7 | /** 8 | * Ean8Generator.java 9 | * 10 | * @author Matthew R. Clark (BarcodeAPI.org, 2017-2024) 11 | */ 12 | public class Ean8Generator extends DefaultBarcode4JProvider { 13 | 14 | public Ean8Generator(CodeType codeType) { 15 | 16 | // Setup EAN8 generator 17 | super(codeType, new EAN8Bean()); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/org/barcodeapi/server/gen/types/UPCAGenerator.java: -------------------------------------------------------------------------------- 1 | package org.barcodeapi.server.gen.types; 2 | 3 | import org.barcodeapi.server.core.CodeType; 4 | import org.barcodeapi.server.gen.impl.DefaultBarcode4JProvider; 5 | import org.krysalis.barcode4j.impl.upcean.UPCABean; 6 | 7 | /** 8 | * UPCAGenerator.java 9 | * 10 | * @author Matthew R. Clark (BarcodeAPI.org, 2017-2025) 11 | */ 12 | public class UPCAGenerator extends DefaultBarcode4JProvider { 13 | 14 | public UPCAGenerator(CodeType codeType) { 15 | 16 | // Setup UPC-A generator 17 | super(codeType, new UPCABean()); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/org/barcodeapi/server/gen/types/UPCEGenerator.java: -------------------------------------------------------------------------------- 1 | package org.barcodeapi.server.gen.types; 2 | 3 | import org.barcodeapi.server.core.CodeType; 4 | import org.barcodeapi.server.gen.impl.DefaultBarcode4JProvider; 5 | import org.krysalis.barcode4j.impl.upcean.UPCEBean; 6 | 7 | /** 8 | * UPCEGenerator.java 9 | * 10 | * @author Matthew R. Clark (BarcodeAPI.org, 2017-2025) 11 | */ 12 | public class UPCEGenerator extends DefaultBarcode4JProvider { 13 | 14 | public UPCEGenerator(CodeType codeType) { 15 | 16 | // Setup UPC-E generator 17 | super(codeType, new UPCEBean()); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/org/barcodeapi/server/gen/types/Ean13Generator.java: -------------------------------------------------------------------------------- 1 | package org.barcodeapi.server.gen.types; 2 | 3 | import org.barcodeapi.server.core.CodeType; 4 | import org.barcodeapi.server.gen.impl.DefaultBarcode4JProvider; 5 | import org.krysalis.barcode4j.impl.upcean.EAN13Bean; 6 | 7 | /** 8 | * Ean13Generator.java 9 | * 10 | * @author Matthew R. Clark (BarcodeAPI.org, 2017-2024) 11 | */ 12 | public class Ean13Generator extends DefaultBarcode4JProvider { 13 | 14 | public Ean13Generator(CodeType codeType) { 15 | 16 | // Setup EAN13 generator 17 | super(codeType, new EAN13Bean()); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/org/barcodeapi/server/gen/types/ITF14Generator.java: -------------------------------------------------------------------------------- 1 | package org.barcodeapi.server.gen.types; 2 | 3 | import org.barcodeapi.server.core.CodeType; 4 | import org.barcodeapi.server.gen.impl.DefaultBarcode4JProvider; 5 | import org.krysalis.barcode4j.impl.int2of5.ITF14Bean; 6 | 7 | /** 8 | * ITF14Generator.java 9 | * 10 | * @author Matthew R. Clark (BarcodeAPI.org, 2017-2024) 11 | */ 12 | public class ITF14Generator extends DefaultBarcode4JProvider { 13 | 14 | public ITF14Generator(CodeType codeType) { 15 | 16 | // Setup ITF14 generator 17 | super(codeType, new ITF14Bean()); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/org/barcodeapi/server/gen/types/Code39Generator.java: -------------------------------------------------------------------------------- 1 | package org.barcodeapi.server.gen.types; 2 | 3 | import org.barcodeapi.server.core.CodeType; 4 | import org.barcodeapi.server.gen.impl.DefaultBarcode4JProvider; 5 | import org.krysalis.barcode4j.impl.code39.Code39Bean; 6 | 7 | /** 8 | * Code39Generator.java 9 | * 10 | * @author Matthew R. Clark (BarcodeAPI.org, 2017-2024) 11 | */ 12 | public class Code39Generator extends DefaultBarcode4JProvider { 13 | 14 | public Code39Generator(CodeType codeType) { 15 | 16 | // Setup Code39 generator 17 | super(codeType, new Code39Bean()); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/org/barcodeapi/server/gen/types/PDF417Generator.java: -------------------------------------------------------------------------------- 1 | package org.barcodeapi.server.gen.types; 2 | 3 | import org.barcodeapi.server.core.CodeType; 4 | import org.barcodeapi.server.gen.impl.DefaultBarcode4JProvider; 5 | import org.krysalis.barcode4j.impl.pdf417.PDF417Bean; 6 | 7 | /** 8 | * PDF417Generator.java 9 | * 10 | * @author Matthew R. Clark (BarcodeAPI.org, 2017-2024) 11 | */ 12 | public class PDF417Generator extends DefaultBarcode4JProvider { 13 | 14 | public PDF417Generator(CodeType codeType) { 15 | 16 | // Setup PDF417 generator 17 | super(codeType, new PDF417Bean()); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /config/apptest/subscribers.json: -------------------------------------------------------------------------------- 1 | { 2 | "subscribers": [ 3 | { 4 | "customer": "AppTest", 5 | "subscribed": 1735693200000, 6 | "active": true, 7 | "enforce": true, 8 | "limit": 1000, 9 | "batch": 100, 10 | "ips": [], 11 | "keys": [ 12 | "appTest" 13 | ], 14 | "apps": [] 15 | }, 16 | { 17 | "customer": "BarcodeAPI.org", 18 | "subscribed": 1735693200000, 19 | "active": true, 20 | "enforce": false, 21 | "limit": -1, 22 | "batch": 500, 23 | "ips": [ 24 | "127.0.0.1" 25 | ], 26 | "keys": [], 27 | "apps": [] 28 | } 29 | ] 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/org/barcodeapi/server/gen/types/CodabarGenerator.java: -------------------------------------------------------------------------------- 1 | package org.barcodeapi.server.gen.types; 2 | 3 | import org.barcodeapi.server.core.CodeType; 4 | import org.barcodeapi.server.gen.impl.DefaultBarcode4JProvider; 5 | import org.krysalis.barcode4j.impl.codabar.CodabarBean; 6 | 7 | /** 8 | * CodabarGenerator.java 9 | * 10 | * @author Matthew R. Clark (BarcodeAPI.org, 2017-2024) 11 | */ 12 | public class CodabarGenerator extends DefaultBarcode4JProvider { 13 | 14 | public CodabarGenerator(CodeType codeType) { 15 | 16 | // Setup Codabar generator 17 | super(codeType, new CodabarBean()); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/org/barcodeapi/server/gen/types/Code128Generator.java: -------------------------------------------------------------------------------- 1 | package org.barcodeapi.server.gen.types; 2 | 3 | import org.barcodeapi.server.core.CodeType; 4 | import org.barcodeapi.server.gen.impl.DefaultBarcode4JProvider; 5 | import org.krysalis.barcode4j.impl.code128.Code128Bean; 6 | 7 | /** 8 | * Code128Generator.java 9 | * 10 | * @author Matthew R. Clark (BarcodeAPI.org, 2017-2024) 11 | */ 12 | public class Code128Generator extends DefaultBarcode4JProvider { 13 | 14 | public Code128Generator(CodeType codeType) { 15 | 16 | // Setup Code128 generator 17 | super(codeType, new Code128Bean()); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /config/apptest/plans.json: -------------------------------------------------------------------------------- 1 | { 2 | "free": { 3 | "limit": 1000, 4 | "enforce": true, 5 | "batch": 25, 6 | "description": "System Test: Up to 1,000 tokens per day.", 7 | "support": "Not safe for Humans." 8 | }, 9 | "paid": [ 10 | { 11 | "name": "AppTest Subscribers", 12 | "description": "System Test: More tokens for paying users.", 13 | "support": "Application is provided as-is.", 14 | "price": [ 15 | { 16 | "name": "$10/mo", 17 | "link": false 18 | }, 19 | { 20 | "name": "$100/yr", 21 | "link": false 22 | } 23 | ] 24 | } 25 | ] 26 | } 27 | 28 | -------------------------------------------------------------------------------- /resources/ext/js/keyboard.js: -------------------------------------------------------------------------------- 1 | // 2 | // BarcodeAPI.org, 2017-2025 3 | // keyboard.js // keyboard.html 4 | // 5 | 6 | function init() { 7 | var btns = document.getElementsByClassName("button"); 8 | for (var x in btns) { 9 | if (btns[x].addEventListener) { 10 | btns[x].addEventListener('click', addChar); 11 | } 12 | } 13 | } 14 | 15 | function addChar(event) { 16 | 17 | var val = event.target.value; 18 | window.opener.addCharacter(val) 19 | } 20 | 21 | function _help() { 22 | window.opener.actionNonprintingHelp(); 23 | } 24 | 25 | function _close() { 26 | window.opener.actionCloseKeyboard(); 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/org/barcodeapi/server/gen/types/RoyalMailGenerator.java: -------------------------------------------------------------------------------- 1 | package org.barcodeapi.server.gen.types; 2 | 3 | import org.barcodeapi.server.core.CodeType; 4 | import org.barcodeapi.server.gen.impl.DefaultBarcode4JProvider; 5 | import org.krysalis.barcode4j.impl.fourstate.RoyalMailCBCBean; 6 | 7 | /** 8 | * RoyalMailGenerator.java 9 | * 10 | * @author Matthew R. Clark (BarcodeAPI.org, 2017-2024) 11 | */ 12 | public class RoyalMailGenerator extends DefaultBarcode4JProvider { 13 | 14 | public RoyalMailGenerator(CodeType codeType) { 15 | 16 | // Setup RoyalMail generator 17 | super(codeType, new RoyalMailCBCBean()); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | 2 | # Build 3 | FROM maven:3-openjdk-11-slim AS build 4 | COPY src /home/app/src 5 | COPY pom.xml /home/app 6 | RUN mvn clean compile assembly:single -f /home/app/pom.xml 7 | 8 | # Package 9 | FROM openjdk:11-jre-slim 10 | RUN apt update && apt install -y libfreetype-dev && rm -rf /var/lib/apt/lists/* 11 | COPY --from=build /home/app/target/server.jar /usr/local/lib/server.jar 12 | COPY config /config 13 | COPY resources /resources 14 | 15 | # Mounts 16 | VOLUME /cache 17 | VOLUME /config 18 | 19 | # Ports 20 | EXPOSE 8080 21 | 22 | # Command 23 | CMD [ "java", "-jar", "/usr/local/lib/server.jar", "--language", "en_US", "--port", "8080" ] 24 | -------------------------------------------------------------------------------- /src/main/java/org/barcodeapi/server/gen/types/USPSMailGenerator.java: -------------------------------------------------------------------------------- 1 | package org.barcodeapi.server.gen.types; 2 | 3 | import org.barcodeapi.server.core.CodeType; 4 | import org.barcodeapi.server.gen.impl.DefaultBarcode4JProvider; 5 | import org.krysalis.barcode4j.impl.fourstate.USPSIntelligentMailBean; 6 | 7 | /** 8 | * USPSMailGenerator.java 9 | * 10 | * @author Matthew R. Clark (BarcodeAPI.org, 2017-2024) 11 | */ 12 | public class USPSMailGenerator extends DefaultBarcode4JProvider { 13 | 14 | public USPSMailGenerator(CodeType codeType) { 15 | 16 | // Setup USPS-Mail generator 17 | super(codeType, new USPSIntelligentMailBean()); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/org/barcodeapi/server/gen/types/AztecGenerator.java: -------------------------------------------------------------------------------- 1 | package org.barcodeapi.server.gen.types; 2 | 3 | import org.barcodeapi.server.core.CodeType; 4 | import org.barcodeapi.server.gen.impl.DefaultZXingProvider; 5 | 6 | import com.google.zxing.BarcodeFormat; 7 | import com.google.zxing.aztec.AztecWriter; 8 | 9 | /** 10 | * AztecGenerator.java 11 | * 12 | * @author Matthew R. Clark (BarcodeAPI.org, 2017-2024) 13 | */ 14 | public class AztecGenerator extends DefaultZXingProvider { 15 | 16 | public AztecGenerator(CodeType codeType) { 17 | 18 | // Setup Aztec generator 19 | super(codeType, BarcodeFormat.AZTEC, new AztecWriter()); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/org/barcodeapi/server/gen/types/QRCodeGenerator.java: -------------------------------------------------------------------------------- 1 | package org.barcodeapi.server.gen.types; 2 | 3 | import org.barcodeapi.server.core.CodeType; 4 | import org.barcodeapi.server.gen.impl.DefaultZXingProvider; 5 | 6 | import com.google.zxing.BarcodeFormat; 7 | import com.google.zxing.qrcode.QRCodeWriter; 8 | 9 | /** 10 | * QRCodeGenerator.java 11 | * 12 | * @author Matthew R. Clark (BarcodeAPI.org, 2017-2024) 13 | */ 14 | public class QRCodeGenerator extends DefaultZXingProvider { 15 | 16 | public QRCodeGenerator(CodeType codeType) { 17 | 18 | // Setup QR generator 19 | super(codeType, BarcodeFormat.QR_CODE, new QRCodeWriter()); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/test/java/org/barcodeapi/test/api/TestServerStatsHandler.java: -------------------------------------------------------------------------------- 1 | package org.barcodeapi.test.api; 2 | 3 | import org.barcodeapi.server.ServerTestBase; 4 | import org.eclipse.jetty.http.HttpStatus; 5 | import org.junit.Assert; 6 | import org.junit.Test; 7 | 8 | /** 9 | * TestServerStatsHandler.java 10 | * 11 | * @author Matthew R. Clark (BarcodeAPI.org, 2017-2025) 12 | */ 13 | public class TestServerStatsHandler extends ServerTestBase { 14 | 15 | @Test 16 | public void testServer_TestStatsEndpoind() { 17 | 18 | serverGet("/server/stats/"); 19 | 20 | Assert.assertEquals("Response Code", // 21 | HttpStatus.UNAUTHORIZED_401, getResponseCode()); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/org/barcodeapi/core/utils/AuthUtils.java: -------------------------------------------------------------------------------- 1 | package org.barcodeapi.core.utils; 2 | 3 | import com.mclarkdev.tools.libextras.LibExtrasHashes; 4 | 5 | /** 6 | * AuthUtils.java 7 | * 8 | * @author Matthew R. Clark (BarcodeAPI.org, 2017-2024) 9 | */ 10 | public class AuthUtils { 11 | 12 | public static void main(String[] args) { 13 | 14 | System.out.println(// 15 | formatUser(args[0], passHash(args[1]))); 16 | } 17 | 18 | public static String passHash(String pass) { 19 | return LibExtrasHashes.sumSHA256(pass.getBytes()); 20 | } 21 | 22 | public static String formatUser(String user, String hash) { 23 | return String.format("%s:%s", user, hash); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/test/java/org/barcodeapi/test/gen/TestBlacklist.java: -------------------------------------------------------------------------------- 1 | package org.barcodeapi.test.gen; 2 | 3 | import org.barcodeapi.server.ServerTestBase; 4 | import org.barcodeapi.server.core.GenerationException; 5 | import org.junit.Assert; 6 | import org.junit.Test; 7 | 8 | /** 9 | * TestBlacklist.java 10 | * 11 | * @author Matthew R. Clark (BarcodeAPI.org, 2017-2025) 12 | */ 13 | public class TestBlacklist extends ServerTestBase { 14 | 15 | @Test 16 | public void testBlacklist_testBlacklistItem() { 17 | 18 | apiGet("_tstblk_"); 19 | 20 | Assert.assertEquals("Response Code", // 21 | GenerationException.ExceptionType.BLACKLIST.getStatusCode(), getResponseCode()); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /ThirdParty.md: -------------------------------------------------------------------------------- 1 | ## Third-Party 2 | 3 | BarcodeAPI.org is only made possible with the use of third-party software. 4 | 5 | **Jetty, [Apache 2.0](https://www.eclipse.org/jetty/licenses.html)** 6 | 7 | * The BarcodeAPI server was built around the [Jetty](https://www.eclipse.org/jetty/) web server framework. 8 | 9 | **Barcode4J, [Apache 2.0](http://barcode4j.sourceforge.net/#Introduction)** 10 | 11 | * [Barcode4J](http://barcode4j.sourceforge.net/) is an open source barcode generator; it is used for the generation of the following code types: 12 | 13 | **ZXing, [Apache 2.0](https://github.com/zxing/zxing/blob/master/LICENSE)** 14 | 15 | * [ZXing](https://github.com/zxing/zxing/) is a barcode processing library that makes QR code generation possible. 16 | -------------------------------------------------------------------------------- /src/test/java/org/barcodeapi/test/core/TestAuthUtils.java: -------------------------------------------------------------------------------- 1 | package org.barcodeapi.test.core; 2 | 3 | import org.barcodeapi.core.utils.AuthUtils; 4 | import org.barcodeapi.server.ServerTestBase; 5 | import org.junit.Assert; 6 | import org.junit.Test; 7 | 8 | /** 9 | * TestAuthUtils.java 10 | * 11 | * @author Matthew R. Clark (BarcodeAPI.org, 2017-2025) 12 | */ 13 | public class TestAuthUtils extends ServerTestBase { 14 | 15 | @Test 16 | public void TestAuthUtils_TestPassGen() { 17 | 18 | String testUserHash = AuthUtils.passHash("test"); 19 | String testUserAuth = AuthUtils.formatUser("testUser", testUserHash); 20 | 21 | Assert.assertEquals("AuthString", // 22 | "testUser:9F86D081884C7D659A2FEAA0C55AD015A3BF4F1B2B0B822CD15D6C15B0F00A08", testUserAuth); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /config/apptest/app.json: -------------------------------------------------------------------------------- 1 | { 2 | "types": [ 3 | "UPC_E", 4 | "UPC_A", 5 | "EAN8", 6 | "EAN13", 7 | "CODABAR", 8 | "ITF14", 9 | "Code39", 10 | "Code128", 11 | "Aztec", 12 | "QRCode", 13 | "DataMatrix", 14 | "PDF417", 15 | "USPSMail", 16 | "RoyalMail", 17 | "AprilTag" 18 | ], 19 | "tasks": [], 20 | "cache": { 21 | "_snapshots": "./cache/", 22 | "barcode": { 23 | "life": 10080, 24 | "shortLife": 240 25 | }, 26 | "session": { 27 | "life": 20160, 28 | "shortLife": 60 29 | }, 30 | "limiter": { 31 | "life": 4320, 32 | "shortLife": 360 33 | }, 34 | "share": { 35 | "life": 129600, 36 | "shortLife": 360 37 | } 38 | }, 39 | "client": { 40 | "cacheStatic": 1440, 41 | "cacheBarcode": 1440 42 | } 43 | } 44 | 45 | -------------------------------------------------------------------------------- /src/test/java/org/barcodeapi/test/gen/TestSpecial.java: -------------------------------------------------------------------------------- 1 | package org.barcodeapi.test.gen; 2 | 3 | import org.barcodeapi.server.ServerTestBase; 4 | import org.eclipse.jetty.http.HttpStatus; 5 | import org.junit.Assert; 6 | import org.junit.Test; 7 | 8 | /** 9 | * TestSpecial.java 10 | * 11 | * @author Matthew R. Clark (BarcodeAPI.org, 2017-2025) 12 | */ 13 | public class TestSpecial extends ServerTestBase { 14 | 15 | @Test 16 | public void testSpecial_Price() { 17 | 18 | apiGet("$12.34"); 19 | 20 | Assert.assertEquals("Response Code", // 21 | HttpStatus.OK_200, getResponseCode()); 22 | 23 | Assert.assertEquals("Code Type", // 24 | "Code39", getHeader("X-Barcode-Type")); 25 | 26 | Assert.assertEquals("Code Data", // 27 | encode("$12.34"), getHeader("X-Barcode-Content")); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/org/barcodeapi/server/gen/types/DataMatrixGenerator.java: -------------------------------------------------------------------------------- 1 | package org.barcodeapi.server.gen.types; 2 | 3 | import org.barcodeapi.server.core.CodeType; 4 | import org.barcodeapi.server.gen.impl.DefaultBarcode4JProvider; 5 | import org.krysalis.barcode4j.impl.datamatrix.DataMatrixBean; 6 | import org.krysalis.barcode4j.impl.datamatrix.SymbolShapeHint; 7 | 8 | /** 9 | * DataMatrixGenerator.java 10 | * 11 | * @author Matthew R. Clark (BarcodeAPI.org, 2017-2024) 12 | */ 13 | public class DataMatrixGenerator extends DefaultBarcode4JProvider { 14 | 15 | public DataMatrixGenerator(CodeType codeType) { 16 | super(codeType, initBean()); 17 | } 18 | 19 | private static DataMatrixBean initBean() { 20 | DataMatrixBean bean = new DataMatrixBean(); 21 | bean.setShape(SymbolShapeHint.FORCE_SQUARE); 22 | return bean; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /resources/ext/js/nonprinting.js: -------------------------------------------------------------------------------- 1 | // 2 | // BarcodeAPI.org, 2017-2025 3 | // nonprinting.js // nonprinting.html 4 | // 5 | 6 | window.addEventListener("load", function() { 7 | 8 | document.supported = document.getElementById("types_supported"); 9 | 10 | fetch('/type/') 11 | .then(response => { 12 | return response.json(); 13 | }) 14 | .then(data => { 15 | for (var x in data) { 16 | if (data[x].nonprinting) { 17 | addSupported(data[x]); 18 | } 19 | } 20 | }); 21 | }); 22 | 23 | function addSupported(type) { 24 | 25 | var dType = document.createElement("a"); 26 | dType.innerHTML = type.display; 27 | dType.href = ("index.html#" + type.targets[0]); 28 | 29 | var iType = document.createElement("li"); 30 | iType.appendChild(dType); 31 | 32 | document.supported.appendChild(iType); 33 | } 34 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![_logo_](resources/ext/logo.svg)](https://barcodeapi.org/) 2 | 3 | # BarcodeAPI.org 4 | 5 | The BarcodeAPI.org server was designed to provide an easy-to-use barcode image generation API served through the HTTP protocol. 6 | 7 | By serving images with this approach, any application where barcode generation libraries may not exist or resources may be limited can continue to serve barcode images, provided network access is available. 8 | 9 | The WebUI is also designed to be responsive, allowing users to generate barcodes that can be scanned directly from their web browser or downloaded after testing. 10 | 11 | Comprehensive documentation regarding server capabilities is built directly into the server, ensuring users have access to all necessary information for efficient usage. 12 | 13 | For detailed instructions and examples, check out our new [User Manual](https://barcodeapi.org/api.html). 14 | -------------------------------------------------------------------------------- /src/main/java/org/barcodeapi/server/api/LimiterHandler.java: -------------------------------------------------------------------------------- 1 | package org.barcodeapi.server.api; 2 | 3 | import java.io.IOException; 4 | 5 | import javax.servlet.http.HttpServletResponse; 6 | 7 | import org.barcodeapi.server.core.RequestContext; 8 | import org.barcodeapi.server.core.RestHandler; 9 | import org.json.JSONObject; 10 | 11 | /** 12 | * SessionDetailsHandler.java 13 | * 14 | * @author Matthew R. Clark (BarcodeAPI.org, 2017-2024) 15 | */ 16 | public class LimiterHandler extends RestHandler { 17 | 18 | public LimiterHandler() { 19 | super(); 20 | } 21 | 22 | @Override 23 | protected void onRequest(RequestContext c, HttpServletResponse r) throws IOException { 24 | 25 | // Print response to client 26 | r.setStatus(HttpServletResponse.SC_OK); 27 | r.setContentType("application/json"); 28 | r.getOutputStream().println(// 29 | c.getLimiter().asJSON().toString(4)); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/org/barcodeapi/server/tasks/ShareCleanupTask.java: -------------------------------------------------------------------------------- 1 | package org.barcodeapi.server.tasks; 2 | 3 | import org.barcodeapi.server.cache.ObjectCache; 4 | import org.barcodeapi.server.core.BackgroundTask; 5 | 6 | import com.mclarkdev.tools.liblog.LibLog; 7 | 8 | /** 9 | * ShareCleanupTask.java 10 | * 11 | * A background task which periodically removes stale shares from the cache. 12 | * Additionally saves a cache snapshot to disk, to be used on server restart. 13 | * 14 | * @author Matthew R. Clark (BarcodeAPI.org, 2017-2024) 15 | */ 16 | public class ShareCleanupTask extends BackgroundTask { 17 | 18 | private final ObjectCache shares = // 19 | ObjectCache.getCache(ObjectCache.CACHE_SHARE); 20 | 21 | public ShareCleanupTask() { 22 | super(); 23 | } 24 | 25 | @Override 26 | public void onRun() { 27 | 28 | // Cleanup Share cache 29 | int removed = shares.expireOldObjects(), active = shares.count(); 30 | LibLog._clogF("I2611", removed, active); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/org/barcodeapi/server/tasks/LimiterCleanupTask.java: -------------------------------------------------------------------------------- 1 | package org.barcodeapi.server.tasks; 2 | 3 | import org.barcodeapi.server.cache.ObjectCache; 4 | import org.barcodeapi.server.core.BackgroundTask; 5 | 6 | import com.mclarkdev.tools.liblog.LibLog; 7 | 8 | /** 9 | * LimiterCleanupTask.java 10 | * 11 | * A background task which periodically removes stale limiters from the cache. 12 | * 13 | * @author Matthew R. Clark (BarcodeAPI.org, 2017-2025) 14 | */ 15 | public class LimiterCleanupTask extends BackgroundTask { 16 | 17 | public LimiterCleanupTask() { 18 | super(); 19 | } 20 | 21 | @Override 22 | public void onRun() { 23 | 24 | // Get the requested limiter cache 25 | ObjectCache cache = ObjectCache.getCache(ObjectCache.CACHE_LIMITERS); 26 | 27 | // Remove expired objects and log current counts 28 | int removed = cache.expireOldObjects(); 29 | int active = cache.count(); 30 | 31 | // Log the session cache info 32 | LibLog._clogF("I2601", removed, active); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /resources/multi.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | 8 | 9 | BarcodeAPI.org - MultiLabel Generator 10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 |
18 | 19 | 20 | 21 | 22 | 23 | 24 |
25 |
26 | 27 | 28 | -------------------------------------------------------------------------------- /src/main/java/org/barcodeapi/server/tasks/SessionCleanupTask.java: -------------------------------------------------------------------------------- 1 | package org.barcodeapi.server.tasks; 2 | 3 | import org.barcodeapi.server.cache.ObjectCache; 4 | import org.barcodeapi.server.core.BackgroundTask; 5 | 6 | import com.mclarkdev.tools.liblog.LibLog; 7 | 8 | /** 9 | * SessionCleanupTask.java 10 | * 11 | * A background task which periodically removes stale sessions from the cache. 12 | * Additionally saves a cache snapshot to disk, to be used on server restart. 13 | * 14 | * @author Matthew R. Clark (BarcodeAPI.org, 2017-2024) 15 | */ 16 | public class SessionCleanupTask extends BackgroundTask { 17 | 18 | private final ObjectCache sessions = // 19 | ObjectCache.getCache(ObjectCache.CACHE_SESSIONS); 20 | 21 | public SessionCleanupTask() { 22 | super(); 23 | } 24 | 25 | @Override 26 | public void onRun() { 27 | 28 | // Remove expired objects and log current counts 29 | int removed = sessions.expireOldObjects(), active = sessions.count(); 30 | LibLog._clogF("I2401", removed, active); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/org/barcodeapi/server/api/PlansHandler.java: -------------------------------------------------------------------------------- 1 | package org.barcodeapi.server.api; 2 | 3 | import java.io.IOException; 4 | 5 | import javax.servlet.http.HttpServletResponse; 6 | 7 | import org.barcodeapi.core.Config; 8 | import org.barcodeapi.core.Config.Cfg; 9 | import org.barcodeapi.server.core.RequestContext; 10 | import org.barcodeapi.server.core.RestHandler; 11 | import org.json.JSONException; 12 | import org.json.JSONObject; 13 | 14 | /** 15 | * PlansHandler.java 16 | * 17 | * @author Matthew R. Clark (BarcodeAPI.org, 2017-2025) 18 | */ 19 | public class PlansHandler extends RestHandler { 20 | 21 | private static final JSONObject plansInfo = Config.get(Cfg.Plans); 22 | 23 | public PlansHandler() { 24 | super(); 25 | } 26 | 27 | @Override 28 | protected void onRequest(RequestContext c, HttpServletResponse r) throws JSONException, IOException { 29 | 30 | // Print response to client 31 | r.setStatus(HttpServletResponse.SC_OK); 32 | r.setContentType("application/json"); 33 | r.getOutputStream().println(plansInfo.toString()); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /config/types/AprilTag.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "AprilTag", 3 | "display": "April Tag", 4 | "show": false, 5 | "decode": false, 6 | "cache": true, 7 | "target": [ 8 | "april", 9 | "apriltag" 10 | ], 11 | "generator": ".AprilTagGenerator", 12 | "threads": 2, 13 | "priority": 65, 14 | "cost": { 15 | "basic": 1, 16 | "custom": 1 17 | }, 18 | "checkdigit": 0, 19 | "nonprinting": false, 20 | "example": [ 21 | "tag36h11:0", 22 | "tag16h5:0" 23 | ], 24 | "pattern": { 25 | "auto": "^tag.*:[0-9]{1,5}$", 26 | "extended": "^tag.*:[0-9]{1,5}$" 27 | }, 28 | "description": { 29 | "en": "AprilTag.\n\nSupported types:\ntag16h5\ntag25h9\ntag36h9\ntag36h10\ntag36h11\ntagCircle21h7\ntagCircle49h12\ntagCustom48h\ntagStandard41h12\ntagStandard52h13\n\nCall as: /api/april/$type:$id" 30 | }, 31 | "wiki": { 32 | "en": "https://github.com/AprilRobotics/apriltag" 33 | }, 34 | "options": { 35 | "scale": { 36 | "name": "Scale", 37 | "type": "number", 38 | "default": 8, 39 | "min": 1, 40 | "max": 20, 41 | "step": 1 42 | } 43 | } 44 | } -------------------------------------------------------------------------------- /src/test/java/org/barcodeapi/test/api/TestServerTypeHandler.java: -------------------------------------------------------------------------------- 1 | package org.barcodeapi.test.api; 2 | 3 | import org.barcodeapi.server.ServerTestBase; 4 | import org.eclipse.jetty.http.HttpStatus; 5 | import org.junit.Assert; 6 | import org.junit.Test; 7 | 8 | /** 9 | * TestServerTypeHandler.java 10 | * 11 | * @author Matthew R. Clark (BarcodeAPI.org, 2017-2025) 12 | */ 13 | public class TestServerTypeHandler extends ServerTestBase { 14 | 15 | @Test 16 | public void testServer_TestTypeEndpoind() { 17 | 18 | serverGet("/type/?type=a"); 19 | 20 | Assert.assertEquals("Response Code", // 21 | HttpStatus.OK_200, getResponseCode()); 22 | } 23 | 24 | @Test 25 | public void testServer_TestTypeEndpointNoArgs() { 26 | 27 | serverGet("/type/?"); 28 | 29 | Assert.assertEquals("Response Code", // 30 | HttpStatus.OK_200, getResponseCode()); 31 | } 32 | 33 | @Test 34 | public void testServer_TestTypeEndpointWrongTarget() { 35 | 36 | serverGet("/type/?type=abc"); 37 | 38 | Assert.assertEquals("Response Code", // 39 | HttpStatus.BAD_REQUEST_400, getResponseCode()); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /resources/ext/js/admin.js: -------------------------------------------------------------------------------- 1 | // 2 | // BarcodeAPI.org, 2017-2025 3 | // admin.js // admin.html 4 | // 5 | 6 | function zero(x) { 7 | return ((x < 10) ? ("0" + x) : x); 8 | } 9 | 10 | function loadInfo() { 11 | fetch("/info/").then(response => { 12 | return response.json(); 13 | }).then(onLoadInfo); 14 | } 15 | 16 | function onLoadInfo(info) { 17 | 18 | // display hostname, version, dist 19 | document.getElementById("appHost").innerHTML = info.hostname; 20 | document.getElementById("appVersion").innerHTML = info.version; 21 | document.getElementById("appDist").innerHTML = info.dist; 22 | 23 | // calculate uptime in HH:MM 24 | var sinceH = Math.floor((info.uptime / 1000 / 60 / 60)).toFixed(0); 25 | var sinceM = Math.floor((info.uptime / 1000 / 60 % 60)).toFixed(0); 26 | 27 | // display uptime 28 | var uptime = ("+" + zero(sinceH) + ":" + zero(sinceM)); 29 | document.getElementById("appUptime").innerHTML = uptime; 30 | 31 | // Log tracking event 32 | var setupMillis = ((new Date()) - timeStart); 33 | trackingEvent("AppEvents", "AppLoad", "Admin", setupMillis); 34 | } 35 | -------------------------------------------------------------------------------- /.gitea/workflows/build.yaml: -------------------------------------------------------------------------------- 1 | name: Java Build 2 | 3 | on: [push] 4 | 5 | jobs: 6 | Build-Artifacts: 7 | runs-on: linux-amd64 8 | steps: 9 | - name: Set up JDK 10 | uses: actions/setup-java@v4 11 | with: 12 | distribution: 'adopt' 13 | java-version: '11' 14 | - name: Clone Repository 15 | run: | 16 | repo="ssh://git@git.mclarkdev.com:2222/${{ gitea.repository }}" 17 | echo "Cloning $repo" && git clone --recurse-submodules $repo . 18 | echo "Checking out $GITHUB_REF_NAME"; git checkout $GITHUB_REF_NAME 19 | - name: Set version number 20 | run: | 21 | echo -n "${{ github.run_id }}" > src/main/resources/app.version 22 | - name: Build artifacts with Maven 23 | run: mvn clean install 24 | - name: Package the server 25 | run: | 26 | mkdir server/ 27 | cp -rv config server/ 28 | cp -rv resources server/ 29 | cp target/server.jar server/ 30 | - name: Archive job artifact 31 | uses: actions/upload-artifact@v3 32 | with: 33 | name: server-${{ github.run_id }} 34 | path: | 35 | server/ 36 | -------------------------------------------------------------------------------- /src/main/java/org/barcodeapi/server/admin/SubscriberReloadHandler.java: -------------------------------------------------------------------------------- 1 | package org.barcodeapi.server.admin; 2 | 3 | import java.io.IOException; 4 | 5 | import javax.servlet.http.HttpServletResponse; 6 | 7 | import org.barcodeapi.server.cache.SubscriberCache; 8 | import org.barcodeapi.server.core.RequestContext; 9 | import org.barcodeapi.server.core.RestHandler; 10 | import org.json.JSONException; 11 | import org.json.JSONObject; 12 | 13 | /** 14 | * SubscriberReloadHandler.java 15 | * 16 | * @author Matthew R. Clark (BarcodeAPI.org, 2017-2024) 17 | */ 18 | public class SubscriberReloadHandler extends RestHandler { 19 | 20 | public SubscriberReloadHandler() { 21 | super(true, false, false); 22 | } 23 | 24 | @Override 25 | protected void onRequest(RequestContext c, HttpServletResponse r) throws JSONException, IOException { 26 | 27 | // Reload the subscribers list 28 | SubscriberCache.reload(); 29 | 30 | // Print response to client 31 | r.setStatus(HttpServletResponse.SC_OK); 32 | r.setContentType("application/json"); 33 | r.getOutputStream().println((new JSONObject()// 34 | .put("code", 200)// 35 | .put("message", "users reloaded")// 36 | ).toString()); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/main/java/org/barcodeapi/server/gen/CodeGenerator.java: -------------------------------------------------------------------------------- 1 | package org.barcodeapi.server.gen; 2 | 3 | import org.barcodeapi.server.core.CodeType; 4 | import org.barcodeapi.server.core.GenerationException; 5 | 6 | /** 7 | * CodeGenerator.java 8 | * 9 | * @author Matthew R. Clark (BarcodeAPI.org, 2017-2024) 10 | */ 11 | public abstract class CodeGenerator { 12 | 13 | private final CodeType codeType; 14 | 15 | public CodeGenerator(CodeType codeType) { 16 | this.codeType = codeType; 17 | } 18 | 19 | /** 20 | * Returns the CodeType associated with the generator. 21 | * 22 | * @return associated CodeType 23 | */ 24 | public CodeType getType() { 25 | return codeType; 26 | } 27 | 28 | /** 29 | * Default validation does not modifications. 30 | * 31 | * @param data 32 | * @return 33 | * @throws GenerationException 34 | */ 35 | public void onValidateRequest(BarcodeRequest data) throws GenerationException { 36 | } 37 | 38 | /** 39 | * Implemented by the specific generator. 40 | * 41 | * Called when a barcode should be rendered. 42 | * 43 | * @param data 44 | * @return 45 | */ 46 | public abstract byte[] onRender(BarcodeRequest request) throws Exception; 47 | } 48 | -------------------------------------------------------------------------------- /src/main/java/org/barcodeapi/server/api/InfoHandler.java: -------------------------------------------------------------------------------- 1 | package org.barcodeapi.server.api; 2 | 3 | import java.io.IOException; 4 | 5 | import javax.servlet.http.HttpServletResponse; 6 | 7 | import org.barcodeapi.core.Config; 8 | import org.barcodeapi.core.ServerRuntime; 9 | import org.barcodeapi.server.core.RequestContext; 10 | import org.barcodeapi.server.core.RestHandler; 11 | import org.json.JSONException; 12 | import org.json.JSONObject; 13 | 14 | /** 15 | * InfoHandler.java 16 | * 17 | * @author Matthew R. Clark (BarcodeAPI.org, 2017-2024) 18 | */ 19 | public class InfoHandler extends RestHandler { 20 | 21 | public InfoHandler() { 22 | super(); 23 | } 24 | 25 | @Override 26 | protected void onRequest(RequestContext c, HttpServletResponse r) throws JSONException, IOException { 27 | 28 | // Print response to client 29 | r.setStatus(HttpServletResponse.SC_OK); 30 | r.setContentType("application/json"); 31 | r.getOutputStream().println((new JSONObject()// 32 | .put("uptime", ServerRuntime.getTimeRunning())// 33 | .put("hostname", ServerRuntime.getHostname())// 34 | .put("version", ServerRuntime.getVersion())// 35 | .put("dist", Config.dist())// 36 | ).toString()); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/main/java/org/barcodeapi/server/tasks/BarcodeCleanupTask.java: -------------------------------------------------------------------------------- 1 | package org.barcodeapi.server.tasks; 2 | 3 | import org.barcodeapi.server.cache.ObjectCache; 4 | import org.barcodeapi.server.core.BackgroundTask; 5 | import org.barcodeapi.server.core.CodeTypes; 6 | 7 | import com.mclarkdev.tools.liblog.LibLog; 8 | 9 | /** 10 | * BarcodeCleanupTask.java 11 | * 12 | * A background task which periodically removes stale barcodes from the cache. 13 | * Additionally saves a cache snapshot to disk, to be used on server restart. 14 | * 15 | * @author Matthew R. Clark (BarcodeAPI.org, 2017-2024) 16 | */ 17 | public class BarcodeCleanupTask extends BackgroundTask { 18 | 19 | public BarcodeCleanupTask() { 20 | super(); 21 | } 22 | 23 | @Override 24 | public void onRun() { 25 | 26 | // Loop each supported type 27 | int removed = 0, active = 0; 28 | for (String type : CodeTypes.inst().getTypes()) { 29 | 30 | // Get the type cache 31 | ObjectCache cache = ObjectCache.getCache(type); 32 | 33 | // Expire and count objects 34 | removed += cache.expireOldObjects(); 35 | active += cache.count(); 36 | } 37 | 38 | // Log the cache counts 39 | LibLog._clogF("I2201", removed, active); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/main/java/org/barcodeapi/server/admin/SessionFlushHandler.java: -------------------------------------------------------------------------------- 1 | package org.barcodeapi.server.admin; 2 | 3 | import java.io.IOException; 4 | 5 | import javax.servlet.http.HttpServletResponse; 6 | 7 | import org.barcodeapi.server.cache.ObjectCache; 8 | import org.barcodeapi.server.core.RequestContext; 9 | import org.barcodeapi.server.core.RestHandler; 10 | import org.json.JSONException; 11 | import org.json.JSONObject; 12 | 13 | /** 14 | * SessionFlushHandler.java 15 | * 16 | * @author Matthew R. Clark (BarcodeAPI.org, 2017-2024) 17 | */ 18 | public class SessionFlushHandler extends RestHandler { 19 | 20 | public SessionFlushHandler() { 21 | super(true, false, false); 22 | } 23 | 24 | @Override 25 | protected void onRequest(RequestContext c, HttpServletResponse r) throws JSONException, IOException { 26 | 27 | // Clear cache and get count 28 | double count = ObjectCache.getCache(// 29 | ObjectCache.CACHE_SESSIONS).clearCache(); 30 | 31 | // Print response to client 32 | r.setStatus(HttpServletResponse.SC_OK); 33 | r.setContentType("application/json"); 34 | r.getOutputStream().println((new JSONObject()// 35 | .put("message", "sessions flushed")// 36 | .put("count", count)// 37 | ).toString()); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /config/types/Aztec.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Aztec", 3 | "display": "Aztec", 4 | "show": true, 5 | "decode": true, 6 | "cache": true, 7 | "target": [ 8 | "aztec" 9 | ], 10 | "generator": ".AztecGenerator", 11 | "threads": 2, 12 | "priority": 52, 13 | "cost": { 14 | "basic": 3, 15 | "custom": 3 16 | }, 17 | "checkdigit": 0, 18 | "nonprinting": true, 19 | "example": [ 20 | "Aztec Barcode" 21 | ], 22 | "pattern": { 23 | "auto": "^[ !#$()*.\\/0-9=?A-Z_a-z~]{1,16}$", 24 | "extended": "^[ !\\\"#$%&\\'()*+,\\-.\\/0-9:;<=>?@A-Z\\[\\]^_`a-z{|}~]+$" 25 | }, 26 | "description": { 27 | "en": "A 2D data barcode whose timing markers radiate outward from the middle." 28 | }, 29 | "wiki": { 30 | "en": "https://en.wikipedia.org/wiki/Aztec_Code" 31 | }, 32 | "options": { 33 | "size": { 34 | "name": "Size", 35 | "type": "number", 36 | "default": 275, 37 | "min": 50, 38 | "max": 500 39 | }, 40 | "qz": { 41 | "name": "Quiet Zone", 42 | "type": "number", 43 | "default": 2, 44 | "min": 0, 45 | "max": 8 46 | }, 47 | "correction": { 48 | "name": "Error Correction", 49 | "type": "number", 50 | "default": 33, 51 | "min": 8, 52 | "max": 65 53 | } 54 | } 55 | } -------------------------------------------------------------------------------- /src/test/java/org/barcodeapi/test/api/TestPlansHandler.java: -------------------------------------------------------------------------------- 1 | package org.barcodeapi.test.api; 2 | 3 | import org.barcodeapi.server.ServerTestBase; 4 | import org.eclipse.jetty.http.HttpStatus; 5 | import org.json.JSONArray; 6 | import org.json.JSONObject; 7 | import org.junit.Assert; 8 | import org.junit.Test; 9 | 10 | /** 11 | * TestServerTypesHandler.java 12 | * 13 | * @author Matthew R. Clark (BarcodeAPI.org, 2017-2025) 14 | */ 15 | public class TestPlansHandler extends ServerTestBase { 16 | 17 | @Test 18 | public void testServer_TestPlansEndpoint() { 19 | 20 | serverGet("/plans/"); 21 | 22 | Assert.assertEquals("Response Code", // 23 | HttpStatus.OK_200, getResponseCode()); 24 | 25 | Assert.assertEquals("Response Header", // 26 | "application/json;charset=utf-8", getHeader("Content-Type")); 27 | 28 | JSONObject response = getResponseAsJSON(); 29 | 30 | JSONObject free = response.getJSONObject("free"); 31 | 32 | Assert.assertEquals("Free Plan Limit", // 33 | 1000, free.getInt("limit")); 34 | 35 | Assert.assertEquals("Free Plan Enforcement", // 36 | true, free.getBoolean("enforce")); 37 | 38 | JSONArray paid = response.getJSONArray("paid"); 39 | 40 | Assert.assertNotEquals("Paid Plan Info", 0, paid.length()); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /config/types/QRCode.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "QRCode", 3 | "display": "QR Code", 4 | "show": true, 5 | "decode": true, 6 | "cache": true, 7 | "target": [ 8 | "qr", 9 | "qr-code", 10 | "qrcode" 11 | ], 12 | "generator": ".QRCodeGenerator", 13 | "threads": 2, 14 | "priority": 58, 15 | "cost": { 16 | "basic": 3, 17 | "custom": 3 18 | }, 19 | "checkdigit": 0, 20 | "nonprinting": true, 21 | "example": [ 22 | "QR Barcode" 23 | ], 24 | "pattern": { 25 | "auto": "^.{1,64}$", 26 | "extended": "^.{1,65535}$" 27 | }, 28 | "description": { 29 | "en": "A 2D data barcode whose timing markers are aligned at the corners." 30 | }, 31 | "wiki": { 32 | "en": "https://en.wikipedia.org/wiki/QR_code" 33 | }, 34 | "options": { 35 | "size": { 36 | "name": "Size", 37 | "type": "number", 38 | "default": 275, 39 | "min": 50, 40 | "max": 500 41 | }, 42 | "qz": { 43 | "name": "Quiet Zone", 44 | "type": "number", 45 | "default": 2, 46 | "min": 0, 47 | "max": 8 48 | }, 49 | "correction": { 50 | "name": "Error Correction", 51 | "type": "option", 52 | "default": "M", 53 | "options": [ 54 | "M", 55 | "L", 56 | "H", 57 | "Q" 58 | ] 59 | } 60 | } 61 | } -------------------------------------------------------------------------------- /src/main/java/org/barcodeapi/server/admin/ServerStatsHandler.java: -------------------------------------------------------------------------------- 1 | package org.barcodeapi.server.admin; 2 | 3 | import java.io.IOException; 4 | 5 | import javax.servlet.http.HttpServletResponse; 6 | 7 | import org.barcodeapi.server.core.RequestContext; 8 | import org.barcodeapi.server.core.RestHandler; 9 | import org.json.JSONException; 10 | import org.json.JSONObject; 11 | 12 | import com.mclarkdev.tools.libmetrics.LibMetrics; 13 | 14 | /** 15 | * ServerStatsHandler.java 16 | * 17 | * @author Matthew R. Clark (BarcodeAPI.org, 2017-2024) 18 | */ 19 | public class ServerStatsHandler extends RestHandler { 20 | 21 | public ServerStatsHandler() { 22 | super(true, false, false); 23 | } 24 | 25 | @Override 26 | protected void onRequest(RequestContext c, HttpServletResponse r) throws JSONException, IOException { 27 | 28 | // Determine which stats cache to use 29 | String cache = c.getRequest().getParameter("cache"); 30 | cache = (cache != null) ? cache : "default"; 31 | 32 | // Get the details from the requested instance 33 | JSONObject stats = LibMetrics.instance(cache).getDetails(); 34 | 35 | // Print response to client 36 | r.setStatus(HttpServletResponse.SC_OK); 37 | r.setContentType("application/json"); 38 | r.getOutputStream().println(stats.toString()); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/main/java/org/barcodeapi/server/admin/ShareListHandler.java: -------------------------------------------------------------------------------- 1 | package org.barcodeapi.server.admin; 2 | 3 | import java.io.IOException; 4 | 5 | import javax.servlet.http.HttpServletResponse; 6 | 7 | import org.barcodeapi.server.cache.ObjectCache; 8 | import org.barcodeapi.server.core.RequestContext; 9 | import org.barcodeapi.server.core.RestHandler; 10 | import org.json.JSONArray; 11 | import org.json.JSONException; 12 | import org.json.JSONObject; 13 | 14 | /** 15 | * ShareListHandler.java 16 | * 17 | * @author Matthew R. Clark (BarcodeAPI.org, 2017-2024) 18 | */ 19 | public class ShareListHandler extends RestHandler { 20 | 21 | public ShareListHandler() { 22 | super(true, false, false); 23 | } 24 | 25 | @Override 26 | protected void onRequest(RequestContext c, HttpServletResponse r) throws JSONException, IOException { 27 | 28 | // Loop all sessions 29 | JSONArray shares = new JSONArray(); 30 | for (String key : ObjectCache.getCache(// 31 | ObjectCache.CACHE_SHARE).raw().keySet()) { 32 | shares.put(key); 33 | } 34 | 35 | // Print response to client 36 | r.setStatus(HttpServletResponse.SC_OK); 37 | r.setContentType("application/json"); 38 | r.getOutputStream().println((new JSONObject()// 39 | .put("shares", shares)// 40 | .put("count", shares.length())// 41 | ).toString()); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/main/java/org/barcodeapi/server/admin/SessionListHandler.java: -------------------------------------------------------------------------------- 1 | package org.barcodeapi.server.admin; 2 | 3 | import java.io.IOException; 4 | 5 | import javax.servlet.http.HttpServletResponse; 6 | 7 | import org.barcodeapi.server.cache.ObjectCache; 8 | import org.barcodeapi.server.core.RequestContext; 9 | import org.barcodeapi.server.core.RestHandler; 10 | import org.json.JSONArray; 11 | import org.json.JSONException; 12 | import org.json.JSONObject; 13 | 14 | /** 15 | * SessionListHandler.java 16 | * 17 | * @author Matthew R. Clark (BarcodeAPI.org, 2017-2024) 18 | */ 19 | public class SessionListHandler extends RestHandler { 20 | 21 | public SessionListHandler() { 22 | super(true, false, false); 23 | } 24 | 25 | @Override 26 | protected void onRequest(RequestContext c, HttpServletResponse r) throws JSONException, IOException { 27 | 28 | // Loop all sessions 29 | JSONArray sessions = new JSONArray(); 30 | for (String key : ObjectCache.getCache(// 31 | ObjectCache.CACHE_SESSIONS).raw().keySet()) { 32 | sessions.put(key); 33 | } 34 | 35 | // Print response to client 36 | r.setStatus(HttpServletResponse.SC_OK); 37 | r.setContentType("application/json"); 38 | r.getOutputStream().println((new JSONObject()// 39 | .put("sessions", sessions)// 40 | .put("count", sessions.length())// 41 | ).toString()); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/main/java/org/barcodeapi/server/admin/CacheFlushHandler.java: -------------------------------------------------------------------------------- 1 | package org.barcodeapi.server.admin; 2 | 3 | import java.io.IOException; 4 | 5 | import javax.servlet.http.HttpServletResponse; 6 | 7 | import org.barcodeapi.server.cache.ObjectCache; 8 | import org.barcodeapi.server.core.CodeTypes; 9 | import org.barcodeapi.server.core.RequestContext; 10 | import org.barcodeapi.server.core.RestHandler; 11 | import org.json.JSONException; 12 | import org.json.JSONObject; 13 | 14 | /** 15 | * CacheFlushHandler.java 16 | * 17 | * @author Matthew R. Clark (BarcodeAPI.org, 2017-2024) 18 | */ 19 | public class CacheFlushHandler extends RestHandler { 20 | 21 | public CacheFlushHandler() { 22 | super(true, false, false); 23 | } 24 | 25 | @Override 26 | protected void onRequest(RequestContext c, HttpServletResponse r) throws JSONException, IOException { 27 | 28 | JSONObject counts = new JSONObject(); 29 | 30 | // Loop all configured types 31 | for (String type : CodeTypes.inst().getTypes()) { 32 | 33 | // Clear the cache and add count to map 34 | counts.put(type, ObjectCache.getCache(type).clearCache()); 35 | } 36 | 37 | // Print response to client 38 | r.setStatus(HttpServletResponse.SC_OK); 39 | r.setContentType("application/json"); 40 | r.getOutputStream().println((new JSONObject()// 41 | .put("code", 200)// 42 | .put("message", "caches flushed")// 43 | .put("counts", counts)// 44 | ).toString()); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/test/java/org/barcodeapi/test/api/TestInfoHandler.java: -------------------------------------------------------------------------------- 1 | package org.barcodeapi.test.api; 2 | 3 | import java.io.IOException; 4 | 5 | import org.barcodeapi.server.ServerTestBase; 6 | import org.eclipse.jetty.http.HttpStatus; 7 | import org.json.JSONObject; 8 | import org.junit.Assert; 9 | import org.junit.Test; 10 | 11 | import com.mclarkdev.tools.libextras.LibExtrasStreams; 12 | 13 | /** 14 | * TestServerTypesHandler.java 15 | * 16 | * @author Matthew R. Clark (BarcodeAPI.org, 2017-2025) 17 | */ 18 | public class TestInfoHandler extends ServerTestBase { 19 | 20 | @Test 21 | public void testServer_TestInfoEndpoint() { 22 | 23 | serverGet("/info/"); 24 | 25 | Assert.assertEquals("Response Code", // 26 | HttpStatus.OK_200, getResponseCode()); 27 | 28 | Assert.assertEquals("Response Header", // 29 | "application/json;charset=utf-8", getHeader("Content-Type")); 30 | 31 | try { 32 | 33 | String response = LibExtrasStreams.readStream(getResponse()); 34 | JSONObject parsed = new JSONObject(response); 35 | 36 | // Server Uptime is set 37 | Assert.assertTrue("Server Uptime", // 38 | parsed.getLong("uptime") > 0); 39 | 40 | // Server Hostname is set 41 | Assert.assertTrue("Server Hostname", // 42 | parsed.getString("hostname").length() > 0); 43 | 44 | // Server version is set 45 | Assert.assertTrue("Server Version", // 46 | parsed.getInt("version") >= 0); 47 | 48 | } catch (IOException e) { 49 | Assert.fail(e.getMessage()); 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /resources/ext/css/multi.css: -------------------------------------------------------------------------------- 1 | @page { 2 | size: auto; 3 | margin: 0; 4 | } 5 | 6 | .multis-barcode { 7 | margin-top: 3em; 8 | margin-bottom: 3em; 9 | max-width: 80mm; 10 | margin-left: 3em; 11 | margin-right: 3em; 12 | } 13 | 14 | #barcodes { 15 | margin-top: 1.5em; 16 | border-spacing: 10mm; 17 | } 18 | 19 | .controller { 20 | position: fixed; 21 | top: 0px; 22 | left: 0px; 23 | display: inline-grid; 24 | } 25 | 26 | .button { 27 | border: none; 28 | color: white; 29 | padding: 10px 32px; 30 | text-align: center; 31 | text-decoration: none; 32 | display: inline-block; 33 | font-size: 16px; 34 | } 35 | 36 | .button-home { 37 | width: 100px; 38 | grid-column: 1; 39 | background-color: rgb(0, 0, 255); 40 | } 41 | 42 | .button-generate { 43 | width: 185px; 44 | grid-column: 3; 45 | background-color: #04AA6D; 46 | } 47 | 48 | .button-share { 49 | width: 112px; 50 | grid-column: 4; 51 | background-color: rgb(255, 128, 0); 52 | } 53 | 54 | .button-print { 55 | width: 112px; 56 | grid-column: 5; 57 | background-color: rgb(127, 0, 255); 58 | } 59 | 60 | .button-clear { 61 | width: 112px; 62 | grid-column: 6; 63 | background-color: rgb(200, 20, 20); 64 | } 65 | 66 | #input { 67 | grid-column: 2; 68 | width: 350px; 69 | height: 36px; 70 | resize: none; 71 | 72 | padding: 8px; 73 | box-sizing: border-box; 74 | border: 2px solid #ccc; 75 | background-color: #f8f8f8; 76 | font-size: 16px; 77 | } 78 | 79 | #input:focus { 80 | outline: none; 81 | } 82 | 83 | @media print { 84 | .controller { 85 | display: none; 86 | } 87 | } -------------------------------------------------------------------------------- /src/test/java/org/barcodeapi/test/api/TestServerRoot.java: -------------------------------------------------------------------------------- 1 | package org.barcodeapi.test.api; 2 | 3 | import org.barcodeapi.server.ServerTestBase; 4 | import org.eclipse.jetty.http.HttpStatus; 5 | import org.junit.Assert; 6 | import org.junit.Test; 7 | 8 | /** 9 | * TestServerRoot.java 10 | * 11 | * @author Matthew R. Clark (BarcodeAPI.org, 2017-2025) 12 | */ 13 | public class TestServerRoot extends ServerTestBase { 14 | 15 | @Test 16 | public void testServer_RootRedirect() { 17 | 18 | serverGet("/"); 19 | 20 | Assert.assertEquals("Response Code", // 21 | HttpStatus.FOUND_302, getResponseCode()); 22 | 23 | Assert.assertTrue("Redirect", // 24 | getHeader("Location").endsWith("/index.html")); 25 | } 26 | 27 | @Test 28 | public void testServer_StaticRoot() { 29 | 30 | serverGet("/index.html"); 31 | 32 | Assert.assertEquals("Response Code", // 33 | HttpStatus.OK_200, getResponseCode()); 34 | 35 | // TODO Assert loaded response body 36 | } 37 | 38 | @Test 39 | public void testServer_StaticResource() { 40 | 41 | serverGet("/ext/logo.svg"); 42 | 43 | Assert.assertEquals("Response Code", // 44 | HttpStatus.OK_200, getResponseCode()); 45 | 46 | // TODO Assert loaded image 47 | } 48 | 49 | @Test 50 | public void testServer_302RedirectToAPI() { 51 | 52 | serverGet("/unknown.html"); 53 | 54 | Assert.assertEquals("Response Code", // 55 | HttpStatus.FOUND_302, getResponseCode()); 56 | 57 | Assert.assertTrue("Redirect", // 58 | getHeader("Location").endsWith("/api/auto/unknown.html")); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/main/java/org/barcodeapi/server/admin/LimiterListHandler.java: -------------------------------------------------------------------------------- 1 | package org.barcodeapi.server.admin; 2 | 3 | import java.io.IOException; 4 | import java.util.Map; 5 | 6 | import javax.servlet.http.HttpServletResponse; 7 | 8 | import org.barcodeapi.server.cache.CachedLimiter; 9 | import org.barcodeapi.server.cache.CachedObject; 10 | import org.barcodeapi.server.cache.ObjectCache; 11 | import org.barcodeapi.server.core.RequestContext; 12 | import org.barcodeapi.server.core.RestHandler; 13 | import org.json.JSONException; 14 | import org.json.JSONObject; 15 | 16 | /** 17 | * LimiterListHandler.java 18 | * 19 | * @author Matthew R. Clark (BarcodeAPI.org, 2017-2024) 20 | */ 21 | public class LimiterListHandler extends RestHandler { 22 | 23 | public LimiterListHandler() { 24 | super(true, false, false); 25 | } 26 | 27 | @Override 28 | protected void onRequest(RequestContext c, HttpServletResponse r) throws JSONException, IOException { 29 | 30 | // List limiters 31 | JSONObject byKey = new JSONObject(); 32 | ObjectCache keyCache = ObjectCache.getCache(ObjectCache.CACHE_LIMITERS); 33 | for (Map.Entry entry : keyCache.raw().entrySet()) { 34 | CachedLimiter limiter = (CachedLimiter) entry.getValue(); 35 | byKey.put(limiter.getCaller(), limiter.getTokenCount()); 36 | } 37 | 38 | // Print response to client 39 | r.setStatus(HttpServletResponse.SC_OK); 40 | r.setContentType("application/json"); 41 | r.getOutputStream().println((new JSONObject()// 42 | .put("limiters", byKey)// 43 | ).toString()); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/main/java/org/barcodeapi/server/tasks/LimiterMintingTask.java: -------------------------------------------------------------------------------- 1 | package org.barcodeapi.server.tasks; 2 | 3 | import java.util.Map; 4 | 5 | import org.barcodeapi.server.cache.CachedLimiter; 6 | import org.barcodeapi.server.cache.CachedObject; 7 | import org.barcodeapi.server.cache.ObjectCache; 8 | import org.barcodeapi.server.core.BackgroundTask; 9 | 10 | import com.mclarkdev.tools.liblog.LibLog; 11 | 12 | /** 13 | * LimiterMintingTask.java 14 | * 15 | * A background task which periodically mints rate limiting tokens. 16 | * 17 | * @author Matthew R. Clark (BarcodeAPI.org, 2017-2024) 18 | */ 19 | public class LimiterMintingTask extends BackgroundTask { 20 | 21 | public LimiterMintingTask() { 22 | super(); 23 | } 24 | 25 | @Override 26 | public void onRun() { 27 | double tokensMinted = 0; 28 | 29 | // Mint tokens for IP cache 30 | tokensMinted += mintTokens(ObjectCache.CACHE_LIMITERS); 31 | 32 | // Log number of tokens minted 33 | LibLog._clogF("I2621", tokensMinted); 34 | getStats().hitCounter(tokensMinted, "task", getName(), "minted"); 35 | } 36 | 37 | private double mintTokens(String cacheName) { 38 | double tokensMinted = 0; 39 | 40 | // Lookup the requested cache 41 | ObjectCache cache = ObjectCache.getCache(cacheName); 42 | 43 | // Loop each cache entry 44 | for (Map.Entry entry : cache.raw().entrySet()) { 45 | 46 | // Mint tokens for the limiter 47 | tokensMinted += ((CachedLimiter) entry.getValue()).mintTokens(); 48 | } 49 | 50 | // Return number minted 51 | return tokensMinted; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /config/community/app.json: -------------------------------------------------------------------------------- 1 | { 2 | "types": [ 3 | "UPC_E", 4 | "UPC_A", 5 | "EAN8", 6 | "EAN13", 7 | "CODABAR", 8 | "ITF14", 9 | "Code39", 10 | "Code128", 11 | "Aztec", 12 | "QRCode", 13 | "DataMatrix", 14 | "PDF417", 15 | "USPSMail", 16 | "RoyalMail", 17 | "AprilTag" 18 | ], 19 | "tasks": [ 20 | { 21 | "name": "Watchdog", 22 | "impl": ".WatchdogTask", 23 | "interval": 15 24 | }, 25 | { 26 | "name": "Stats", 27 | "impl": ".StatsDumpTask", 28 | "interval": 300 29 | }, 30 | { 31 | "name": "Session Cleanup", 32 | "impl": ".SessionCleanupTask", 33 | "interval": 1800 34 | }, 35 | { 36 | "name": "Barcode Cleanup", 37 | "impl": ".BarcodeCleanupTask", 38 | "interval": 3600 39 | }, 40 | { 41 | "name": "Limiter Cleanup", 42 | "impl": ".LimiterCleanupTask", 43 | "interval": 3600 44 | }, 45 | { 46 | "name": "Limiter Minting", 47 | "impl": ".LimiterMintingTask", 48 | "interval": 600 49 | }, 50 | { 51 | "name": "Share Cleanup", 52 | "impl": ".ShareCleanupTask", 53 | "interval": 3600 54 | } 55 | ], 56 | "cache": { 57 | "_snapshots": "./cache/", 58 | "barcode": { 59 | "life": 10080, 60 | "shortLife": 240 61 | }, 62 | "session": { 63 | "life": 20160, 64 | "shortLife": 60 65 | }, 66 | "limiter": { 67 | "life": 4320, 68 | "shortLife": 360 69 | }, 70 | "share": { 71 | "life": 129600, 72 | "shortLife": 360 73 | } 74 | }, 75 | "client": { 76 | "cacheStatic": 1440, 77 | "cacheBarcode": 1440 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /resources/abuse.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | 8 | 9 | BarcodeAPI.org - Rate Limits 10 | 11 | 13 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 | 30 |
31 | 32 |
33 |

34 |
FEEDBACK & SUPPORT
35 |
support@barcodeapi.org
36 |

37 |
38 |
39 |
40 | 41 |
42 |
43 |

API Abuse

44 |
45 | Abusers will be IP banned. 46 |
47 |
48 |
49 |
50 | 51 | 52 | -------------------------------------------------------------------------------- /src/main/java/org/barcodeapi/server/core/BackgroundTask.java: -------------------------------------------------------------------------------- 1 | package org.barcodeapi.server.core; 2 | 3 | import java.util.TimerTask; 4 | 5 | import com.mclarkdev.tools.liblog.LibLog; 6 | import com.mclarkdev.tools.libmetrics.LibMetrics; 7 | 8 | /** 9 | * BackgroundTask.java 10 | * 11 | * @author Matthew R. Clark (BarcodeAPI.org, 2017-2024) 12 | */ 13 | public abstract class BackgroundTask extends TimerTask { 14 | 15 | private final LibMetrics stats = LibMetrics.instance(); 16 | 17 | private final String name; 18 | 19 | public BackgroundTask() { 20 | 21 | String clazz = getClass().getName(); 22 | name = clazz.substring(clazz.lastIndexOf('.') + 1); 23 | } 24 | 25 | /** 26 | * Returns an instance of the stats collector. 27 | * 28 | * @return an instance of the stats collector 29 | */ 30 | protected LibMetrics getStats() { 31 | return stats; 32 | } 33 | 34 | /** 35 | * Returns the name of the background task. 36 | * 37 | * @return the name of the background task 38 | */ 39 | public String getName() { 40 | return name; 41 | } 42 | 43 | @Override 44 | public void run() { 45 | 46 | // Log the task name 47 | LibLog._clogF("I2001", getName()); 48 | long timeStart = System.currentTimeMillis(); 49 | 50 | // Call implemented method 51 | this.onRun(); 52 | 53 | // Hit the counter 54 | getStats().hitCounter("task", getName(), "count"); 55 | long timeTask = System.currentTimeMillis() - timeStart; 56 | getStats().hitCounter(timeTask, "task", getName(), "time"); 57 | 58 | // Clean garbage 59 | System.gc(); 60 | } 61 | 62 | /** 63 | * The implemented logic to run on execution of the BackgroundTask. 64 | */ 65 | public abstract void onRun(); 66 | } 67 | -------------------------------------------------------------------------------- /src/test/java/org/barcodeapi/test/cust/TestMiscCustomer.java: -------------------------------------------------------------------------------- 1 | package org.barcodeapi.test.cust; 2 | 3 | import org.barcodeapi.server.ServerTestBase; 4 | import org.eclipse.jetty.http.HttpStatus; 5 | import org.junit.Assert; 6 | import org.junit.Test; 7 | 8 | /** 9 | * TestMiscCustomer.java 10 | * 11 | * @author Matthew R. Clark (BarcodeAPI.org, 2017-2025) 12 | */ 13 | public class TestMiscCustomer extends ServerTestBase { 14 | 15 | @Test 16 | public void testMiscCustomer_CustomEAN13() { 17 | 18 | apiGet("ean13/8859178779797", "width=2&height=20&format=png"); 19 | 20 | Assert.assertEquals("Response Code", // 21 | HttpStatus.OK_200, getResponseCode()); 22 | 23 | Assert.assertEquals("Code Type", // 24 | "EAN13", getHeader("X-Barcode-Type")); 25 | 26 | Assert.assertEquals("Code Data", // 27 | "8859178779797", getHeader("X-Barcode-Content")); 28 | 29 | Assert.assertEquals("Code Format", // 30 | "image/png;charset=utf-8", getHeader("Content-Type")); 31 | } 32 | 33 | @Test 34 | public void testMiscCustomer_CustomCode128() { 35 | 36 | apiGet("128/RD309874", "dpi=100&rotation=0&color=%23000000"); 37 | 38 | Assert.assertEquals("Response Code", // 39 | HttpStatus.OK_200, getResponseCode()); 40 | 41 | Assert.assertEquals("Code Type", // 42 | "Code128", getHeader("X-Barcode-Type")); 43 | 44 | Assert.assertEquals("Code Data", // 45 | "RD309874", getHeader("X-Barcode-Content")); 46 | 47 | Assert.assertEquals("Code Format", // 48 | "image/png;charset=utf-8", getHeader("Content-Type")); 49 | } 50 | 51 | @Test 52 | public void testMiscCustomer_MACAddress() { 53 | 54 | apiGet("aa:11:bb:22:cc:33"); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /config/types/DataMatrix.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "DataMatrix", 3 | "display": "DataMatrix", 4 | "show": true, 5 | "decode": true, 6 | "cache": true, 7 | "target": [ 8 | "dm", 9 | "data-matrix", 10 | "datamatrix", 11 | "matrix", 12 | "data" 13 | ], 14 | "generator": ".DataMatrixGenerator", 15 | "threads": 2, 16 | "priority": 55, 17 | "cost": { 18 | "basic": 3, 19 | "custom": 5 20 | }, 21 | "checkdigit": 0, 22 | "nonprinting": true, 23 | "example": [ 24 | "Data Matrix Barcode" 25 | ], 26 | "pattern": { 27 | "auto": "^[ !\\\"#$%&\\'()*+,\\-.\\/0-9:;<=>?@A-Z\\[\\]^_`a-z{|}~]{1,2335}$", 28 | "extended": "^[ !\\\"#$%&\\'()*+,\\-.\\/0-9:;<=>?@A-Z\\[\\]^_`a-z{|}~]{1,2335}$" 29 | }, 30 | "description": { 31 | "en": "A 2D data barode whose timing markers are along the the edges." 32 | }, 33 | "wiki": { 34 | "en": "https://en.wikipedia.org/wiki/Data_Matrix" 35 | }, 36 | "options": { 37 | "dpi": { 38 | "name": "Resolution", 39 | "type": "number", 40 | "default": 150, 41 | "min": 50, 42 | "max": 300, 43 | "step": 10 44 | }, 45 | "module": { 46 | "name": "Module", 47 | "type": "number", 48 | "default": 10, 49 | "min": 1, 50 | "max": 35, 51 | "step": 1 52 | }, 53 | "qz": { 54 | "name": "Quiet Zone", 55 | "type": "number", 56 | "default": 3, 57 | "min": 0, 58 | "max": 20, 59 | "step": 1 60 | }, 61 | "bg": { 62 | "name": "Background", 63 | "type": "text", 64 | "default": "ffffff", 65 | "pattern": "[0-9A-F]{6}" 66 | }, 67 | "fg": { 68 | "name": "Foreground", 69 | "type": "text", 70 | "default": "000000", 71 | "pattern": "[0-9A-F]{6}" 72 | } 73 | } 74 | } -------------------------------------------------------------------------------- /src/main/java/org/barcodeapi/server/core/GenerationException.java: -------------------------------------------------------------------------------- 1 | package org.barcodeapi.server.core; 2 | 3 | import org.barcodeapi.server.cache.CachedBarcode; 4 | import org.barcodeapi.server.gen.BarcodeGenerator; 5 | 6 | import com.mclarkdev.tools.liblog.LibLog; 7 | 8 | /** 9 | * GenerationException.java 10 | * 11 | * @author Matthew R. Clark (BarcodeAPI.org, 2017-2024) 12 | */ 13 | public class GenerationException extends Exception { 14 | 15 | private static final long serialVersionUID = 20241123L; 16 | 17 | public enum ExceptionType { 18 | EMPTY(400, "/128/$$@EMPTY$$@"), 19 | 20 | INVALID(400, "/128/$$@INVALID$$@"), 21 | 22 | BLACKLIST(403, "/128/$$@BLACKLIST$$@"), 23 | 24 | CHECKSUM(409, "/128/$$@CHECKSUM$$@"), 25 | 26 | LIMITED(429, "/128/$$@RATE$$@$$@LIMIT$$@"), 27 | 28 | FAILED(500, "/128/$$@FAILED$$@"), 29 | 30 | BUSY(503, "/128/$$@BUSY$$@"); 31 | 32 | private final int status; 33 | 34 | private final CachedBarcode image; 35 | 36 | ExceptionType(int status, String target) { 37 | this.status = status; 38 | try { 39 | this.image = BarcodeGenerator.requestBarcode(target); 40 | } catch (GenerationException e) { 41 | throw LibLog._clog("E0789", e)// 42 | .asException(IllegalStateException.class); 43 | } 44 | } 45 | 46 | public int getStatusCode() { 47 | return status; 48 | } 49 | 50 | public CachedBarcode getBarcodeImage() { 51 | return image; 52 | } 53 | } 54 | 55 | private final ExceptionType type; 56 | 57 | public GenerationException(ExceptionType type, Throwable throwable) { 58 | super(throwable.getCause() != null ? throwable.getCause() : throwable); 59 | 60 | this.type = type; 61 | } 62 | 63 | public ExceptionType getExceptionType() { 64 | 65 | return type; 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /config/types/PDF417.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "PDF417", 3 | "display": "PDF-417", 4 | "show": true, 5 | "decode": true, 6 | "cache": true, 7 | "target": [ 8 | "417", 9 | "pdf417", 10 | "pdf" 11 | ], 12 | "generator": ".PDF417Generator", 13 | "threads": 2, 14 | "priority": 45, 15 | "cost": { 16 | "basic": 2, 17 | "custom": 4 18 | }, 19 | "checkdigit": 0, 20 | "nonprinting": true, 21 | "example": [ 22 | "PDF - 417" 23 | ], 24 | "pattern": { 25 | "auto": "^[ !\\\"#$%&\\'()*+,\\-.\\/0-9:;<=>?@A-Z\\[\\]^_`a-z{|}~]{1,2335}$", 26 | "extended": "^[ !\\\"#$%&\\'()*+,\\-.\\/0-9:;<=>?@A-Z\\[\\]^_`a-z{|}~]{1,2335}$" 27 | }, 28 | "description": { 29 | "en": "A stacked linear barcode most commonly found on identification cards." 30 | }, 31 | "wiki": { 32 | "en": "https://en.wikipedia.org/wiki/PDF417" 33 | }, 34 | "options": { 35 | "dpi": { 36 | "name": "Resolution", 37 | "type": "number", 38 | "default": 150, 39 | "min": 50, 40 | "max": 300, 41 | "step": 10 42 | }, 43 | "module": { 44 | "name": "Module", 45 | "type": "number", 46 | "default": 6, 47 | "min": 1, 48 | "max": 35, 49 | "step": 1 50 | }, 51 | "qz": { 52 | "name": "Quiet Zone", 53 | "type": "number", 54 | "default": 4, 55 | "min": 0, 56 | "max": 20, 57 | "step": 1 58 | }, 59 | "height": { 60 | "name": "Height", 61 | "type": "number", 62 | "default": 6, 63 | "min": 2, 64 | "max": 150, 65 | "step": 5 66 | }, 67 | "bg": { 68 | "name": "Background", 69 | "type": "text", 70 | "default": "ffffff", 71 | "pattern": "[0-9A-F]{6}" 72 | }, 73 | "fg": { 74 | "name": "Foreground", 75 | "type": "text", 76 | "default": "000000", 77 | "pattern": "[0-9A-F]{6}" 78 | } 79 | } 80 | } -------------------------------------------------------------------------------- /resources/nonprinting.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | 8 | 9 | BarcodeAPI.org - Nonprinting Characters 10 | 11 | 13 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 | 30 |
31 | 32 |
33 |

34 |
FEEDBACK & SUPPORT
35 |
support@barcodeapi.org
36 |

37 |
38 |
39 |
40 | 41 | 42 |
43 |
44 |

Non-Printing Characters

45 |

46 | Additional details coming soon. 47 |

48 |
49 | 50 |
51 |

Supported Types

52 |
    53 |
    54 |
    55 |
    56 | 57 | 58 | -------------------------------------------------------------------------------- /src/main/java/org/barcodeapi/server/core/TypeSelector.java: -------------------------------------------------------------------------------- 1 | package org.barcodeapi.server.core; 2 | 3 | import java.util.HashMap; 4 | 5 | import com.mclarkdev.tools.libmetrics.LibMetrics; 6 | 7 | /** 8 | * TypeSelector.java 9 | * 10 | * @author Matthew R. Clark (BarcodeAPI.org, 2017-2024) 11 | */ 12 | public class TypeSelector { 13 | 14 | private static HashMap typeCache = new HashMap<>(); 15 | 16 | static { 17 | 18 | // Loop all known types 19 | CodeTypes types = CodeTypes.inst(); 20 | for (String name : types.getTypes()) { 21 | 22 | // Add types strings to cache 23 | CodeType type = types.getType(name); 24 | for (String target : type.getTargets()) { 25 | typeCache.put(target.toLowerCase(), type); 26 | } 27 | } 28 | } 29 | 30 | /** 31 | * Get a CodeType object by any of its associated string IDs. 32 | * 33 | * Will return null if none are found. 34 | * 35 | * @param codeType 36 | * @return 37 | */ 38 | public static CodeType getTypeFromString(String codeType) { 39 | LibMetrics.hitMethodRunCounter(); 40 | 41 | return ((codeType == null) ? null : typeCache.get(codeType.toLowerCase())); 42 | } 43 | 44 | /** 45 | * Returns a CodeType object best suited for the given data string. 46 | * 47 | * @param data 48 | * @return 49 | */ 50 | public static CodeType getTypeFromData(String data) { 51 | LibMetrics.hitMethodRunCounter(); 52 | 53 | int priority = 0; 54 | CodeType type = null; 55 | CodeTypes types = CodeTypes.inst(); 56 | 57 | for (String name : types.getTypes()) { 58 | CodeType target = types.getType(name); 59 | if (data.matches(target.getPatternAuto())) { 60 | if (target.getPriority() > priority) { 61 | priority = target.getPriority(); 62 | type = target; 63 | } 64 | } 65 | } 66 | 67 | return type; 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /config/types/RoyalMail.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "RoyalMail", 3 | "display": "Royal Mail", 4 | "show": false, 5 | "decode": false, 6 | "cache": true, 7 | "target": [ 8 | "royal", 9 | "royal-mail" 10 | ], 11 | "generator": ".RoyalMailGenerator", 12 | "threads": 2, 13 | "priority": 8, 14 | "cost": { 15 | "basic": 1, 16 | "custom": 1 17 | }, 18 | "checkdigit": 0, 19 | "nonprinting": false, 20 | "example": [ 21 | "11212345612345678" 22 | ], 23 | "pattern": { 24 | "auto": "^[0-9]{1,32}$", 25 | "extended": "^[0-9]{1,32}$" 26 | }, 27 | "description": { 28 | "en": "Royal Mail" 29 | }, 30 | "wiki": { 31 | "en": "https://en.wikipedia.org/wiki/PostBar" 32 | }, 33 | "options": { 34 | "dpi": { 35 | "name": "Resolution", 36 | "type": "number", 37 | "default": 180, 38 | "min": 50, 39 | "max": 300, 40 | "step": 10 41 | }, 42 | "module": { 43 | "name": "Module", 44 | "type": "number", 45 | "default": 10, 46 | "min": 1, 47 | "max": 35, 48 | "step": 1 49 | }, 50 | "qz": { 51 | "name": "Quiet Zone", 52 | "type": "number", 53 | "default": 2, 54 | "min": 0, 55 | "max": 20, 56 | "step": 1 57 | }, 58 | "height": { 59 | "name": "Height", 60 | "type": "number", 61 | "default": 22, 62 | "min": 5, 63 | "max": 150, 64 | "step": 5 65 | }, 66 | "font": { 67 | "name": "Font Size", 68 | "type": "number", 69 | "default": 4, 70 | "min": 0, 71 | "max": 20, 72 | "step": 2 73 | }, 74 | "bg": { 75 | "name": "Background", 76 | "type": "text", 77 | "default": "ffffff", 78 | "pattern": "[0-9A-F]{6}" 79 | }, 80 | "fg": { 81 | "name": "Foreground", 82 | "type": "text", 83 | "default": "000000", 84 | "pattern": "[0-9A-F]{6}" 85 | } 86 | } 87 | } -------------------------------------------------------------------------------- /src/main/java/org/barcodeapi/server/admin/LimiterFlushHandler.java: -------------------------------------------------------------------------------- 1 | package org.barcodeapi.server.admin; 2 | 3 | import java.io.IOException; 4 | 5 | import javax.servlet.http.HttpServletResponse; 6 | 7 | import org.barcodeapi.server.cache.ObjectCache; 8 | import org.barcodeapi.server.core.RequestContext; 9 | import org.barcodeapi.server.core.RestHandler; 10 | import org.json.JSONException; 11 | import org.json.JSONObject; 12 | 13 | /** 14 | * LimiterFlushHandler.java 15 | * 16 | * @author Matthew R. Clark (BarcodeAPI.org, 2017-2024) 17 | */ 18 | public class LimiterFlushHandler extends RestHandler { 19 | 20 | public LimiterFlushHandler() { 21 | super(true, false, false); 22 | } 23 | 24 | @Override 25 | protected void onRequest(RequestContext c, HttpServletResponse r) throws JSONException, IOException { 26 | 27 | // Check & flush caches 28 | String id = c.getRequest().getParameter("id"); 29 | boolean flushed = flush(ObjectCache.CACHE_LIMITERS, id); 30 | 31 | if (!flushed) { 32 | 33 | // Print failure to client 34 | r.setStatus(HttpServletResponse.SC_BAD_REQUEST); 35 | r.setContentType("application/json"); 36 | r.getOutputStream().println((new JSONObject()// 37 | .put("code", 400)// 38 | .put("message", "limiter not found")// 39 | ).toString()); 40 | return; 41 | } 42 | 43 | // Print response to client 44 | r.setStatus(HttpServletResponse.SC_OK); 45 | r.setContentType("application/json"); 46 | r.getOutputStream().println((new JSONObject()// 47 | .put("code", 200)// 48 | .put("message", "limiter flushed")// 49 | ).toString()); 50 | } 51 | 52 | private boolean flush(String cache, String limiter) { 53 | ObjectCache objCache = ObjectCache.getCache(cache); 54 | if ((limiter != null) && objCache.has(limiter)) { 55 | objCache.remove(limiter); 56 | return true; 57 | } 58 | 59 | return false; 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/test/java/org/barcodeapi/test/gen/TestOutputFormat.java: -------------------------------------------------------------------------------- 1 | package org.barcodeapi.test.gen; 2 | 3 | import org.barcodeapi.server.ServerTestBase; 4 | import org.eclipse.jetty.http.HttpStatus; 5 | import org.junit.Assert; 6 | import org.junit.Test; 7 | 8 | /** 9 | * TestOutputFormat.java 10 | * 11 | * @author Matthew R. Clark (BarcodeAPI.org, 2017-2025) 12 | */ 13 | public class TestOutputFormat extends ServerTestBase { 14 | 15 | @Test 16 | public void testOutputFormat_default() { 17 | 18 | apiGet("$12.34"); 19 | 20 | Assert.assertEquals("Response Code", // 21 | HttpStatus.OK_200, getResponseCode()); 22 | 23 | Assert.assertEquals("Code Format", // 24 | "image/png;charset=utf-8", getHeader("Content-Type")); 25 | } 26 | 27 | @Test 28 | public void testOutputFormat_JSON() { 29 | 30 | // Preconditions, set request header 31 | setHeader("Accept", "application/json"); 32 | 33 | apiGet("$12.34"); 34 | 35 | Assert.assertEquals("Response Code", // 36 | HttpStatus.OK_200, getResponseCode()); 37 | 38 | Assert.assertEquals("Code Format", // 39 | "application/json;charset=utf-8", getHeader("Content-Type")); 40 | } 41 | 42 | @Test 43 | public void testOutputFormat_HTML() { 44 | 45 | // Preconditions, set request header 46 | setHeader("Accept", "text/html"); 47 | 48 | apiGet("$12.34"); 49 | 50 | Assert.assertEquals("Response Code", // 51 | HttpStatus.OK_200, getResponseCode()); 52 | 53 | Assert.assertEquals("Code Format", // 54 | "text/html;charset=utf-8", getHeader("Content-Type")); 55 | } 56 | 57 | @Test 58 | public void testOutputFormat_PNG() { 59 | 60 | // Preconditions, set request header 61 | setHeader("Accept", "image/png"); 62 | 63 | apiGet("$12.34"); 64 | 65 | Assert.assertEquals("Response Code", // 66 | HttpStatus.OK_200, getResponseCode()); 67 | 68 | Assert.assertEquals("Code Format", // 69 | "image/png;charset=utf-8", getHeader("Content-Type")); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/test/java/org/barcodeapi/test/api/TestServerHeaders.java: -------------------------------------------------------------------------------- 1 | package org.barcodeapi.test.api; 2 | 3 | import org.barcodeapi.server.ServerTestBase; 4 | import org.eclipse.jetty.http.HttpStatus; 5 | import org.junit.Assert; 6 | import org.junit.Test; 7 | 8 | /** 9 | * TestServerHeaders.java 10 | * 11 | * @author Matthew R. Clark (BarcodeAPI.org, 2017-2025) 12 | */ 13 | public class TestServerHeaders extends ServerTestBase { 14 | 15 | @Test 16 | public void testServerHeaders_testServerNameHeader() { 17 | 18 | apiGet("test-headers"); 19 | 20 | Assert.assertEquals("Response Code", // 21 | HttpStatus.OK_200, getResponseCode()); 22 | 23 | Assert.assertEquals("Server Name Header", // 24 | "BarcodeAPI.org", getHeader("Server")); 25 | } 26 | 27 | @Test 28 | public void testServerHeaders_testServerNodeHeader() { 29 | 30 | apiGet("test-headers"); 31 | 32 | Assert.assertEquals("Response Code", // 33 | HttpStatus.OK_200, getResponseCode()); 34 | 35 | Assert.assertNotNull("Server Node Header", getHeader("Server-Node")); 36 | } 37 | 38 | @Test 39 | public void testServerHeaders_testServerCacheHeaders() { 40 | 41 | apiGet("test-headers"); 42 | 43 | Assert.assertEquals("Response Code", // 44 | HttpStatus.OK_200, getResponseCode()); 45 | 46 | Assert.assertEquals("Access Control Max Age Header", // 47 | "86400", getHeader("Access-Control-Max-Age")); 48 | 49 | Assert.assertEquals("Access Control Allow Header", // 50 | "true", getHeader("Access-Control-Allow-Credentials")); 51 | 52 | Assert.assertEquals("Access Control Origin Header", // 53 | "*", getHeader("Access-Control-Allow-Origin")); 54 | } 55 | 56 | @Test 57 | public void testServerHeaders_testServerTokens() { 58 | 59 | apiGet("test-headers"); 60 | 61 | Assert.assertEquals("Response Code", // 62 | HttpStatus.OK_200, getResponseCode()); 63 | 64 | Assert.assertNotNull("RateLimit Token Header", getHeader("X-RateLimit-Tokens")); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/main/resources/strings/codes.en_US.properties: -------------------------------------------------------------------------------- 1 | I0001=Initializing API server... 2 | I0002=Starting system tasks... 3 | I0003=Starting server... 4 | 5 | I0011=Creating Jetty API Server 6 | I0012=Initializing static resource handler 7 | 8 | I0021=Initializing handler: %s 9 | 10 | I0031=Registering shutdown hook. 11 | I0036=Setting up task: %s 12 | 13 | I0042=Saving cache snapshot. 14 | I0043=Saved cache snapshots. (%d ips, %d keys, %d sessions) 15 | E0049=Failed to save cache snapshot. 16 | 17 | E0051=Failed initializing CodeType. 18 | 19 | I0061=Loading type config: %s 20 | I0062=Initialized CodeType: %s (max: %d) 21 | 22 | I0101=Initializing cache: %s 23 | 24 | I0180=Creating new generator: %s 25 | I0181=Destroying generator: %s 26 | 27 | I2001=Running task: %s 28 | 29 | I2201=Cache Info: Stale [ %d ] Active [ %d ] 30 | I2401=Session Info: Stale [ %d ] Active [ %d ] 31 | I2601=Limiter Info: Stale [ %d ] Active [ %d ] 32 | I2602=Saved cache snapshot. (%s : %d entries) 33 | I2611=Share Cache: Stale [ %d ] Active [ %d ] 34 | I2621=Tokens minted: %.2f 35 | 36 | I2604=Uploading metrics. 37 | 38 | I4001=%s : %s : %s : %s 39 | I4002=%s : %s : %s : %s : %s 40 | 41 | I0601=Rendered [ %s ] with [ %s ] size [ %dB ] in [ %dms ] 42 | 43 | E0006=Failed to load app config. 44 | 45 | E0008=Failed to start server. 46 | E0009=Failed to stop server. 47 | 48 | E0039=Failed to setup task. 49 | 50 | E0059=Failed setting up code generator. 51 | E0069=Failed loading type config. 52 | 53 | E0509=Failed to generate bulk barcodes. (%s) 54 | 55 | E0539=Failed to add request to share. (%s : %s) 56 | 57 | E0608=Failed to decode URL. 58 | E0609=Client is out of tokens: %s 59 | 60 | E0699=Encountered an error while handling the request. 61 | 62 | E2602=Failed to save cache snapshot. 63 | E2603=Failed to load cache snapshot. 64 | 65 | E2609=Failed to upload metrics: %s 66 | 67 | E6009=Failed [ %s ] reason [ %s ] 68 | 69 | E0789=Failed to create default barcodes. 70 | -------------------------------------------------------------------------------- /resources/ext/js/types.js: -------------------------------------------------------------------------------- 1 | // 2 | // BarcodeAPI.org, 2017-2025 3 | // types.js // types.html 4 | // 5 | 6 | window.onhashchange = function() { 7 | location.reload(); 8 | } 9 | 10 | function init() { 11 | 12 | fetch('/type/') 13 | .then(response => { 14 | return response.json(); 15 | }).then((data) => { 16 | 17 | data.sort((a, b) => { 18 | if (a.name < b.name) { 19 | return -1; 20 | } 21 | if (a.name > b.name) { 22 | return 1; 23 | } 24 | return 0; 25 | }); 26 | 27 | return data; 28 | }).then(loadTypes); 29 | } 30 | 31 | function loadTypes(data) { 32 | 33 | for (var x in data) { 34 | addType(data[x]); 35 | } 36 | 37 | delTemplate(); 38 | 39 | // Log tracking event 40 | var setupMillis = ((new Date()) - timeStart); 41 | trackingEvent("AppEvents", "AppLoad", "Types", setupMillis); 42 | } 43 | 44 | function addType(type) { 45 | 46 | var target = type.targets[0]; 47 | 48 | var info = document.getElementById("barcode-template").cloneNode(true); 49 | info.setAttribute("id", "barcode-type-" + target); 50 | 51 | var link = ("/api/" + target + "/" + type.example[0]); 52 | info.querySelector(".type-example").src = link; 53 | 54 | info.querySelector(".type-name").innerHTML = type.display; 55 | info.querySelector(".type-target").innerHTML = ('/' + target + '/'); 56 | info.querySelector(".type-cost-basic").innerHTML = type.costBasic; 57 | info.querySelector(".type-cost-custom").innerHTML = type.costCustom; 58 | 59 | var homeLink = ("index.html#" + target); 60 | info.querySelector(".type-gen").href = homeLink; 61 | 62 | var moreLink = ("type.html#" + target); 63 | info.querySelector(".type-more").href = moreLink; 64 | info.querySelector(".type-example-link").href = moreLink; 65 | 66 | document.getElementById("barcode-types").append(info); 67 | } 68 | 69 | function delTemplate() { 70 | document.getElementById("barcode-types")// 71 | .removeChild(document.getElementById("barcode-template")); 72 | } 73 | -------------------------------------------------------------------------------- /src/main/java/org/barcodeapi/server/cache/LimiterCache.java: -------------------------------------------------------------------------------- 1 | package org.barcodeapi.server.cache; 2 | 3 | import org.barcodeapi.core.Config; 4 | import org.barcodeapi.core.Config.Cfg; 5 | import org.json.JSONObject; 6 | 7 | import com.mclarkdev.tools.libmetrics.LibMetrics; 8 | 9 | /** 10 | * LimiterCache.java 11 | * 12 | * @author Matthew R. Clark (BarcodeAPI.org, 2017-2025) 13 | */ 14 | public class LimiterCache { 15 | 16 | // Default values for new limiters 17 | private static final int DEFLIMIT_RATE; 18 | private static final boolean DEFLIMIT_ENFORCE; 19 | 20 | // Local instance of the limiters cache 21 | private static final ObjectCache _LIMITERS; 22 | 23 | static { 24 | 25 | // Load plan from configuration 26 | JSONObject freePlan = Config// 27 | .get(Cfg.Plans).getJSONObject("free"); 28 | 29 | // Free plan defaults 30 | DEFLIMIT_RATE = freePlan.getInt("limit"); 31 | DEFLIMIT_ENFORCE = freePlan.getBoolean("enforce"); 32 | 33 | // Get the limiter cache 34 | _LIMITERS = ObjectCache.getCache(ObjectCache.CACHE_LIMITERS); 35 | } 36 | 37 | public static CachedLimiter getLimiter(Subscriber sub, String address) { 38 | LibMetrics.hitMethodRunCounter(); 39 | 40 | // User ID is customer or address 41 | String userID = (sub != null) ? sub.getCustomer() : address; 42 | 43 | // Determine if limiter exists 44 | CachedLimiter limiter; 45 | if (_LIMITERS.has(userID)) { 46 | 47 | // Get the existing limiter from the cache 48 | limiter = (CachedLimiter) _LIMITERS.get(userID); 49 | } else { 50 | 51 | // Determine enforce / limits for the new limiter 52 | int limit = (sub != null) ? sub.getLimit() : DEFLIMIT_RATE; 53 | boolean enforce = (sub != null) ? sub.isEnforced() : DEFLIMIT_ENFORCE; 54 | 55 | // Create a new limiter and add it to the cache 56 | limiter = new CachedLimiter(enforce, userID, limit); 57 | _LIMITERS.put(userID, limiter); 58 | } 59 | 60 | // Return the limiter 61 | return limiter; 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/test/java/org/barcodeapi/test/api/TestSessionHandler.java: -------------------------------------------------------------------------------- 1 | package org.barcodeapi.test.api; 2 | 3 | import org.barcodeapi.server.ServerTestBase; 4 | import org.eclipse.jetty.http.HttpStatus; 5 | import org.json.JSONObject; 6 | import org.junit.Assert; 7 | import org.junit.Ignore; 8 | import org.junit.Test; 9 | 10 | /** 11 | * TestServerTypesHandler.java 12 | * 13 | * @author Matthew R. Clark (BarcodeAPI.org, 2017-2025) 14 | */ 15 | public class TestSessionHandler extends ServerTestBase { 16 | 17 | @Test 18 | @Ignore 19 | public void testServer_TestGetSessionEndpoint() { 20 | 21 | serverGet("/session/"); 22 | 23 | Assert.assertEquals("Response Code", // 24 | HttpStatus.OK_200, getResponseCode()); 25 | 26 | Assert.assertEquals("Response Header", // 27 | "application/json;charset=utf-8", getHeader("Content-Type")); 28 | 29 | JSONObject response = getResponseAsJSON(); 30 | 31 | // Key is set 32 | Assert.assertEquals("Session Key", // 33 | 36, response.getString("key").length()); 34 | 35 | // Created time of session 36 | Assert.assertTrue("Creation Time", // 37 | response.getLong("created") <= System.currentTimeMillis()); 38 | 39 | // Expiration time of session 40 | Assert.assertTrue("Expiration Time", // 41 | response.getLong("expires") > System.currentTimeMillis()); 42 | 43 | // Last touched time 44 | Assert.assertTrue("Last Touched", // 45 | response.getLong("last") <= System.currentTimeMillis()); 46 | 47 | // Total hit count for session 48 | Assert.assertTrue("Hit Count", // 49 | response.getLong("count") >= 1); 50 | 51 | // Addresses list [] 52 | 53 | // Request list [] 54 | 55 | } 56 | 57 | @Test 58 | @Ignore 59 | public void testServer_TestDeleteSessionEndpoint() { 60 | 61 | serverGet("/session/"); 62 | JSONObject response = getResponseAsJSON(); 63 | String key = response.getString("key"); 64 | 65 | setHeader("session", key); 66 | serverDelete("/session/"); 67 | 68 | Assert.assertEquals("Response Code", // 69 | HttpStatus.OK_200, getResponseCode()); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/main/java/org/barcodeapi/server/tasks/StatsDumpTask.java: -------------------------------------------------------------------------------- 1 | package org.barcodeapi.server.tasks; 2 | 3 | import java.net.HttpURLConnection; 4 | import java.net.URL; 5 | import java.net.URLConnection; 6 | 7 | import org.barcodeapi.server.core.BackgroundTask; 8 | 9 | import com.mclarkdev.tools.libargs.LibArgs; 10 | import com.mclarkdev.tools.liblog.LibLog; 11 | 12 | /** 13 | * StatsDumpTask.java 14 | * 15 | * A background task which periodically logs the server stats. 16 | * 17 | * @author Matthew R. Clark (BarcodeAPI.org, 2017-2024) 18 | */ 19 | public class StatsDumpTask extends BackgroundTask { 20 | 21 | private static final String _TELEM_TARGET = "https://barcodeapi.org/stats/upload"; 22 | private static final boolean _TELEM_ENABLED = (!LibArgs.instance().getBoolean("no-telemetry")); 23 | 24 | public StatsDumpTask() { 25 | super(); 26 | } 27 | 28 | @Override 29 | public void onRun() { 30 | 31 | // Get and log metrics data 32 | String data = getStats().getDetails().toString(); 33 | LibLog.log("stats", data); 34 | 35 | // Upload if enabled 36 | if (_TELEM_ENABLED) { 37 | sendUsageReport(data); 38 | } 39 | } 40 | 41 | /** 42 | * Send usage statistics to a remote server to monitoring. 43 | * 44 | * @param data usage statistics 45 | */ 46 | private void sendUsageReport(String data) { 47 | 48 | LibLog._clog("I2604"); 49 | 50 | try { 51 | 52 | // Create HTTP client 53 | URL url = new URL(_TELEM_TARGET); 54 | URLConnection con = url.openConnection(); 55 | HttpURLConnection http = (HttpURLConnection) con; 56 | 57 | // Set request options 58 | http.setDoOutput(true); 59 | http.setRequestMethod("POST"); 60 | http.setFixedLengthStreamingMode(data.length()); 61 | http.setRequestProperty("Content-Type", "application/json; charset=UTF-8"); 62 | 63 | // Connect 64 | http.connect(); 65 | 66 | // Write metrics data 67 | http.getOutputStream().write(data.getBytes()); 68 | http.getResponseCode(); 69 | 70 | } catch (Exception | Error e) { 71 | 72 | LibLog._clog("E2609", e); 73 | } 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/main/java/org/barcodeapi/server/admin/CacheDumpHandler.java: -------------------------------------------------------------------------------- 1 | package org.barcodeapi.server.admin; 2 | 3 | import java.io.IOException; 4 | import java.util.Map; 5 | 6 | import javax.servlet.http.HttpServletResponse; 7 | 8 | import org.barcodeapi.server.cache.CachedObject; 9 | import org.barcodeapi.server.cache.ObjectCache; 10 | import org.barcodeapi.server.core.CodeTypes; 11 | import org.barcodeapi.server.core.RequestContext; 12 | import org.barcodeapi.server.core.RestHandler; 13 | import org.json.JSONArray; 14 | import org.json.JSONException; 15 | import org.json.JSONObject; 16 | 17 | /** 18 | * CacheDumpHandler.java 19 | * 20 | * @author Matthew R. Clark (BarcodeAPI.org, 2017-2024) 21 | */ 22 | public class CacheDumpHandler extends RestHandler { 23 | 24 | public CacheDumpHandler() { 25 | super(true, false, false); 26 | } 27 | 28 | @Override 29 | protected void onRequest(RequestContext c, HttpServletResponse r) throws JSONException, IOException { 30 | 31 | // Loop all caches 32 | JSONObject types = new JSONObject(); 33 | for (String type : CodeTypes.inst().getTypes()) { 34 | 35 | // Loop all cached objects 36 | JSONArray entries = new JSONArray(); 37 | ObjectCache cache = ObjectCache.getCache(type); 38 | for (Map.Entry entry : cache.raw().entrySet()) { 39 | 40 | CachedObject obj = entry.getValue(); 41 | 42 | entries.put(new JSONObject()// 43 | .put("text", entry.getKey())// 44 | .put("hits", obj.getAccessCount())// 45 | .put("created", obj.getTimeCreated())// 46 | .put("expires", obj.getTimeExpires())// 47 | .put("shortLived", obj.isShortLived())); 48 | } 49 | 50 | // Add to master element 51 | types.put(type, entries); 52 | } 53 | 54 | // Print response to client 55 | r.setStatus(HttpServletResponse.SC_OK); 56 | r.setContentType("application/json"); 57 | r.setHeader("Content-Disposition", "attachment, filename=cache.json"); 58 | r.getOutputStream().println((new JSONObject()// 59 | .put("code", 200)// 60 | .put("cache", types)// 61 | ).toString()); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /config/types/USPSMail.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "USPSMail", 3 | "display": "USPS Mail", 4 | "show": false, 5 | "decode": false, 6 | "cache": true, 7 | "target": [ 8 | "usps", 9 | "uspsmail", 10 | "intelligent-mail" 11 | ], 12 | "generator": ".USPSMailGenerator", 13 | "threads": 2, 14 | "priority": 10, 15 | "cost": { 16 | "basic": 1, 17 | "custom": 2 18 | }, 19 | "checkdigit": 0, 20 | "nonprinting": false, 21 | "example": [ 22 | "0123456709498765432101234567891" 23 | ], 24 | "pattern": { 25 | "auto": "^[0-9]{1,32}$", 26 | "extended": "^[0-9]{1,32}$" 27 | }, 28 | "description": { 29 | "en": "Used by the United States Postal Service for Intelligent Mail sorting." 30 | }, 31 | "wiki": { 32 | "en": "https://en.wikipedia.org/wiki/Intelligent_Mail_barcode" 33 | }, 34 | "options": { 35 | "dpi": { 36 | "name": "Resolution", 37 | "type": "number", 38 | "default": 180, 39 | "min": 50, 40 | "max": 300, 41 | "step": 10 42 | }, 43 | "module": { 44 | "name": "Module", 45 | "type": "number", 46 | "default": 10, 47 | "min": 1, 48 | "max": 35, 49 | "step": 1 50 | }, 51 | "qz": { 52 | "name": "Quiet Zone", 53 | "type": "number", 54 | "default": 2, 55 | "min": 0, 56 | "max": 20, 57 | "step": 1 58 | }, 59 | "height": { 60 | "name": "Height", 61 | "type": "number", 62 | "default": 22, 63 | "min": 5, 64 | "max": 150, 65 | "step": 5 66 | }, 67 | "font": { 68 | "name": "Font Size", 69 | "type": "number", 70 | "default": 4, 71 | "min": 0, 72 | "max": 20, 73 | "step": 2 74 | }, 75 | "text": { 76 | "name": "Text Placement", 77 | "type": "option", 78 | "default": "bottom", 79 | "options": [ 80 | "bottom", 81 | "top", 82 | "none" 83 | ] 84 | }, 85 | "bg": { 86 | "name": "Background", 87 | "type": "text", 88 | "default": "ffffff", 89 | "pattern": "[0-9A-F]{6}" 90 | }, 91 | "fg": { 92 | "name": "Foreground", 93 | "type": "text", 94 | "default": "000000", 95 | "pattern": "[0-9A-F]{6}" 96 | } 97 | } 98 | } -------------------------------------------------------------------------------- /src/main/java/org/barcodeapi/server/api/SessionHandler.java: -------------------------------------------------------------------------------- 1 | package org.barcodeapi.server.api; 2 | 3 | import java.io.IOException; 4 | 5 | import javax.servlet.http.HttpServletResponse; 6 | 7 | import org.barcodeapi.server.core.RequestContext; 8 | import org.barcodeapi.server.core.RestHandler; 9 | import org.barcodeapi.server.core.SessionHelper; 10 | import org.json.JSONObject; 11 | 12 | /** 13 | * SessionDetailsHandler.java 14 | * 15 | * @author Matthew R. Clark (BarcodeAPI.org, 2017-2024) 16 | */ 17 | public class SessionHandler extends RestHandler { 18 | 19 | public SessionHandler() { 20 | super( 21 | // Authentication not required 22 | false, 23 | // Do not use client rate limit 24 | false, 25 | // Do not create new session 26 | false); 27 | } 28 | 29 | @Override 30 | protected void onRequest(RequestContext c, HttpServletResponse r) throws IOException { 31 | 32 | if (!c.hasSession()) { 33 | r.setStatus(HttpServletResponse.SC_BAD_REQUEST); 34 | r.setContentType("application/json"); 35 | r.getOutputStream().println((new JSONObject() // 36 | .put("code", 400)// 37 | .put("message", "no session in request")// 38 | ).toString()); 39 | return; 40 | } 41 | 42 | switch (c.getMethod()) { 43 | case "GET": 44 | doGET(c, r); 45 | break; 46 | 47 | case "DELETE": 48 | doDELETE(c, r); 49 | break; 50 | 51 | default: 52 | r.setStatus(HttpServletResponse.SC_BAD_REQUEST); 53 | break; 54 | } 55 | } 56 | 57 | protected void doGET(RequestContext c, HttpServletResponse r) throws IOException { 58 | 59 | // Print response to client 60 | r.setStatus(HttpServletResponse.SC_OK); 61 | r.setContentType("application/json"); 62 | r.getOutputStream().println(// 63 | c.getSession().asJSON().toString(4)); 64 | } 65 | 66 | protected void doDELETE(RequestContext c, HttpServletResponse r) throws IOException { 67 | 68 | // Attempt session deletion 69 | boolean deleted = SessionHelper.deleteSession(c.getSession().getKey()); 70 | 71 | // Print response to client 72 | r.setStatus((deleted) ? // 73 | HttpServletResponse.SC_OK : HttpServletResponse.SC_BAD_REQUEST); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /.github/workflows/docker-publish.yml: -------------------------------------------------------------------------------- 1 | name: Docker 2 | 3 | # This workflow uses actions that are not certified by GitHub. 4 | # They are provided by a third-party and are governed by 5 | # separate terms of service, privacy policy, and support 6 | # documentation. 7 | 8 | on: 9 | schedule: 10 | - cron: '29 3 5 * *' 11 | push: 12 | branches: [ master ] 13 | # Publish semver tags as releases. 14 | tags: [ 'v*.*.*' ] 15 | pull_request: 16 | branches: [ master ] 17 | 18 | env: 19 | # Use docker.io for Docker Hub if empty 20 | REGISTRY: ghcr.io 21 | # github.repository as / 22 | IMAGE_NAME: ${{ github.repository }} 23 | 24 | 25 | jobs: 26 | build: 27 | 28 | runs-on: ubuntu-latest 29 | permissions: 30 | contents: read 31 | packages: write 32 | 33 | steps: 34 | - name: Checkout repository 35 | uses: actions/checkout@v2 36 | 37 | # Login against a Docker registry except on PR 38 | # https://github.com/docker/login-action 39 | - name: Log into registry ${{ env.REGISTRY }} 40 | if: github.event_name != 'pull_request' 41 | uses: docker/login-action@28218f9b04b4f3f62068d7b6ce6ca5b26e35336c 42 | with: 43 | registry: ${{ env.REGISTRY }} 44 | username: ${{ github.actor }} 45 | password: ${{ secrets.GITHUB_TOKEN }} 46 | 47 | # Extract metadata (tags, labels) for Docker 48 | # https://github.com/docker/metadata-action 49 | - name: Extract Docker metadata 50 | id: meta 51 | uses: docker/metadata-action@98669ae865ea3cffbcbaa878cf57c20bbf1c6c38 52 | with: 53 | images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} 54 | 55 | # Build and push Docker image with Buildx (don't push on PR) 56 | # https://github.com/docker/build-push-action 57 | - name: Build and push Docker image 58 | uses: docker/build-push-action@ad44023a93711e3deb337508980b4b5e9bcdc5dc 59 | with: 60 | context: . 61 | push: ${{ github.event_name != 'pull_request' }} 62 | tags: ${{ steps.meta.outputs.tags }} 63 | labels: ${{ steps.meta.outputs.labels }} 64 | -------------------------------------------------------------------------------- /resources/license.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | 8 | 9 | BarcodeAPI.org - License 10 | 11 | 13 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 |
    28 | 29 |
    30 | 31 |
    32 |

    33 |
    FEEDBACK & SUPPORT
    34 |
    support@barcodeapi.org
    35 |

    36 |
    37 |
    38 |
    39 | 40 |
    41 |
    42 |

    License

    43 |
    44 | BarcodeAPI.org
    45 | Copyright 2017-2024
    46 | 
    47 | 
    48 | Licensed under the Apache License, Version 2.0 (the "License");
    49 | you may not use this file except in compliance with the License.
    50 | You may obtain a copy of the License at
    51 | 
    52 | http://www.apache.org/licenses/LICENSE-2.0
    53 | 
    54 | Unless required by applicable law or agreed to in writing, software
    55 | distributed under the License is distributed on an "AS IS" BASIS,
    56 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    57 | See the License for the specific language governing permissions and
    58 | limitations under the License.
    59 | 				
    60 |
    61 |
    62 |
    63 | 64 | 65 | -------------------------------------------------------------------------------- /src/main/java/org/barcodeapi/server/core/GeneratorPoolController.java: -------------------------------------------------------------------------------- 1 | package org.barcodeapi.server.core; 2 | 3 | import java.lang.reflect.Constructor; 4 | 5 | import org.barcodeapi.server.gen.CodeGenerator; 6 | 7 | import com.mclarkdev.tools.liblog.LibLog; 8 | import com.mclarkdev.tools.libmetrics.LibMetrics; 9 | import com.mclarkdev.tools.libobjectpooler.LibObjectPoolerController; 10 | 11 | /** 12 | * GeneratorPoolController.java 13 | * 14 | * Manages the creation and destruction of pooled code generators. 15 | * 16 | * @author Matthew R. Clark (BarcodeAPI.org, 2017-2024) 17 | */ 18 | public class GeneratorPoolController implements LibObjectPoolerController { 19 | 20 | private final CodeType type; 21 | private final Class clazz; 22 | private final Constructor constructor; 23 | 24 | @SuppressWarnings("unchecked") 25 | public GeneratorPoolController(CodeType type) { 26 | 27 | try { 28 | 29 | this.type = type; 30 | this.clazz = (Class) Class.forName(type.getGeneratorClass()); 31 | this.constructor = clazz.getDeclaredConstructor(CodeType.class); 32 | 33 | } catch (Exception e) { 34 | 35 | // Log the initialization failure 36 | throw LibLog._clog("E0059", e).asException(); 37 | } 38 | } 39 | 40 | @Override 41 | public CodeGenerator onCreate() { 42 | LibMetrics.hitMethodRunCounter(); 43 | 44 | // Log the object creation 45 | LibLog._clogF("I0180", type.getName()); 46 | LibMetrics.instance().hitCounter("generators", type.getName(), "pool", "created"); 47 | 48 | try { 49 | 50 | // Create and return new object instance 51 | return constructor.newInstance(new Object[] { type }); 52 | } catch (Exception | Error e) { 53 | 54 | // Log the failure 55 | LibLog._clog("E0059", e); 56 | return null; 57 | } 58 | } 59 | 60 | @Override 61 | public void onDestroy(CodeGenerator t) { 62 | LibMetrics.hitMethodRunCounter(); 63 | 64 | // Log the object destruction 65 | LibLog._clogF("I0181", type.getName()); 66 | LibMetrics.instance().hitCounter("generators", type.getName(), "pool", "destroyed"); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/main/java/org/barcodeapi/server/gen/impl/DefaultZXingProvider.java: -------------------------------------------------------------------------------- 1 | package org.barcodeapi.server.gen.impl; 2 | 3 | import java.io.ByteArrayOutputStream; 4 | import java.util.HashMap; 5 | import java.util.Map; 6 | 7 | import org.barcodeapi.server.core.CodeType; 8 | import org.barcodeapi.server.gen.BarcodeRequest; 9 | import org.barcodeapi.server.gen.CodeGenerator; 10 | import org.json.JSONObject; 11 | 12 | import com.google.zxing.BarcodeFormat; 13 | import com.google.zxing.EncodeHintType; 14 | import com.google.zxing.Writer; 15 | import com.google.zxing.client.j2se.MatrixToImageWriter; 16 | import com.google.zxing.common.BitMatrix; 17 | 18 | public abstract class DefaultZXingProvider extends CodeGenerator { 19 | 20 | private final Writer generator; 21 | 22 | private final BarcodeFormat format; 23 | 24 | public DefaultZXingProvider(CodeType codeType, BarcodeFormat format, Writer bean) { 25 | super(codeType); 26 | 27 | this.format = format; 28 | this.generator = bean; 29 | } 30 | 31 | @Override 32 | public byte[] onRender(BarcodeRequest request) throws Exception { 33 | 34 | JSONObject options = request.getOptions(); 35 | 36 | HashMap defaults = // 37 | request.getType().getDefaults(); 38 | 39 | int size = options.optInt("size", // 40 | (Integer) defaults.getOrDefault("size", 275)); 41 | 42 | int qz = options.optInt("qz", // 43 | (Integer) defaults.getOrDefault("qz", 4)); 44 | 45 | // String correction = options.optString("correction", // 46 | // (String) defaults.getOrDefault("correction", "M")); 47 | 48 | Map hintsMap = new HashMap<>(); 49 | // hintsMap.put(EncodeHintType.CHARACTER_SET, "utf-8"); 50 | // hintsMap.put(EncodeHintType.ERROR_CORRECTION, // 51 | // ErrorCorrectionLevel.valueOf(correction)); 52 | hintsMap.put(EncodeHintType.MARGIN, qz); 53 | 54 | synchronized (generator) { 55 | 56 | BitMatrix bitMatrix = generator.encode(// 57 | request.getData(), format, size, size, hintsMap); 58 | 59 | ByteArrayOutputStream out = new ByteArrayOutputStream(); 60 | MatrixToImageWriter.writeToStream(bitMatrix, "png", out); 61 | 62 | return out.toByteArray(); 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/test/java/org/barcodeapi/test/api/TestLimiterHandler.java: -------------------------------------------------------------------------------- 1 | package org.barcodeapi.test.api; 2 | 3 | import java.io.IOException; 4 | 5 | import org.barcodeapi.server.ServerTestBase; 6 | import org.eclipse.jetty.http.HttpStatus; 7 | import org.json.JSONObject; 8 | import org.junit.Assert; 9 | import org.junit.Test; 10 | 11 | import com.mclarkdev.tools.libextras.LibExtrasStreams; 12 | 13 | /** 14 | * TestServerTypesHandler.java 15 | * 16 | * @author Matthew R. Clark (BarcodeAPI.org, 2017-2025) 17 | */ 18 | public class TestLimiterHandler extends ServerTestBase { 19 | 20 | @Test 21 | public void testServer_TestLimiterEndpoint() { 22 | 23 | setHeader("Authorization", "Token=appTest"); 24 | serverGet("/limiter/"); 25 | 26 | Assert.assertEquals("Response Code", // 27 | HttpStatus.OK_200, getResponseCode()); 28 | 29 | Assert.assertEquals("Response Header", // 30 | "application/json;charset=utf-8", getHeader("Content-Type")); 31 | 32 | try { 33 | 34 | String response = LibExtrasStreams.readStream(getResponse()); 35 | JSONObject parsed = new JSONObject(response); 36 | 37 | // Caller is set 38 | Assert.assertEquals("Caller", // 39 | "AppTest", parsed.getString("caller")); 40 | 41 | // Creation time of limiter 42 | Assert.assertTrue("Created", // 43 | parsed.getLong("created") <= System.currentTimeMillis()); 44 | 45 | // Expiration time of limiter 46 | Assert.assertTrue("Expiration time", // 47 | parsed.getLong("expires") > System.currentTimeMillis()); 48 | 49 | // Last touched time 50 | Assert.assertTrue("Last Touched", // 51 | parsed.getLong("last") <= System.currentTimeMillis()); 52 | 53 | // Limiter enforcement 54 | Assert.assertTrue("Limiter Enforcement", // 55 | parsed.getBoolean("enforce") == true); 56 | 57 | // Token limit is set 58 | Assert.assertTrue("Token Limit", // 59 | parsed.getDouble("tokenLimit") == 1000); 60 | 61 | // Token count is set 62 | Assert.assertTrue("Token Count", // 63 | parsed.getDouble("tokenCount") >= 100); 64 | 65 | // Token spend is set 66 | Assert.assertTrue("Token Spend", // 67 | parsed.getDouble("tokenSpend") >= 0); 68 | 69 | } catch (IOException e) { 70 | Assert.fail(e.getMessage()); 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /config/types/CODABAR.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "CODABAR", 3 | "display": "CODABAR", 4 | "show": true, 5 | "decode": false, 6 | "cache": true, 7 | "target": [ 8 | "codabar" 9 | ], 10 | "generator": ".CodabarGenerator", 11 | "threads": 2, 12 | "priority": 78, 13 | "cost": { 14 | "basic": 1, 15 | "custom": 3 16 | }, 17 | "checkdigit": 0, 18 | "nonprinting": false, 19 | "example": [ 20 | "1234567890" 21 | ], 22 | "pattern": { 23 | "auto": "^[0-9:$]{4,12}$", 24 | "extended": "^[0-9\\-:$\\/.+]{1,22}$" 25 | }, 26 | "description": { 27 | "en": "An early barcode design, initially used with dot-matrix printers." 28 | }, 29 | "wiki": { 30 | "en": "https://en.wikipedia.org/wiki/Codabar" 31 | }, 32 | "options": { 33 | "dpi": { 34 | "name": "Resolution", 35 | "type": "number", 36 | "default": 150, 37 | "min": 50, 38 | "max": 300, 39 | "step": 10 40 | }, 41 | "module": { 42 | "name": "Module", 43 | "type": "number", 44 | "default": 4, 45 | "min": 1, 46 | "max": 35, 47 | "step": 1 48 | }, 49 | "qz": { 50 | "name": "Quiet Zone", 51 | "type": "number", 52 | "default": 4, 53 | "min": 0, 54 | "max": 20, 55 | "step": 1 56 | }, 57 | "height": { 58 | "name": "Height", 59 | "type": "number", 60 | "default": 22, 61 | "min": 5, 62 | "max": 150, 63 | "step": 5 64 | }, 65 | "font": { 66 | "name": "Font Size", 67 | "type": "number", 68 | "default": 5, 69 | "min": 0, 70 | "max": 20, 71 | "step": 2 72 | }, 73 | "text": { 74 | "name": "Text Placement", 75 | "type": "option", 76 | "default": "bottom", 77 | "options": [ 78 | "bottom", 79 | "top", 80 | "none" 81 | ] 82 | }, 83 | "pattern": { 84 | "name": "Pattern", 85 | "type": "text", 86 | "default": "_", 87 | "pattern": "[ _]+" 88 | }, 89 | "bg": { 90 | "name": "Background", 91 | "type": "text", 92 | "default": "ffffff", 93 | "pattern": "[0-9A-F]{6}" 94 | }, 95 | "fg": { 96 | "name": "Foreground", 97 | "type": "text", 98 | "default": "000000", 99 | "pattern": "[0-9A-F]{6}" 100 | } 101 | } 102 | } -------------------------------------------------------------------------------- /src/main/java/org/barcodeapi/server/cache/CachedShare.java: -------------------------------------------------------------------------------- 1 | package org.barcodeapi.server.cache; 2 | 3 | import java.util.List; 4 | 5 | import org.barcodeapi.server.gen.BarcodeRequest; 6 | import org.json.JSONArray; 7 | import org.json.JSONObject; 8 | 9 | import com.mclarkdev.tools.libextras.LibExtrasHashes; 10 | 11 | /** 12 | * CachedShare.java 13 | * 14 | * @author Matthew R. Clark (BarcodeAPI.org, 2017-2024) 15 | */ 16 | public class CachedShare extends CachedObject { 17 | 18 | private static final long serialVersionUID = 20241222L; 19 | 20 | private final String data; 21 | 22 | private final int entries; 23 | 24 | private final String hash; 25 | 26 | /** 27 | * Create a new share object from a given set of BarcodeRequest objects. 28 | * 29 | * @param requests 30 | */ 31 | public CachedShare(List requests) { 32 | super("share"); 33 | 34 | // Turn requests into JSON array 35 | JSONArray reqs = new JSONArray(); 36 | for (BarcodeRequest r : requests) { 37 | reqs.put(r.encodeURI()); 38 | } 39 | 40 | // Hash the data and save for later 41 | this.data = reqs.toString(); 42 | this.entries = reqs.length(); 43 | this.hash = LibExtrasHashes.sumMD5(data); 44 | } 45 | 46 | /** 47 | * Returns the data contained in the share. 48 | * 49 | * @return the data contained in the share 50 | */ 51 | public String getData() { 52 | 53 | this.touch(); 54 | return data; 55 | } 56 | 57 | /** 58 | * Returns the number of request entries in the share. 59 | * 60 | * @return the number of request entries in the share 61 | */ 62 | public int getNumEntries() { 63 | 64 | return entries; 65 | } 66 | 67 | /** 68 | * Returns the hash of the share data, this is used as the access key. 69 | * 70 | * @return the hash of the share data 71 | */ 72 | public String getHash() { 73 | 74 | return hash; 75 | } 76 | 77 | /** 78 | * Returns the share data encoded in a JSON object. 79 | * 80 | * @return share data in JSON format 81 | */ 82 | public String encodeJSON() { 83 | 84 | return ((new JSONObject()) // 85 | .put("created", getTimeCreated())// 86 | .put("expires", getTimeExpires())// 87 | .put("accessed", getAccessCount())// 88 | .put("hash", getHash())// 89 | .put("data", getData())// 90 | ).toString(); 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /config/types/EAN8.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "EAN8", 3 | "display": "EAN 8", 4 | "show": true, 5 | "decode": true, 6 | "cache": true, 7 | "target": [ 8 | "8", 9 | "ean-8", 10 | "ean8" 11 | ], 12 | "generator": ".Ean8Generator", 13 | "threads": 2, 14 | "priority": 88, 15 | "cost": { 16 | "basic": 1, 17 | "custom": 2 18 | }, 19 | "checkdigit": 8, 20 | "nonprinting": false, 21 | "example": [ 22 | "01234565" 23 | ], 24 | "pattern": { 25 | "auto": "^[0-9]{8}$", 26 | "extended": "^[0-9]{7,8}(\\+(([0-9]{2})|([0-9]{5}))){0,1}$" 27 | }, 28 | "description": { 29 | "en": "Derived from the longer EAN-13 UPC code. For smaller packages." 30 | }, 31 | "wiki": { 32 | "en": "https://en.wikipedia.org/wiki/EAN-8" 33 | }, 34 | "options": { 35 | "dpi": { 36 | "name": "Resolution", 37 | "type": "number", 38 | "default": 150, 39 | "min": 50, 40 | "max": 300, 41 | "step": 10 42 | }, 43 | "module": { 44 | "name": "Module", 45 | "type": "number", 46 | "default": 4, 47 | "min": 1, 48 | "max": 35, 49 | "step": 1 50 | }, 51 | "qz": { 52 | "name": "Quiet Zone", 53 | "type": "number", 54 | "default": 4, 55 | "min": 0, 56 | "max": 20, 57 | "step": 1 58 | }, 59 | "height": { 60 | "name": "Height", 61 | "type": "number", 62 | "default": 22, 63 | "min": 5, 64 | "max": 150, 65 | "step": 5 66 | }, 67 | "font": { 68 | "name": "Font Size", 69 | "type": "number", 70 | "default": 5, 71 | "min": 0, 72 | "max": 20, 73 | "step": 2 74 | }, 75 | "text": { 76 | "name": "Text Placement", 77 | "type": "option", 78 | "default": "bottom", 79 | "options": [ 80 | "bottom", 81 | "top", 82 | "none" 83 | ] 84 | }, 85 | "pattern": { 86 | "name": "Pattern", 87 | "type": "text", 88 | "default": "_", 89 | "pattern": "[ _]+" 90 | }, 91 | "bg": { 92 | "name": "Background", 93 | "type": "text", 94 | "default": "ffffff", 95 | "pattern": "[0-9A-F]{6}" 96 | }, 97 | "fg": { 98 | "name": "Foreground", 99 | "type": "text", 100 | "default": "000000", 101 | "pattern": "[0-9A-F]{6}" 102 | } 103 | } 104 | } -------------------------------------------------------------------------------- /config/types/ITF14.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ITF14", 3 | "display": "ITF-14", 4 | "show": true, 5 | "decode": true, 6 | "cache": true, 7 | "target": [ 8 | "14", 9 | "itf14", 10 | "itf-14", 11 | "scc-14", 12 | "gtin" 13 | ], 14 | "generator": ".ITF14Generator", 15 | "threads": 2, 16 | "priority": 48, 17 | "cost": { 18 | "basic": 1, 19 | "custom": 2 20 | }, 21 | "checkdigit": 0, 22 | "nonprinting": false, 23 | "example": [ 24 | "98765432109213" 25 | ], 26 | "pattern": { 27 | "auto": "", 28 | "extended": "^[0-9]{14}$" 29 | }, 30 | "description": { 31 | "en": "Interleaved 2 of 5, type 14." 32 | }, 33 | "wiki": { 34 | "en": "https://en.wikipedia.org/wiki/Interleaved_2_of_5" 35 | }, 36 | "options": { 37 | "dpi": { 38 | "name": "Resolution", 39 | "type": "number", 40 | "default": 150, 41 | "min": 50, 42 | "max": 300, 43 | "step": 10 44 | }, 45 | "module": { 46 | "name": "Module", 47 | "type": "number", 48 | "default": 4, 49 | "min": 1, 50 | "max": 20, 51 | "step": 1 52 | }, 53 | "qz": { 54 | "name": "Quiet Zone", 55 | "type": "number", 56 | "default": 4, 57 | "min": 4, 58 | "max": 25, 59 | "step": 2 60 | }, 61 | "height": { 62 | "name": "Height", 63 | "type": "number", 64 | "default": 25, 65 | "min": 5, 66 | "max": 150, 67 | "step": 5 68 | }, 69 | "font": { 70 | "name": "Font Size", 71 | "type": "number", 72 | "default": 5, 73 | "min": 0, 74 | "max": 20, 75 | "step": 2 76 | }, 77 | "text": { 78 | "name": "Text Placement", 79 | "type": "option", 80 | "default": "bottom", 81 | "options": [ 82 | "bottom", 83 | "top", 84 | "none" 85 | ] 86 | }, 87 | "pattern": { 88 | "name": "Pattern", 89 | "type": "text", 90 | "default": "_ __ _____ _____ _", 91 | "pattern": "[ _]+" 92 | }, 93 | "bg": { 94 | "name": "Background", 95 | "type": "text", 96 | "default": "ffffff", 97 | "pattern": "[0-9A-F]{6}" 98 | }, 99 | "fg": { 100 | "name": "Foreground", 101 | "type": "text", 102 | "default": "000000", 103 | "pattern": "[0-9A-F]{6}" 104 | } 105 | } 106 | } -------------------------------------------------------------------------------- /config/types/Code39.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Code39", 3 | "display": "Code 39", 4 | "show": true, 5 | "decode": true, 6 | "cache": true, 7 | "target": [ 8 | "39", 9 | "code-39", 10 | "code39" 11 | ], 12 | "generator": ".Code39Generator", 13 | "threads": 2, 14 | "priority": 68, 15 | "cost": { 16 | "basic": 2, 17 | "custom": 3 18 | }, 19 | "checkdigit": 0, 20 | "nonprinting": false, 21 | "example": [ 22 | "TRY 39" 23 | ], 24 | "pattern": { 25 | "auto": "^[A-Z0-9 $.\\/]{1,12}$", 26 | "extended": "^[A-Z*0-9 \\-$%.\\/+]{1,30}$" 27 | }, 28 | "description": { 29 | "en": "A basic alphanumeric barcode. Uppercase letters and numbers only." 30 | }, 31 | "wiki": { 32 | "en": "https://en.wikipedia.org/wiki/Code_39" 33 | }, 34 | "options": { 35 | "dpi": { 36 | "name": "Resolution", 37 | "type": "number", 38 | "default": 150, 39 | "min": 50, 40 | "max": 300, 41 | "step": 10 42 | }, 43 | "module": { 44 | "name": "Module", 45 | "type": "number", 46 | "default": 4, 47 | "min": 1, 48 | "max": 35, 49 | "step": 1 50 | }, 51 | "qz": { 52 | "name": "Quiet Zone", 53 | "type": "number", 54 | "default": 4, 55 | "min": 0, 56 | "max": 20, 57 | "step": 1 58 | }, 59 | "height": { 60 | "name": "Height", 61 | "type": "number", 62 | "default": 22, 63 | "min": 5, 64 | "max": 150, 65 | "step": 5 66 | }, 67 | "font": { 68 | "name": "Font Size", 69 | "type": "number", 70 | "default": 5, 71 | "min": 0, 72 | "max": 20, 73 | "step": 2 74 | }, 75 | "text": { 76 | "name": "Text Placement", 77 | "type": "option", 78 | "default": "bottom", 79 | "options": [ 80 | "bottom", 81 | "top", 82 | "none" 83 | ] 84 | }, 85 | "pattern": { 86 | "name": "Pattern", 87 | "type": "text", 88 | "default": "_", 89 | "pattern": "[ _]+" 90 | }, 91 | "bg": { 92 | "name": "Background", 93 | "type": "text", 94 | "default": "ffffff", 95 | "pattern": "[0-9A-F]{6}" 96 | }, 97 | "fg": { 98 | "name": "Foreground", 99 | "type": "text", 100 | "default": "000000", 101 | "pattern": "[0-9A-F]{6}" 102 | } 103 | } 104 | } -------------------------------------------------------------------------------- /config/types/EAN13.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "EAN13", 3 | "display": "EAN 13", 4 | "show": true, 5 | "decode": true, 6 | "cache": true, 7 | "target": [ 8 | "13", 9 | "ean-13", 10 | "ean13" 11 | ], 12 | "generator": ".Ean13Generator", 13 | "threads": 2, 14 | "priority": 85, 15 | "cost": { 16 | "basic": 1, 17 | "custom": 2 18 | }, 19 | "checkdigit": 13, 20 | "nonprinting": false, 21 | "example": [ 22 | "1234567890128", 23 | "1123332122321+11252" 24 | ], 25 | "pattern": { 26 | "auto": "^[0-9]{13}$", 27 | "extended": "^[0-9]{12,13}(\\+(([0-9]{2})|([0-9]{5}))){0,1}$" 28 | }, 29 | "description": { 30 | "en": "A globally recognized product identification code. Encodes manufacturer, product / variant, and store use codes." 31 | }, 32 | "wiki": { 33 | "en": "https://en.wikipedia.org/wiki/International_Article_Number" 34 | }, 35 | "options": { 36 | "dpi": { 37 | "name": "Resolution", 38 | "type": "number", 39 | "default": 150, 40 | "min": 50, 41 | "max": 300, 42 | "step": 10 43 | }, 44 | "module": { 45 | "name": "Module", 46 | "type": "number", 47 | "default": 4, 48 | "min": 1, 49 | "max": 35, 50 | "step": 1 51 | }, 52 | "qz": { 53 | "name": "Quiet Zone", 54 | "type": "number", 55 | "default": 4, 56 | "min": 0, 57 | "max": 20, 58 | "step": 1 59 | }, 60 | "height": { 61 | "name": "Height", 62 | "type": "number", 63 | "default": 22, 64 | "min": 5, 65 | "max": 150, 66 | "step": 5 67 | }, 68 | "font": { 69 | "name": "Font Size", 70 | "type": "number", 71 | "default": 5, 72 | "min": 0, 73 | "max": 20, 74 | "step": 2 75 | }, 76 | "text": { 77 | "name": "Text Placement", 78 | "type": "option", 79 | "default": "bottom", 80 | "options": [ 81 | "bottom", 82 | "top", 83 | "none" 84 | ] 85 | }, 86 | "pattern": { 87 | "name": "Pattern", 88 | "type": "text", 89 | "default": "_", 90 | "pattern": "[ _]+" 91 | }, 92 | "bg": { 93 | "name": "Background", 94 | "type": "text", 95 | "default": "ffffff", 96 | "pattern": "[0-9A-F]{6}" 97 | }, 98 | "fg": { 99 | "name": "Foreground", 100 | "type": "text", 101 | "default": "000000", 102 | "pattern": "[0-9A-F]{6}" 103 | } 104 | } 105 | } -------------------------------------------------------------------------------- /config/types/UPC_E.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "UPC_E", 3 | "display": "UPC E", 4 | "show": true, 5 | "decode": true, 6 | "cache": true, 7 | "target": [ 8 | "e", 9 | "upc-e", 10 | "upce" 11 | ], 12 | "generator": ".UPCEGenerator", 13 | "threads": 2, 14 | "priority": 98, 15 | "cost": { 16 | "basic": 1, 17 | "custom": 3 18 | }, 19 | "checkdigit": 8, 20 | "nonprinting": false, 21 | "example": [ 22 | "01023459" 23 | ], 24 | "pattern": { 25 | "auto": "^(0|1)[0-9]{7}$", 26 | "extended": "^(0|1)[0-9]{6,7}(\\+(([0-9]{2})|([0-9]{5}))){0,1}$" 27 | }, 28 | "description": { 29 | "en": "A compact version of UPC-A, removing unneeded '0's." 30 | }, 31 | "wiki": { 32 | "en": "https://en.wikipedia.org/wiki/Universal_Product_Code" 33 | }, 34 | "options": { 35 | "dpi": { 36 | "name": "Resolution", 37 | "type": "number", 38 | "default": 150, 39 | "min": 50, 40 | "max": 300, 41 | "step": 10 42 | }, 43 | "module": { 44 | "name": "Module", 45 | "type": "number", 46 | "default": 4, 47 | "min": 1, 48 | "max": 35, 49 | "step": 1 50 | }, 51 | "qz": { 52 | "name": "Quiet Zone", 53 | "type": "number", 54 | "default": 4, 55 | "min": 0, 56 | "max": 20, 57 | "step": 1 58 | }, 59 | "height": { 60 | "name": "Height", 61 | "type": "number", 62 | "default": 22, 63 | "min": 5, 64 | "max": 150, 65 | "step": 5 66 | }, 67 | "font": { 68 | "name": "Font Size", 69 | "type": "number", 70 | "default": 5, 71 | "min": 0, 72 | "max": 20, 73 | "step": 2 74 | }, 75 | "text": { 76 | "name": "Text Placement", 77 | "type": "option", 78 | "default": "bottom", 79 | "options": [ 80 | "bottom", 81 | "top", 82 | "none" 83 | ] 84 | }, 85 | "pattern": { 86 | "name": "Pattern", 87 | "type": "text", 88 | "default": "_", 89 | "pattern": "[ _]+" 90 | }, 91 | "bg": { 92 | "name": "Background", 93 | "type": "text", 94 | "default": "ffffff", 95 | "pattern": "[0-9A-F]{6}" 96 | }, 97 | "fg": { 98 | "name": "Foreground", 99 | "type": "text", 100 | "default": "000000", 101 | "pattern": "[0-9A-F]{6}" 102 | } 103 | } 104 | } -------------------------------------------------------------------------------- /src/main/java/org/barcodeapi/Launcher.java: -------------------------------------------------------------------------------- 1 | package org.barcodeapi; 2 | 3 | import org.barcodeapi.core.ServerLauncher; 4 | 5 | /** 6 | * Launcher.java 7 | * 8 | * This class should handle the master life-cycle of the BarcodeAPI.org 9 | * application, web server, and sub components there-in. 10 | * 11 | * @author Matthew R. Clark (BarcodeAPI.org, 2017-2024) 12 | */ 13 | public class Launcher { 14 | 15 | // The instance of the running application. 16 | private static ServerLauncher apiServer; 17 | 18 | /** 19 | * This method should serve as the main entry point for the application when 20 | * being started by any means, it should catch and properly log any fatal 21 | * exceptions which might arise at any time in the applications life before it 22 | * is properly terminated. 23 | * 24 | * @param args Any arguments passed to the application from the command line. 25 | */ 26 | public static void main(String[] args) { 27 | 28 | // Print startup message 29 | System.out.println(""); 30 | System.out.println("Starting..."); 31 | System.out.println(""); 32 | 33 | try { 34 | 35 | // Instantiate the loader 36 | apiServer = new ServerLauncher(args); 37 | 38 | // Launch the system 39 | apiServer.launch(); 40 | 41 | } catch (Exception e) { 42 | 43 | // Log launch exception and quit 44 | e.printStackTrace(System.err); 45 | Launcher.invokeSystemShutdown(1); 46 | 47 | } catch (NoClassDefFoundError e) { 48 | 49 | // Log error and quit on launch dependency failure 50 | System.out.println("Missing required libraries. Exiting."); 51 | System.err.println("Missing required libraries. Exiting."); 52 | e.printStackTrace(System.err); 53 | Launcher.invokeSystemShutdown(1); 54 | } 55 | } 56 | 57 | /** 58 | * This method may be called by any class, at any time, to request that the 59 | * application be terminated with the given exit code. 60 | * 61 | * @param exitCode The exit code to be returned to the operating system. 62 | */ 63 | public static void invokeSystemShutdown(final int exitCode) { 64 | 65 | // Attempt a graceful shutdown the server 66 | apiServer.stop(); 67 | 68 | // Run this in a dedicated thread so the call returns 69 | new Thread(new Runnable() { 70 | 71 | public void run() { 72 | 73 | // Exit the JVM with a given status code 74 | System.exit(exitCode); 75 | } 76 | }).start(); 77 | } 78 | } -------------------------------------------------------------------------------- /config/types/UPC_A.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "UPC_A", 3 | "display": "UPC A", 4 | "show": true, 5 | "decode": true, 6 | "cache": true, 7 | "target": [ 8 | "a", 9 | "upc-a", 10 | "upca", 11 | "upc" 12 | ], 13 | "generator": ".UPCAGenerator", 14 | "threads": 2, 15 | "priority": 95, 16 | "cost": { 17 | "basic": 1, 18 | "custom": 3 19 | }, 20 | "checkdigit": 12, 21 | "nonprinting": false, 22 | "example": [ 23 | "123456789012" 24 | ], 25 | "pattern": { 26 | "auto": "^[0-9]{12}$", 27 | "extended": "^[0-9]{11,12}(\\+(([0-9]{2})|([0-9]{5}))){0,1}$" 28 | }, 29 | "description": { 30 | "en": "A universally recognized 12 digit barcode. Encodes manufacturer, product / variant, and store use codes." 31 | }, 32 | "wiki": { 33 | "en": "https://en.wikipedia.org/wiki/Universal_Product_Code" 34 | }, 35 | "options": { 36 | "dpi": { 37 | "name": "Resolution", 38 | "type": "number", 39 | "default": 150, 40 | "min": 50, 41 | "max": 300, 42 | "step": 10 43 | }, 44 | "module": { 45 | "name": "Module", 46 | "type": "number", 47 | "default": 4, 48 | "min": 1, 49 | "max": 35, 50 | "step": 1 51 | }, 52 | "qz": { 53 | "name": "Quiet Zone", 54 | "type": "number", 55 | "default": 4, 56 | "min": 0, 57 | "max": 20, 58 | "step": 1 59 | }, 60 | "height": { 61 | "name": "Height", 62 | "type": "number", 63 | "default": 22, 64 | "min": 5, 65 | "max": 150, 66 | "step": 5 67 | }, 68 | "font": { 69 | "name": "Font Size", 70 | "type": "number", 71 | "default": 5, 72 | "min": 0, 73 | "max": 20, 74 | "step": 2 75 | }, 76 | "text": { 77 | "name": "Text Placement", 78 | "type": "option", 79 | "default": "bottom", 80 | "options": [ 81 | "bottom", 82 | "top", 83 | "none" 84 | ] 85 | }, 86 | "pattern": { 87 | "name": "Pattern", 88 | "type": "text", 89 | "default": "_", 90 | "pattern": "[ _]+" 91 | }, 92 | "bg": { 93 | "name": "Background", 94 | "type": "text", 95 | "default": "ffffff", 96 | "pattern": "[0-9A-F]{6}" 97 | }, 98 | "fg": { 99 | "name": "Foreground", 100 | "type": "text", 101 | "default": "000000", 102 | "pattern": "[0-9A-F]{6}" 103 | } 104 | } 105 | } -------------------------------------------------------------------------------- /config/types/Code128.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Code128", 3 | "display": "Code 128", 4 | "show": true, 5 | "decode": true, 6 | "cache": true, 7 | "target": [ 8 | "128", 9 | "code-128", 10 | "code128" 11 | ], 12 | "generator": ".Code128Generator", 13 | "threads": 2, 14 | "priority": 65, 15 | "cost": { 16 | "basic": 2, 17 | "custom": 3 18 | }, 19 | "checkdigit": 0, 20 | "nonprinting": true, 21 | "example": [ 22 | "Try Me!", 23 | "ABCD-1234" 24 | ], 25 | "pattern": { 26 | "auto": "^[ !#$()*.\\/0-9=?A-Za-z~]{1,12}$", 27 | "extended": "^[ !\\\"#$%&\\'()*+,\\-.\\/0-9:;<=>?@A-Z\\[\\]^_`a-z{|}~]{1,48}$" 28 | }, 29 | "description": { 30 | "en": "A medium densisty alphanumeric barcode with a larger character set." 31 | }, 32 | "wiki": { 33 | "en": "https://en.wikipedia.org/wiki/Code_128" 34 | }, 35 | "options": { 36 | "dpi": { 37 | "name": "Resolution", 38 | "type": "number", 39 | "default": 150, 40 | "min": 50, 41 | "max": 300, 42 | "step": 10 43 | }, 44 | "module": { 45 | "name": "Module", 46 | "type": "number", 47 | "default": 4, 48 | "min": 1, 49 | "max": 35, 50 | "step": 1 51 | }, 52 | "qz": { 53 | "name": "Quiet Zone", 54 | "type": "number", 55 | "default": 4, 56 | "min": 0, 57 | "max": 20, 58 | "step": 1 59 | }, 60 | "height": { 61 | "name": "Height", 62 | "type": "number", 63 | "default": 22, 64 | "min": 5, 65 | "max": 150, 66 | "step": 5 67 | }, 68 | "font": { 69 | "name": "Font Size", 70 | "type": "number", 71 | "default": 5, 72 | "min": 0, 73 | "max": 20, 74 | "step": 2 75 | }, 76 | "text": { 77 | "name": "Text Placement", 78 | "type": "option", 79 | "default": "bottom", 80 | "options": [ 81 | "bottom", 82 | "top", 83 | "none" 84 | ] 85 | }, 86 | "pattern": { 87 | "name": "Pattern", 88 | "type": "text", 89 | "default": "_", 90 | "pattern": "[ _]+" 91 | }, 92 | "bg": { 93 | "name": "Background", 94 | "type": "text", 95 | "default": "ffffff", 96 | "pattern": "[0-9A-F]{6}" 97 | }, 98 | "fg": { 99 | "name": "Foreground", 100 | "type": "text", 101 | "default": "000000", 102 | "pattern": "[0-9A-F]{6}" 103 | } 104 | } 105 | } -------------------------------------------------------------------------------- /src/main/java/org/barcodeapi/server/core/CodeTypes.java: -------------------------------------------------------------------------------- 1 | package org.barcodeapi.server.core; 2 | 3 | import java.io.File; 4 | import java.io.IOException; 5 | import java.util.HashMap; 6 | import java.util.Set; 7 | 8 | import org.json.JSONException; 9 | import org.json.JSONObject; 10 | 11 | import com.mclarkdev.tools.libextras.LibExtrasStreams; 12 | import com.mclarkdev.tools.liblog.LibLog; 13 | 14 | /** 15 | * CodeTypes.java 16 | * 17 | * @author Matthew R. Clark (BarcodeAPI.org, 2017-2024) 18 | */ 19 | public class CodeTypes { 20 | 21 | private static final CodeTypes typeCache = new CodeTypes(); 22 | 23 | public static CodeTypes inst() { 24 | return typeCache; 25 | } 26 | 27 | private final File typeDir = new File("config", "types"); 28 | 29 | private final HashMap codeTypes; 30 | 31 | private CodeTypes() { 32 | codeTypes = new HashMap<>(); 33 | } 34 | 35 | /** 36 | * Initializes a give CodeType from config. 37 | * 38 | * @param name the name of the config file 39 | * @return the loaded CodeType 40 | */ 41 | public CodeType loadType(String name) { 42 | 43 | // Setup and log config file name 44 | File typeFile = new File(typeDir, (name + ".json")); 45 | LibLog._clogF("I0061", typeFile.getPath()); 46 | 47 | try { 48 | 49 | // Read config file, parse as JSON 50 | JSONObject typeConfig = new JSONObject(// 51 | LibExtrasStreams.readFile(typeFile)); 52 | 53 | // Initialize and log the CodeType 54 | CodeType codeType = CodeType.fromJSON(typeConfig); 55 | LibLog._clogF("I0062", codeType.getName(), codeType.getNumThreads()); 56 | 57 | // Add to map and return the type 58 | codeTypes.put(name, codeType); 59 | return codeType; 60 | 61 | } catch (JSONException | IOException e) { 62 | 63 | // Print and throw failure loading type 64 | throw LibLog._clog("E0069", e).asException(); 65 | } 66 | } 67 | 68 | /** 69 | * Returns a list of all supported types. 70 | * 71 | * @return a list of all supported types 72 | */ 73 | public Set getTypes() { 74 | 75 | return codeTypes.keySet(); 76 | } 77 | 78 | /** 79 | * Returns the CodeType object for a give name. 80 | * 81 | * @param name the name of the CodeType 82 | * @return the CodeType object 83 | */ 84 | public CodeType getType(String name) { 85 | 86 | return codeTypes.get(name); 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /src/main/java/org/barcodeapi/server/api/TypeHandler.java: -------------------------------------------------------------------------------- 1 | package org.barcodeapi.server.api; 2 | 3 | import java.io.IOException; 4 | import java.util.HashMap; 5 | 6 | import javax.servlet.http.HttpServletResponse; 7 | 8 | import org.barcodeapi.server.core.CodeType; 9 | import org.barcodeapi.server.core.CodeTypes; 10 | import org.barcodeapi.server.core.RequestContext; 11 | import org.barcodeapi.server.core.RestHandler; 12 | import org.barcodeapi.server.core.TypeSelector; 13 | import org.json.JSONArray; 14 | import org.json.JSONException; 15 | import org.json.JSONObject; 16 | 17 | /** 18 | * TypeHandler.java 19 | * 20 | * @author Matthew R. Clark (BarcodeAPI.org, 2017-2024) 21 | */ 22 | public class TypeHandler extends RestHandler { 23 | 24 | // Static map of JSON responses 25 | private static final HashMap typesConfig; 26 | 27 | private static final String typesComplete; 28 | 29 | static { 30 | typesConfig = new HashMap<>(); 31 | CodeTypes types = CodeTypes.inst(); 32 | 33 | // Convert each type to JSON, and cache 34 | JSONArray complete = new JSONArray(); 35 | for (String type : types.getTypes()) { 36 | CodeType t = types.getType(type); 37 | JSONObject details = CodeType.toJSON(t); 38 | 39 | complete.put(details); 40 | 41 | typesConfig.put(t, details.toString()); 42 | } 43 | 44 | typesComplete = complete.toString(); 45 | } 46 | 47 | public TypeHandler() { 48 | super(); 49 | } 50 | 51 | @Override 52 | protected void onRequest(RequestContext c, HttpServletResponse r) throws JSONException, IOException { 53 | 54 | // Check for user requested type string 55 | String typeStr = c.getRequest().getParameter("type"); 56 | if (typeStr == null || typeStr.trim().equals("")) { 57 | 58 | // Print all types response to client 59 | r.setStatus(HttpServletResponse.SC_OK); 60 | r.setContentType("application/json"); 61 | r.getOutputStream().println(typesComplete); 62 | return; 63 | } 64 | 65 | // Lookup requested type string 66 | CodeType type = TypeSelector.getTypeFromString(typeStr); 67 | 68 | // Fail if type not found 69 | if (type == null) { 70 | 71 | // Print error to client 72 | r.setStatus(HttpServletResponse.SC_BAD_REQUEST); 73 | return; 74 | } 75 | 76 | // Print single type response to client 77 | r.setStatus(HttpServletResponse.SC_OK); 78 | r.setContentType("application/json"); 79 | r.getOutputStream().println(typesConfig.get(type)); 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /resources/ext/js/bulk.js: -------------------------------------------------------------------------------- 1 | // 2 | // BarcodeAPI.org, 2017-2025 3 | // bulk.js // bulk.html 4 | // 5 | 6 | var submitButton; 7 | 8 | function init() { 9 | 10 | submitButton = document.getElementById("generate-bc"); 11 | 12 | document.getElementsByClassName("link-csv")[0].addEventListener('click', actionDownloadCSV); 13 | document.getElementById("csvFile").addEventListener('change', checkIfFileSelected); 14 | 15 | // Log tracking event 16 | var setupMillis = ((new Date()) - timeStart); 17 | trackingEvent("AppEvents", "AppLoad", "Bulk", setupMillis); 18 | } 19 | 20 | function actionDownloadCSV() { 21 | 22 | var blob = ""; 23 | for (var line in csvExample) { 24 | blob += (csvExample[line] + "\n"); 25 | } 26 | 27 | // Create download link 28 | var file = new Blob([blob], { type: 'text/csv' }); 29 | var a = document.createElement("a"); 30 | a.href = URL.createObjectURL(file); 31 | a.download = 'bulk-barcodes-example.csv'; 32 | document.body.appendChild(a); 33 | a.click(); 34 | document.body.removeChild(a); 35 | 36 | // Log tracking event 37 | trackingEvent("Bulk", "Download", "Example"); 38 | } 39 | 40 | function checkIfFileSelected(obj) { 41 | 42 | const fileInput = obj.target; 43 | const file = fileInput.files[0]; 44 | 45 | if (file) { 46 | const fileName = file.name.toLowerCase(); 47 | const isCSV = fileName.endsWith('.csv'); 48 | 49 | if (isCSV) { 50 | submitButton.removeAttribute("disabled"); 51 | } else { 52 | submitButton.setAttribute("disabled", "true"); 53 | alert("Please select a CSV file."); 54 | } 55 | } else { 56 | submitButton.setAttribute("disabled", "true"); 57 | } 58 | } 59 | 60 | function onUpload(e) { 61 | 62 | // Log tracking event 63 | trackingEvent("Bulk", "Upload"); 64 | 65 | return false; 66 | } 67 | 68 | var csvExample = [ 69 | "Aztec Barcode,aztec", 70 | "1234567890,codabar", 71 | "Try Me!,128", 72 | "TRY 39 ME,39", 73 | "Data Matrix Barcode,dm", 74 | "1234567890128,13", 75 | "01234565,8", 76 | "98765432109213,14", 77 | "PDF - 417,417", 78 | "QR Barcode,qr", 79 | "11212345612345678,royal", 80 | "123456789012,a", 81 | "01023459,e", 82 | "0123456709498765432101234567891,usps", 83 | "test-128-colors,128,fg=206060,bg=c0c0c0", 84 | "test-128-notext,128,height=8,text=none", 85 | "test-dm-color,dm,fg=602060,bg=d0d0d0", 86 | "test-dm-noqz,dm,qz=0", 87 | "00000000,8,text=none,fg=ff0000" 88 | ]; 89 | -------------------------------------------------------------------------------- /src/main/java/org/barcodeapi/server/api/StaticHandler.java: -------------------------------------------------------------------------------- 1 | package org.barcodeapi.server.api; 2 | 3 | import java.io.IOException; 4 | 5 | import javax.servlet.ServletException; 6 | import javax.servlet.http.HttpServletRequest; 7 | import javax.servlet.http.HttpServletResponse; 8 | 9 | import org.barcodeapi.core.Config; 10 | import org.barcodeapi.core.Config.Cfg; 11 | import org.barcodeapi.server.core.RequestContext; 12 | import org.barcodeapi.server.core.RestHandler; 13 | import org.eclipse.jetty.server.Request; 14 | import org.eclipse.jetty.server.Server; 15 | import org.eclipse.jetty.server.handler.ResourceHandler; 16 | 17 | /** 18 | * StaticHandler.java 19 | * 20 | * @author Matthew R. Clark (BarcodeAPI.org, 2017-2024) 21 | */ 22 | public class StaticHandler extends RestHandler { 23 | 24 | private static final int CACHED_LIFE_MIN = Config.get(Cfg.App)// 25 | .getJSONObject("client").getInt("cacheStatic"); 26 | 27 | private static final int CACHED_LIFE_SEC = (CACHED_LIFE_MIN * 60); 28 | 29 | private static final int CACHED_LIFE_MS = (CACHED_LIFE_SEC * 1000); 30 | 31 | private ResourceHandler resources = new ResourceHandler(); 32 | 33 | public StaticHandler(Server server) throws Exception { 34 | super(); 35 | 36 | // Load the Jetty resource handler 37 | resources = new ResourceHandler(); 38 | resources.setServer(server); 39 | resources.setResourceBase("resources"); 40 | resources.setRedirectWelcome(true); 41 | resources.setWelcomeFiles(new String[] { "index.html" }); 42 | resources.setCacheControl(("max-age=" + CACHED_LIFE_SEC)); 43 | resources.start(); 44 | } 45 | 46 | @Override 47 | public void _impl(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) 48 | throws IOException, ServletException { 49 | 50 | // Calculate and set cache expiration time 51 | response.setDateHeader("expires", // 52 | (System.currentTimeMillis() + CACHED_LIFE_MS)); 53 | 54 | // Call through to resources 55 | baseRequest.setHandled(false); 56 | resources.handle(target, baseRequest, request, response); 57 | 58 | // Send non resources to API 59 | if (!baseRequest.isHandled()) { 60 | response.sendRedirect("/api/auto" + request.getPathInfo()); 61 | } 62 | } 63 | 64 | @Override 65 | protected void onRequest(RequestContext c, HttpServletResponse r) throws Exception { 66 | 67 | // Do Nothing. 68 | // This class overrides the lower level (handle) method to serve static files. 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/test/java/org/barcodeapi/test/gen/TestURLs.java: -------------------------------------------------------------------------------- 1 | package org.barcodeapi.test.gen; 2 | 3 | import org.barcodeapi.server.ServerTestBase; 4 | import org.eclipse.jetty.http.HttpStatus; 5 | import org.junit.Assert; 6 | import org.junit.Test; 7 | 8 | /** 9 | * TestURLs.java 10 | * 11 | * @author Matthew R. Clark (BarcodeAPI.org, 2017-2025) 12 | */ 13 | public class TestURLs extends ServerTestBase { 14 | 15 | @Test 16 | public void testURL_TestHTTP() { 17 | 18 | apiGet("http://barcodeapi.org/"); 19 | 20 | Assert.assertEquals("Response Code", // 21 | HttpStatus.OK_200, getResponseCode()); 22 | 23 | Assert.assertEquals("Code Type", // 24 | "QRCode", getHeader("X-Barcode-Type")); 25 | 26 | Assert.assertEquals("Code Data", // 27 | encode("http://barcodeapi.org/"), getHeader("X-Barcode-Content")); 28 | } 29 | 30 | @Test 31 | public void testURL_TestHTTPS() { 32 | 33 | apiGet("https://barcodeapi.org/"); 34 | 35 | Assert.assertEquals("Response Code", // 36 | HttpStatus.OK_200, getResponseCode()); 37 | 38 | Assert.assertEquals("Code Type", // 39 | "QRCode", getHeader("X-Barcode-Type")); 40 | 41 | Assert.assertEquals("Code Data", // 42 | encode("https://barcodeapi.org/"), getHeader("X-Barcode-Content")); 43 | } 44 | 45 | @Test 46 | public void testURL_TestBarcodeOptions() { 47 | 48 | apiGet("https://barcodeapi.org/", "size=20"); 49 | 50 | Assert.assertEquals("Response Code", // 51 | HttpStatus.OK_200, getResponseCode()); 52 | 53 | Assert.assertEquals("Code Type", // 54 | "QRCode", getHeader("X-Barcode-Type")); 55 | 56 | Assert.assertEquals("Code Data", // 57 | encode("https://barcodeapi.org/"), getHeader("X-Barcode-Content")); 58 | } 59 | 60 | @Test 61 | public void testURL_TestURLOptions() { 62 | 63 | apiGet("https://barcodeapi.org/", "product=123"); 64 | 65 | Assert.assertEquals("Response Code", // 66 | HttpStatus.OK_200, getResponseCode()); 67 | 68 | Assert.assertEquals("Code Type", // 69 | "QRCode", getHeader("X-Barcode-Type")); 70 | 71 | Assert.assertEquals("Code Data", // 72 | encode("https://barcodeapi.org/?product=123"), getHeader("X-Barcode-Content")); 73 | } 74 | 75 | @Test 76 | public void testURL_TestURLEncoded() { 77 | 78 | apiGet("https://barcodeapi.org/?product=123"); 79 | 80 | Assert.assertEquals("Response Code", // 81 | HttpStatus.OK_200, getResponseCode()); 82 | 83 | Assert.assertEquals("Code Type", // 84 | "QRCode", getHeader("X-Barcode-Type")); 85 | 86 | Assert.assertEquals("Code Data", // 87 | encode("https://barcodeapi.org/?product=123"), getHeader("X-Barcode-Content")); 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /src/main/java/org/barcodeapi/core/Config.java: -------------------------------------------------------------------------------- 1 | package org.barcodeapi.core; 2 | 3 | import java.io.File; 4 | import java.util.Map; 5 | import java.util.concurrent.ConcurrentHashMap; 6 | 7 | import org.json.JSONObject; 8 | 9 | import com.mclarkdev.tools.libargs.LibArgs; 10 | import com.mclarkdev.tools.libextras.LibExtrasStreams; 11 | 12 | public class Config { 13 | 14 | public enum Cfg { 15 | App, Blacklist, Admins, Plans, Subscribers; 16 | } 17 | 18 | private static final String dist = LibArgs.instance().getString("config", "community"); 19 | 20 | private static final Map configs = new ConcurrentHashMap(); 21 | 22 | /** 23 | * Retrieves the name of the configuration distribution currently in use. 24 | * 25 | * @return name of the configuration distribution in use 26 | */ 27 | public static String dist() { 28 | return dist; 29 | } 30 | 31 | /** 32 | * Retrieve application configuration. 33 | * 34 | * @param cfg configuration type 35 | * @return the cached configuration object 36 | */ 37 | public static JSONObject get(Cfg cfg) { 38 | return get(cfg, false); 39 | } 40 | 41 | /** 42 | * Retrieve application configuration. 43 | * 44 | * @param cfg configuration type 45 | * @param forceReload force reload from disk 46 | * @return the cached configuration object 47 | */ 48 | public static JSONObject get(Cfg cfg, boolean forceReload) { 49 | 50 | // Check if already loaded or forcing a reload 51 | if ((!configs.containsKey(cfg)) || forceReload) { 52 | 53 | // Reload from disk and add to map 54 | configs.put(cfg, loadConfig(cfg)); 55 | } 56 | 57 | // Return the cached data 58 | return configs.get(cfg); 59 | } 60 | 61 | /** 62 | * Load a given configuration file from disk. 63 | * 64 | * @param cfg configuration type 65 | * @return the loaded configuration object 66 | */ 67 | private static final JSONObject loadConfig(Cfg cfg) { 68 | 69 | try { 70 | 71 | // Determine name of file loaded 72 | String confName = (cfg.toString().toLowerCase()); 73 | 74 | // Find file on disk 75 | File confFile = new File(// 76 | new File("config", dist), // 77 | (confName + ".json")); 78 | 79 | // Read the file as a raw string 80 | String raw = LibExtrasStreams.readFile(confFile); 81 | 82 | // Return string parsed as JSON 83 | return new JSONObject(raw); 84 | 85 | } catch (Error | Exception e) { 86 | 87 | // Log general exception 88 | throw new Error("Failed loading app config.", e); 89 | } 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /src/main/java/org/barcodeapi/server/cache/Subscriber.java: -------------------------------------------------------------------------------- 1 | package org.barcodeapi.server.cache; 2 | 3 | import org.json.JSONArray; 4 | import org.json.JSONObject; 5 | 6 | /** 7 | * CachedSession.java 8 | * 9 | * A user session object allowing for the detailed tracking of individual user 10 | * usage activities across all handlers. 11 | * 12 | * @author Matthew R. Clark (BarcodeAPI.org, 2017-2024) 13 | */ 14 | public class Subscriber { 15 | 16 | private final String customer; 17 | private final long subscribed; 18 | private final boolean active; 19 | private final boolean enforce; 20 | private final int limit; 21 | private final int batch; 22 | 23 | private final JSONArray ips; 24 | private final JSONArray keys; 25 | private final JSONArray apps; 26 | 27 | public Subscriber(JSONObject def) { 28 | 29 | // Basic subscriber details 30 | this.customer = def.getString("customer"); 31 | this.subscribed = def.getLong("subscribed"); 32 | 33 | // Account active / enforced 34 | this.active = def.getBoolean("active"); 35 | this.enforce = def.getBoolean("enforce"); 36 | 37 | // Account limits 38 | this.limit = def.getInt("limit"); 39 | this.batch = def.getInt("batch"); 40 | 41 | // Account associations 42 | this.ips = def.optJSONArray("ips", new JSONArray()); 43 | this.keys = def.optJSONArray("keys", new JSONArray()); 44 | this.apps = def.optJSONArray("apps", new JSONArray()); 45 | } 46 | 47 | public String getCustomer() { 48 | return this.customer; 49 | } 50 | 51 | public long getSubscribed() { 52 | return this.subscribed; 53 | } 54 | 55 | public boolean getActive() { 56 | return this.active; 57 | } 58 | 59 | public boolean isEnforced() { 60 | return this.enforce; 61 | } 62 | 63 | public int getLimit() { 64 | return this.limit; 65 | } 66 | 67 | public int getMaxBatch() { 68 | return this.batch; 69 | } 70 | 71 | public JSONArray getIPs() { 72 | return this.ips; 73 | } 74 | 75 | public JSONArray getKeys() { 76 | return this.keys; 77 | } 78 | 79 | public JSONArray getApps() { 80 | return this.apps; 81 | } 82 | 83 | /** 84 | * Returns the subscriber info as a JSON object. 85 | * 86 | * @return the subscriber info in JSON format 87 | */ 88 | public JSONObject asJSON() { 89 | 90 | return new JSONObject() // 91 | .put("customer", getCustomer())// 92 | .put("subscribed", getSubscribed())// 93 | .put("active", getActive())// 94 | .put("enforce", isEnforced())// 95 | .put("limit", getLimit())// 96 | .put("batch", getMaxBatch())// 97 | .put("ips", getIPs())// 98 | .put("keys", getKeys()); 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /resources/types.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | 8 | 9 | BarcodeAPI.org - Barcode Types 10 | 11 | 13 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 |
    29 | 30 |
    31 | 32 |
    33 |

    34 |
    FEEDBACK & SUPPORT
    35 |
    support@barcodeapi.org
    36 |

    37 |
    38 |
    39 |
    40 | 41 |
    42 |
    43 |

    Barcode Types

    44 |
    45 | The following barcode types are supported by the server. 46 |
    47 |
    48 | View our User Manual for additional 49 | information. 50 |
    51 |
    52 | Click a barcode format to learn more. 53 |
    54 |
    55 | 56 |
    57 |
    58 |
    59 | - 60 |
    61 |
    62 | Cost: !-! 63 |
    64 | Target: - 65 |
    66 |
    67 | Generate Now 68 |
    69 | More Details 70 |
    71 | 72 |
    73 | 74 | 75 | 76 |
    77 |
    78 |
    79 |
    80 | 81 | 82 | 83 | -------------------------------------------------------------------------------- /src/test/java/org/barcodeapi/test/core/TestBarcodeOptions.java: -------------------------------------------------------------------------------- 1 | package org.barcodeapi.test.core; 2 | 3 | import org.barcodeapi.server.ServerTestBase; 4 | import org.barcodeapi.server.core.GenerationException; 5 | import org.barcodeapi.server.gen.BarcodeRequest; 6 | import org.eclipse.jetty.http.HttpStatus; 7 | import org.junit.Assert; 8 | import org.junit.Test; 9 | 10 | /** 11 | * TestBarcodeOptions.java 12 | * 13 | * @author Matthew R. Clark (BarcodeAPI.org, 2017-2025) 14 | */ 15 | public class TestBarcodeOptions extends ServerTestBase { 16 | 17 | @Test 18 | public void testBarcodeRequest_TestStripDefaults() { 19 | 20 | try { 21 | BarcodeRequest req = // 22 | BarcodeRequest.fromURI("/api/auto/abc?fg=000000&bg=ffffff"); 23 | 24 | Assert.assertFalse("Request is not Simple", req.isComplex()); 25 | } catch (GenerationException e) { 26 | 27 | Assert.fail("Failed to create request."); 28 | } 29 | } 30 | 31 | @Test 32 | public void testBarcodeOptions_Options() { 33 | 34 | try { 35 | BarcodeRequest r = BarcodeRequest// 36 | .fromURI("/api/128/barcode_test?bg=fffff0&fg=00000f"); 37 | 38 | Assert.assertEquals("Request Type", // 39 | "Code128", r.getType().getName()); 40 | 41 | Assert.assertEquals("Request Data", // 42 | "barcode_test", r.getData()); 43 | 44 | Assert.assertEquals("Request Options", // 45 | 2, r.getOptions().length()); 46 | 47 | Assert.assertEquals("Request Options (FG)", // 48 | "00000f", r.getOptions().getString("fg")); 49 | 50 | Assert.assertEquals("Request Options (BG)", // 51 | "fffff0", r.getOptions().getString("bg")); 52 | 53 | } catch (GenerationException e) { 54 | 55 | Assert.fail(e.getMessage()); 56 | } 57 | } 58 | 59 | @Test 60 | public void testBarcodeOptions_MissingOption() { 61 | 62 | apiGet("128/RD309874", "dpi=100&&size=20"); 63 | 64 | Assert.assertEquals("Response Code", // 65 | HttpStatus.OK_200, getResponseCode()); 66 | 67 | Assert.assertEquals("Code Type", // 68 | "Code128", getHeader("X-Barcode-Type")); 69 | 70 | Assert.assertEquals("Code Data", // 71 | "RD309874", getHeader("X-Barcode-Content")); 72 | 73 | Assert.assertEquals("Code Format", // 74 | "image/png;charset=utf-8", getHeader("Content-Type")); 75 | } 76 | 77 | @Test 78 | public void testBarcodeOptions_DoubleOption() { 79 | 80 | apiGet("128/RD309874", "dpi=100&dpi=120"); 81 | 82 | Assert.assertEquals("Response Code", // 83 | HttpStatus.OK_200, getResponseCode()); 84 | 85 | Assert.assertEquals("Code Type", // 86 | "Code128", getHeader("X-Barcode-Type")); 87 | 88 | Assert.assertEquals("Code Data", // 89 | "RD309874", getHeader("X-Barcode-Content")); 90 | 91 | Assert.assertEquals("Code Format", // 92 | "image/png;charset=utf-8", getHeader("Content-Type")); 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /src/main/java/org/barcodeapi/server/core/CodeGenerators.java: -------------------------------------------------------------------------------- 1 | package org.barcodeapi.server.core; 2 | 3 | import java.io.File; 4 | import java.io.FileWriter; 5 | import java.io.IOException; 6 | import java.util.HashMap; 7 | 8 | import org.barcodeapi.core.Config; 9 | import org.barcodeapi.core.Config.Cfg; 10 | import org.barcodeapi.server.gen.CodeGenerator; 11 | import org.json.JSONArray; 12 | 13 | import com.mclarkdev.tools.liblog.LibLog; 14 | import com.mclarkdev.tools.libmetrics.LibMetrics; 15 | import com.mclarkdev.tools.libobjectpooler.LibObjectPooler; 16 | 17 | /** 18 | * CodeGenerators.java 19 | * 20 | * Initializes all supported CodeTypes from configuration and provides access to 21 | * their associated generation object pools. 22 | * 23 | * @author Matthew R. Clark (BarcodeAPI.org, 2017-2024) 24 | */ 25 | public class CodeGenerators { 26 | 27 | private static CodeGenerators codeGenerators; 28 | 29 | /** 30 | * Returns an instance of the CodeGenerators map. 31 | * 32 | * @return an instance of the CodeGenerators map 33 | */ 34 | public static synchronized CodeGenerators getInstance() { 35 | if (codeGenerators == null) { 36 | codeGenerators = new CodeGenerators(); 37 | } 38 | return codeGenerators; 39 | } 40 | 41 | /** 42 | * A map of generation pools for all supported CodeTypes. 43 | */ 44 | private HashMap> generators = new HashMap<>(); 45 | 46 | private CodeGenerators() { 47 | 48 | // Load all enabled code types 49 | JSONArray enabled = Config.get(Cfg.App).getJSONArray("types"); 50 | 51 | // Loop all enabled types 52 | for (int x = 0; x < enabled.length(); x++) { 53 | 54 | try { 55 | 56 | // Load type from config and setup pooler 57 | String type = enabled.getString(x); 58 | CodeType config = CodeTypes.inst().loadType(type); 59 | generators.put(config, setupBarcodePooler(config)); 60 | 61 | } catch (Exception | Error e) { 62 | 63 | // Log initialization failure 64 | LibLog._clog("E0051", e); 65 | } 66 | } 67 | } 68 | 69 | /** 70 | * Creates a new object pool for the given CodeType. 71 | * 72 | * @param codeType type of pooler to create 73 | * @return the created pooler 74 | */ 75 | private LibObjectPooler setupBarcodePooler(final CodeType codeType) { 76 | 77 | LibObjectPooler pooler = // 78 | new LibObjectPooler(3, new GeneratorPoolController(codeType)); 79 | 80 | pooler.setMaxLockTime(1000); 81 | pooler.setMaxIdleTime(15 * 60 * 1000); 82 | pooler.setMaxPoolSize(codeType.getNumThreads()); 83 | 84 | return pooler; 85 | } 86 | 87 | /** 88 | * Returns the generation pool for the given CodeType. 89 | * 90 | * @param type type of pooler to get 91 | * @return the generation pool 92 | */ 93 | public LibObjectPooler getGeneratorPool(CodeType type) { 94 | LibMetrics.hitMethodRunCounter(); 95 | 96 | return generators.get(type); 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /src/main/java/org/barcodeapi/server/gen/impl/DefaultBarcode4JProvider.java: -------------------------------------------------------------------------------- 1 | package org.barcodeapi.server.gen.impl; 2 | 3 | import java.util.HashMap; 4 | 5 | import org.barcodeapi.server.core.CodeType; 6 | import org.barcodeapi.server.core.GenerationException; 7 | import org.barcodeapi.server.gen.BarcodeCanvasProvider; 8 | import org.barcodeapi.server.gen.BarcodeRequest; 9 | import org.barcodeapi.server.gen.CodeGenerator; 10 | import org.json.JSONObject; 11 | import org.krysalis.barcode4j.HumanReadablePlacement; 12 | import org.krysalis.barcode4j.impl.AbstractBarcodeBean; 13 | 14 | public abstract class DefaultBarcode4JProvider extends CodeGenerator { 15 | 16 | private final AbstractBarcodeBean generator; 17 | 18 | public DefaultBarcode4JProvider(CodeType codeType, AbstractBarcodeBean bean) { 19 | super(codeType); 20 | 21 | this.generator = bean; 22 | } 23 | 24 | protected AbstractBarcodeBean getBean() { 25 | 26 | return this.generator; 27 | } 28 | 29 | @Override 30 | public byte[] onRender(BarcodeRequest request) throws Exception { 31 | 32 | JSONObject options = request.getOptions(); 33 | 34 | HashMap defaults = // 35 | request.getType().getDefaults(); 36 | 37 | int dpi = options.optInt("dpi", // 38 | (Integer) defaults.getOrDefault("dpi", 100)); 39 | 40 | int module = options.optInt("module", // 41 | (Integer) defaults.getOrDefault("module", 10)); 42 | 43 | int qz = options.optInt("qz", // 44 | (Integer) defaults.getOrDefault("qz", 3)); 45 | 46 | int height = options.optInt("height", // 47 | (Integer) defaults.getOrDefault("height", 22)); 48 | 49 | int font = options.optInt("font", // 50 | (Integer) defaults.getOrDefault("font", 5)); 51 | 52 | String text = options.optString("text", // 53 | (String) defaults.getOrDefault("text", "bottom")); 54 | 55 | String pattern = options.optString("pattern", // 56 | (String) defaults.getOrDefault("pattern", "_")); 57 | 58 | String colorFG = options.optString("fg", // 59 | (String) defaults.getOrDefault("fg", "000000")); 60 | 61 | String colorBG = options.optString("bg", // 62 | (String) defaults.getOrDefault("bg", "ffffff")); 63 | 64 | byte[] bytes; 65 | synchronized (generator) { 66 | 67 | // Set render options 68 | generator.doQuietZone(true); 69 | generator.setQuietZone(qz); 70 | generator.setHeight(height); 71 | generator.setModuleWidth((module / 10d)); 72 | generator.setPattern(pattern); 73 | generator.setFontSize(font); 74 | generator.setMsgPosition(HumanReadablePlacement.byName(text)); 75 | 76 | // Create the canvas object 77 | BarcodeCanvasProvider canvasProvider = // 78 | new BarcodeCanvasProvider(dpi, colorBG, colorFG); 79 | 80 | // Render the barcode, get as bytes 81 | generator.generateBarcode(canvasProvider, request.getData()); 82 | bytes = canvasProvider.finish(); 83 | } 84 | 85 | return bytes; 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /resources/admin.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | 8 | 9 | BarcodeAPI.org - Admin Tools 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 |
    24 | 25 |
    26 | 27 |
    28 | 29 |
    30 |
    31 | Server 32 |
    33 | Server Information 34 |
    35 | Server Statistics 36 |
    37 |
    38 | Barcodes 39 |
    40 | Download Cache 41 |
    42 | Flush Barcode Caches 43 |
    44 |
    45 | Limiters 46 |
    47 | Current Limiter 48 |
    49 | List Limiters 50 |
    51 | Flush Limiter 52 |
    53 | Reload Users 54 |
    55 |
    56 | Sessions 57 |
    58 | Current Session 59 |
    60 | List Sessions 61 |
    62 | Flush Sessions 63 |
    64 |
    65 | Shares 66 |
    67 | Share Info 68 |
    69 | List Shares 70 |
    71 |
    72 | Generation API 73 |
    74 | Barcode Generator 75 |
    76 | Bulk Generator 77 |
    78 | Barcode Decoder 79 |
    80 | Barcode Types 81 |
    82 |
    83 |
    84 | 85 |
    86 | Hostname 87 |
    88 | _unknown_ 89 |
    90 |
    91 | Uptime 92 |
    93 | _unknown_ 94 |
    95 |
    96 | Version 97 |
    98 | _unknown_ 99 |
    100 |
    101 | Dist 102 |
    103 | _unknown_ 104 |
    105 |
    106 |
    107 |
    108 | 109 | 110 | -------------------------------------------------------------------------------- /resources/ext/js/type.js: -------------------------------------------------------------------------------- 1 | // 2 | // BarcodeAPI.org, 2017-2025 3 | // type.js // type.html 4 | // 5 | 6 | window.onhashchange = function() { 7 | location.reload(); 8 | } 9 | 10 | function init() { 11 | 12 | // Load the requested type target 13 | var target = window.location.hash.substring(1); 14 | 15 | // Redirect if not set 16 | if (!target) { 17 | window.location = "/types.html"; 18 | return; 19 | } 20 | 21 | // Load the type details 22 | fetch(("/type/?type=" + target)) 23 | .then(response => { 24 | return (response.status == 200) ? response.json() : false; 25 | }).then(loadType); 26 | } 27 | 28 | function loadType(type) { 29 | 30 | // Redirect if not loaded 31 | if (!type) { 32 | window.location = "/types.html"; 33 | return; 34 | } 35 | 36 | // Set the page title 37 | document.title = // 38 | document.title.replace("$TYPE$", type.display); 39 | 40 | // Determine default target 41 | var target = type.targets[0]; 42 | 43 | // Determine language 44 | var language = appConfig.userLanguage; 45 | 46 | // Get the DOM template object 47 | var info = document.getElementById("barcode-template"); 48 | info.setAttribute("id", "barcode-type-" + target); 49 | 50 | // Update example link 51 | var link = ("/api/" + target + "/" + type.example[0]); 52 | info.querySelector(".type-example").src = link; 53 | 54 | // Update type basic details 55 | info.querySelector(".type-name").innerHTML = type.display; 56 | info.querySelector(".type-target").innerHTML = ('/' + target + '/'); 57 | info.querySelector(".type-cost-basic").innerHTML = type.costBasic; 58 | info.querySelector(".type-cost-custom").innerHTML = type.costCustom; 59 | 60 | // Update type extended details 61 | info.querySelector(".type-example-link").href = "index.html#" + target; 62 | info.querySelector(".type-format").innerHTML = type.pattern; 63 | info.querySelector(".type-description").innerHTML = type.description[language]; 64 | info.querySelector(".type-wiki").href = type.wiki[language]; 65 | info.querySelector(".type-decode").innerHTML = (type.decode ? "Yes" : "No"); 66 | info.querySelector(".type-checksum").innerHTML = (type.checksum ? "Yes" : "No"); 67 | info.querySelector(".type-nonprinting").innerHTML = (type.nonprinting ? "Yes" : "No"); 68 | info.querySelector(".type-options").innerHTML = buildOptions(type.options); 69 | info.querySelector(".type-options-string").innerHTML = buildOptionsString(type.options); 70 | 71 | // Log tracking event 72 | var setupMillis = ((new Date()) - timeStart); 73 | trackingEvent("AppEvents", "AppLoad", "Type", setupMillis); 74 | } 75 | 76 | function buildOptions(options) { 77 | 78 | var response = ""; 79 | for (var x in options) { 80 | response += (x + "=" + options[x].default + "
    "); 81 | } 82 | return response; 83 | } 84 | 85 | function buildOptionsString(options) { 86 | 87 | var response = "?"; 88 | for (var x in options) { 89 | response += (x + "=" + options[x].default + "&"); 90 | } 91 | return response; 92 | } 93 | -------------------------------------------------------------------------------- /resources/ext/js/session.js: -------------------------------------------------------------------------------- 1 | // 2 | // BarcodeAPI.org, 2017-2025 3 | // session.js // session.html 4 | // 5 | 6 | function init() { 7 | 8 | // Load the type details 9 | fetch("/session/") 10 | .then(response => { 11 | return (response.status == 200) ? response.json() : false; 12 | }).then(onLoadSession); 13 | fetch("/limiter/") 14 | .then(response => { 15 | return (response.status == 200) ? response.json() : false; 16 | }).then(onLoadLimiter); 17 | 18 | // Log tracking event 19 | var setupMillis = ((new Date()) - timeStart); 20 | trackingEvent("AppEvents", "AppLoad", "Session", setupMillis); 21 | } 22 | 23 | function onLoadSession(data) { 24 | 25 | document.getElementById("session-key").innerHTML = data.key; 26 | document.getElementById("session-created").innerHTML = (new Date(data.created)).toJSON(); 27 | document.getElementById("session-expires").innerHTML = (new Date(data.expires)).toJSON(); 28 | document.getElementById("session-count").innerHTML = data.count; 29 | 30 | var addresses = ""; 31 | for (var a in data.addresses) { 32 | var d = data.addresses[a]; 33 | 34 | addresses += // 35 | "" + d.ip + "" + d.hits + ""; 36 | } 37 | document.getElementById("session-addresses").innerHTML = addresses; 38 | 39 | for (var r in data.requests) { 40 | var d = data.requests[r]; 41 | 42 | if (d.text.match(/^\/api\/.*/)) { 43 | addEntryAPI(d.text.substr(4), d.hits); 44 | continue; 45 | } 46 | 47 | addEntryOther(d.text, d.hits); 48 | continue; 49 | } 50 | } 51 | 52 | function onLoadLimiter(data) { 53 | document.getElementById("limiter-caller").innerHTML = data.caller; 54 | document.getElementById("limiter-created").innerHTML = (new Date(data.created)).toJSON(); 55 | document.getElementById("limiter-expires").innerHTML = (new Date(data.expires)).toJSON(); 56 | document.getElementById("limiter-enforce").innerHTML = (data.enforce ? "Yes" : "No"); 57 | document.getElementById("limiter-tokenSpend").innerHTML = data.tokenSpend; 58 | document.getElementById("limiter-tokenLimit").innerHTML = data.tokenLimit; 59 | document.getElementById("limiter-tokenCount").innerHTML = Number(data.tokenCount).toFixed(2); 60 | } 61 | 62 | function makeEntryRow(text, hits) { 63 | 64 | return ("" + text + "" + hits + ""); 65 | } 66 | 67 | function addEntryAPI(text, hits) { 68 | 69 | document.getElementById("session-requests-api").innerHTML += makeEntryRow(text, hits); 70 | } 71 | 72 | function addEntryOther(text, hits) { 73 | 74 | document.getElementById("session-requests-other").innerHTML += makeEntryRow(text, hits); 75 | } 76 | 77 | function sessionDelete() { 78 | if (!confirm("Forget this session?")) { 79 | return; 80 | } 81 | 82 | console.log("Requesting session to be deleted."); 83 | fetch('/session/', { 84 | method: 'DELETE' 85 | }).then(response => { 86 | 87 | return response.ok; 88 | }).then(okay => { 89 | if (!okay) { 90 | alert("Failed deleting session!"); 91 | return; 92 | } 93 | 94 | alert("Session deleted."); 95 | window.location.reload(); 96 | }); 97 | } 98 | -------------------------------------------------------------------------------- /resources/support.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | 8 | 9 | BarcodeAPI.org - Support 10 | 11 | 13 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 |
    28 | 29 |
    30 | 31 |
    32 |

    33 |
    FEEDBACK & SUPPORT
    34 |
    support@barcodeapi.org
    35 |

    36 |
    37 |
    38 |
    39 | 40 |
    41 |
    42 |

    Feedback & Support

    43 |
    44 | Priority support is available for our paying customers. 45 |
    46 |
    47 | If you are a free user wishing to increase your rate limit, have questions about using 48 | the service, or just have a suggestion for us, please contact us at the email address 49 | below. 50 |
    51 |
    52 |
    53 | 54 |
    55 |
    56 |

    API Docs

    57 |
    58 | Trying to use the API? 59 |
    60 |
    61 | View our docs for usage info. 62 |
    63 |
    64 | Learn More 65 |
    66 |
    67 |
    68 |

    Rate Limits

    69 |
    70 | Do your barcodes read RATE LIMIT? 71 |
    72 |
    73 | Be sure to check your rate limits. 74 |
    75 |
    76 | My Account 77 |
    78 |
    79 |
    80 | 81 |
    82 |
    83 |

    Contact Us

    84 |
    85 | Need to reach us? 86 |
    87 | Please send us an email. 88 |
    89 | We will respond as soon as possible. 90 |
    91 |
    92 | support@barcodeapi.org 93 |
    94 |
    95 |
    96 |
    97 | 98 | 99 | -------------------------------------------------------------------------------- /src/main/java/org/barcodeapi/core/ServerRuntime.java: -------------------------------------------------------------------------------- 1 | package org.barcodeapi.core; 2 | 3 | import java.net.InetAddress; 4 | import java.net.UnknownHostException; 5 | import java.util.Timer; 6 | import java.util.UUID; 7 | 8 | import com.mclarkdev.tools.libextras.LibExtrasStreams; 9 | import com.mclarkdev.tools.libmetrics.LibMetrics; 10 | 11 | /** 12 | * ServerRuntime.java 13 | * 14 | * @author Matthew R. Clark (BarcodeAPI.org, 2017-2024) 15 | */ 16 | public class ServerRuntime { 17 | 18 | // System runtime information 19 | private static final String _RUNTIME_ID; 20 | private static final long _RUNTIME_TIMESTART; 21 | private static final int _RUNTIME_VERSION; 22 | private static final String _RUNTIME_HOST; 23 | 24 | // Background task timer 25 | private static final Timer _SYS_TIMER; 26 | 27 | static { 28 | 29 | _RUNTIME_ID = UUID.randomUUID().toString(); 30 | LibMetrics.instance().setValue(_RUNTIME_ID, "system", "runtimeId"); 31 | 32 | _RUNTIME_TIMESTART = System.currentTimeMillis(); 33 | LibMetrics.instance().setValue(_RUNTIME_TIMESTART, "system", "time", "start"); 34 | 35 | try { 36 | _RUNTIME_VERSION = Integer.parseInt(LibExtrasStreams.readStream(// 37 | ServerRuntime.class.getResourceAsStream("/app.version"))); 38 | LibMetrics.instance().setValue(_RUNTIME_VERSION, "system", "version"); 39 | } catch (Exception e) { 40 | throw new RuntimeException(e); 41 | } 42 | 43 | try { 44 | _RUNTIME_HOST = InetAddress.getLocalHost().getCanonicalHostName(); 45 | LibMetrics.instance().setValue(_RUNTIME_HOST, "system", "host"); 46 | } catch (UnknownHostException e) { 47 | throw new RuntimeException(e); 48 | } 49 | 50 | _SYS_TIMER = new Timer(); 51 | } 52 | 53 | /** 54 | * Returns the current runtime ID of the server. 55 | * 56 | * @return the current runtime ID of the server 57 | */ 58 | public static final String getRuntimeID() { 59 | return _RUNTIME_ID; 60 | } 61 | 62 | /** 63 | * Returns the time, in milliseconds, the server was started. 64 | * 65 | * @return the time the server was started 66 | */ 67 | public static final long getTimeStart() { 68 | return _RUNTIME_TIMESTART; 69 | } 70 | 71 | /** 72 | * Returns the amount of time, in milliseconds, the server has been running. 73 | * 74 | * @return the amount of time the server has been running 75 | */ 76 | public static final long getTimeRunning() { 77 | return System.currentTimeMillis() - getTimeStart(); 78 | } 79 | 80 | /** 81 | * Returns the build version of the server. 82 | * 83 | * @return the build version of the server 84 | */ 85 | public static final int getVersion() { 86 | return _RUNTIME_VERSION; 87 | } 88 | 89 | /** 90 | * Returns the system host name. 91 | * 92 | * @return the system host name 93 | */ 94 | public static final String getHostname() { 95 | return _RUNTIME_HOST; 96 | } 97 | 98 | /** 99 | * Returns an instance of the system timer. 100 | * 101 | * @return an instance of the system timer 102 | */ 103 | public static final Timer getSystemTimer() { 104 | return _SYS_TIMER; 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /resources/ext/js/limits.js: -------------------------------------------------------------------------------- 1 | // 2 | // BarcodeAPI.org, 2017-2025 3 | // limits.js // limits.html 4 | // 5 | 6 | window.addEventListener("load", init); 7 | 8 | function init() { 9 | 10 | // Load limiter info 11 | fetch('/limiter/') 12 | .then(response => { 13 | return response.json(); 14 | }) 15 | .then(data => { 16 | if (!data.enforce) { 17 | document.getElementsByClassName("notice-enforced")[0].style.display = 'block'; 18 | document.getElementsByClassName("notice-abusers")[0].style.display = 'none'; 19 | } 20 | 21 | document.getElementById("token_count").innerHTML = // 22 | ((data.tokenCount == -1) ? "Unlimited" : data.tokenCount.toFixed(2)); 23 | 24 | document.getElementById("token_limit").innerHTML = // 25 | ((data.tokenLimit == -1) ? "Unlimited" : data.tokenLimit); 26 | 27 | // Log tracking event 28 | var setupMillis = ((new Date()) - timeStart); 29 | trackingEvent("AppEvents", "AppLoad", "Limits", setupMillis); 30 | }); 31 | 32 | // Load plans info 33 | fetch('/plans/') 34 | .then(response => { 35 | return response.json(); 36 | }) 37 | .then(data => { 38 | 39 | // Add free plan details 40 | document.querySelector(".free-details").innerHTML = data.free.description; 41 | document.querySelector(".free-support").innerHTML = data.free.support; 42 | 43 | // Check if has paid plans 44 | if (data.paid.length > 0) { 45 | 46 | // Show free lmit in info message 47 | document.querySelector(".free-limit").innerHTML = data.free.limit.toLocaleString(); 48 | 49 | // Show information about subscriber plans 50 | document.querySelector(".plan-overview").style.display = "block"; 51 | 52 | // Loop and add each paid plan 53 | for (var x in data.paid) { 54 | addPlan(data.paid[x]); 55 | } 56 | } 57 | 58 | // Delete the plan template 59 | delTemplate(); 60 | }); 61 | } 62 | 63 | function addPlan(plan) { 64 | 65 | var info = document.getElementById("plan-template").cloneNode(true); 66 | 67 | info.setAttribute("id", "plan-X"); 68 | 69 | info.querySelector(".plan-name").innerHTML = plan.name; 70 | 71 | var planPrices = document.createElement("span"); 72 | 73 | for (option in plan.price) { 74 | 75 | if (option > 0) { 76 | var spacer = document.createElement("span"); 77 | spacer.innerHTML = " || "; 78 | planPrices.appendChild(spacer); 79 | } 80 | 81 | var price = plan.price[option]; 82 | var priceInfo = document.createElement("a"); 83 | priceInfo.innerHTML = price.name; 84 | 85 | if (price.link) { 86 | priceInfo.href = price.link; 87 | } 88 | 89 | planPrices.appendChild(priceInfo); 90 | } 91 | 92 | info.querySelector(".plan-prices").appendChild(planPrices); 93 | 94 | info.querySelector(".plan-details").innerHTML = plan.description; 95 | info.querySelector(".plan-support").innerHTML = plan.support; 96 | 97 | document.getElementById("user-plans").append(info); 98 | } 99 | 100 | function delTemplate() { 101 | document.getElementById("user-plans")// 102 | .removeChild(document.getElementById("plan-template")); 103 | } 104 | -------------------------------------------------------------------------------- /src/main/java/org/barcodeapi/server/gen/BarcodeCanvasProvider.java: -------------------------------------------------------------------------------- 1 | package org.barcodeapi.server.gen; 2 | 3 | import java.awt.Color; 4 | import java.awt.Graphics2D; 5 | import java.awt.geom.Rectangle2D; 6 | import java.awt.image.BufferedImage; 7 | import java.io.ByteArrayOutputStream; 8 | import java.io.IOException; 9 | 10 | import org.krysalis.barcode4j.BarcodeDimension; 11 | import org.krysalis.barcode4j.TextAlignment; 12 | import org.krysalis.barcode4j.output.AbstractCanvasProvider; 13 | import org.krysalis.barcode4j.output.bitmap.BitmapBuilder; 14 | import org.krysalis.barcode4j.output.bitmap.BitmapEncoder; 15 | import org.krysalis.barcode4j.output.bitmap.BitmapEncoderRegistry; 16 | import org.krysalis.barcode4j.output.java2d.Java2DCanvasProvider; 17 | 18 | /** 19 | * BarcodeCanvasProvider.java 20 | * 21 | * @author Matthew R. Clark (BarcodeAPI.org, 2017-2024) 22 | */ 23 | public class BarcodeCanvasProvider extends AbstractCanvasProvider { 24 | 25 | private static final String _MIME = "image/x-png"; 26 | 27 | private static final BitmapEncoder _ENCODER = // 28 | BitmapEncoderRegistry.getInstance(_MIME); 29 | 30 | private int dpi; 31 | private BufferedImage image; 32 | private Graphics2D g2d; 33 | private Java2DCanvasProvider delegate; 34 | 35 | private final Color colorBG; 36 | private final Color colorFG; 37 | 38 | public BarcodeCanvasProvider(int dpi, String bg, String fg) { 39 | super(0); 40 | 41 | this.dpi = dpi; 42 | this.colorBG = Color.decode("0x" + bg); 43 | this.colorFG = Color.decode("0x" + fg); 44 | } 45 | 46 | /** 47 | * Encode and render the image. 48 | * 49 | * @return the image bytes 50 | * @throws IOException generation failure 51 | */ 52 | public byte[] finish() throws IOException { 53 | 54 | ByteArrayOutputStream out = // 55 | new ByteArrayOutputStream(); 56 | 57 | synchronized (image) { 58 | 59 | image.flush(); 60 | _ENCODER.encode(image, out, _MIME, dpi); 61 | } 62 | 63 | return out.toByteArray(); 64 | } 65 | 66 | /** {@inheritDoc} */ 67 | public void establishDimensions(BarcodeDimension dim) { 68 | super.establishDimensions(dim); 69 | boolean twoTone = ((colorBG.equals(Color.white)) && (colorFG.equals(Color.black))); 70 | int format = ((twoTone) ? BufferedImage.TYPE_BYTE_BINARY : BufferedImage.TYPE_INT_RGB); 71 | this.image = BitmapBuilder.prepareImage(dim, getOrientation(), dpi, format); 72 | this.g2d = BitmapBuilder.prepareGraphics2D(this.image, dim, 0, false); 73 | this.delegate = new Java2DCanvasProvider(g2d, 0); 74 | this.delegate.establishDimensions(dim); 75 | this.g2d.setColor(colorBG); 76 | this.g2d.fill(new Rectangle2D.Double(0, 0, image.getWidth(), image.getHeight())); 77 | } 78 | 79 | /** {@inheritDoc} */ 80 | public void deviceFillRect(double x, double y, double w, double h) { 81 | this.g2d.setColor(colorFG); 82 | this.g2d.fill(new Rectangle2D.Double(x, y, w, h)); 83 | } 84 | 85 | /** {@inheritDoc} */ 86 | public void deviceText(String text, double x1, double x2, double y1, String fontName, double fontSize, 87 | TextAlignment textAlign) { 88 | this.g2d.setColor(colorFG); 89 | this.g2d.setPaint(colorFG); 90 | this.delegate.deviceText(text, x1, x2, y1, fontName, fontSize, textAlign); 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /resources/type.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | 8 | 9 | BarcodeAPI.org - $TYPE$ 10 | 11 | 13 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 |
    29 | 30 |
    31 | 32 |
    33 |

    34 |
    FEEDBACK & SUPPORT
    35 |
    support@barcodeapi.org
    36 |

    37 |
    38 |
    39 |
    40 | 41 |
    42 | 51 |
    52 | 53 |
    54 |
    55 |
    56 | - 57 |
    58 |
    59 | Cost 60 |
    61 | Basic: - 62 |
    63 | Custom: - 64 |
    65 |
    66 | Target: - 67 |
    68 |
    69 | Format: - 70 |
    71 |
    72 | Decode Supported: - 73 |
    74 | Checksums Enforced: - 75 |
    76 | Non-Printing Characters: - ? 78 |
    79 |
    80 |
    -
    81 |
    82 |
    83 | Read more on Wikipedia. 84 |
    85 |
    86 | Parameters 87 |
    88 |
    -
    89 |
    90 |
    -
    91 |
    92 |
    93 |
    94 | 95 | 96 | 97 |
    98 |
    99 |
    100 |
    101 | 102 | 103 | -------------------------------------------------------------------------------- /src/main/java/org/barcodeapi/server/gen/types/AprilTagGenerator.java: -------------------------------------------------------------------------------- 1 | package org.barcodeapi.server.gen.types; 2 | 3 | import java.awt.image.BufferedImage; 4 | import java.io.ByteArrayOutputStream; 5 | import java.io.IOException; 6 | import java.util.HashMap; 7 | 8 | import javax.imageio.ImageIO; 9 | 10 | import org.barcodeapi.server.core.CodeType; 11 | import org.barcodeapi.server.core.GenerationException; 12 | import org.barcodeapi.server.core.GenerationException.ExceptionType; 13 | import org.barcodeapi.server.gen.BarcodeRequest; 14 | import org.barcodeapi.server.gen.CodeGenerator; 15 | import org.json.JSONObject; 16 | 17 | import com.google.zxing.WriterException; 18 | import com.mclarkdev.tools.libapriltag.TagFamily; 19 | import com.mclarkdev.tools.libapriltag.families.Tag16h5; 20 | import com.mclarkdev.tools.libapriltag.families.Tag25h9; 21 | import com.mclarkdev.tools.libapriltag.families.Tag36h10; 22 | import com.mclarkdev.tools.libapriltag.families.Tag36h11; 23 | import com.mclarkdev.tools.libapriltag.families.Tag36h9; 24 | import com.mclarkdev.tools.libapriltag.families.TagCircle21h7; 25 | import com.mclarkdev.tools.libapriltag.families.TagCircle49h12; 26 | import com.mclarkdev.tools.libapriltag.families.TagCustom48h12; 27 | import com.mclarkdev.tools.libapriltag.families.TagStandard41h12; 28 | import com.mclarkdev.tools.libapriltag.families.TagStandard52h13; 29 | 30 | /** 31 | * AztecGenerator.java 32 | * 33 | * @author Matthew R. Clark (BarcodeAPI.org, 2017-2024) 34 | */ 35 | public class AprilTagGenerator extends CodeGenerator { 36 | 37 | private final HashMap tagFamilies; 38 | 39 | public AprilTagGenerator(CodeType codeType) { 40 | super(codeType); 41 | 42 | tagFamilies = new HashMap<>(); 43 | 44 | // Basic tag families 45 | tagFamilies.put("tag16h5", new Tag16h5()); 46 | tagFamilies.put("tag25h9", new Tag25h9()); 47 | tagFamilies.put("tag36h9", new Tag36h9()); 48 | tagFamilies.put("tag36h10", new Tag36h10()); 49 | tagFamilies.put("tag36h11", new Tag36h11()); 50 | 51 | // Extended tag families 52 | tagFamilies.put("tagCircle21h7", new TagCircle21h7()); 53 | tagFamilies.put("tagCircle49h12", new TagCircle49h12()); 54 | tagFamilies.put("tagCustom48h12", new TagCustom48h12()); 55 | tagFamilies.put("tagStandard41h12", new TagStandard41h12()); 56 | tagFamilies.put("tagStandard52h13", new TagStandard52h13()); 57 | } 58 | 59 | @Override 60 | public byte[] onRender(BarcodeRequest request) throws WriterException, IOException, GenerationException { 61 | 62 | String[] parts = request.getData().split(":"); 63 | 64 | String family = parts[0]; 65 | int id = Integer.parseInt(parts[1]); 66 | 67 | JSONObject options = request.getOptions(); 68 | 69 | HashMap defaults = // 70 | request.getType().getDefaults(); 71 | 72 | TagFamily tagFamily = tagFamilies.get(family); 73 | 74 | if (tagFamily == null) { 75 | throw new GenerationException(ExceptionType.INVALID, // 76 | new Throwable("Unsupported tag type.")); 77 | } 78 | 79 | int scale = options.optInt("scale", // 80 | (Integer) defaults.getOrDefault("scale", 8)); 81 | 82 | BufferedImage img = tagFamily.getLayout().renderToImage(tagFamily.getCodes()[id], scale); 83 | 84 | try { 85 | ByteArrayOutputStream baos = new ByteArrayOutputStream(); 86 | ImageIO.write(img, "png", baos); 87 | baos.close(); 88 | return baos.toByteArray(); 89 | } catch (Exception e) { 90 | return null; 91 | } 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /src/test/java/org/barcodeapi/test/gen/types/TestEan8.java: -------------------------------------------------------------------------------- 1 | package org.barcodeapi.test.gen.types; 2 | 3 | import org.barcodeapi.server.ServerTestBase; 4 | import org.barcodeapi.server.core.GenerationException.ExceptionType; 5 | import org.eclipse.jetty.http.HttpStatus; 6 | import org.junit.Assert; 7 | import org.junit.Test; 8 | 9 | public class TestEan8 extends ServerTestBase { 10 | 11 | @Test 12 | public void testEan8_8Nums() { 13 | 14 | apiGet("8/12345670"); 15 | 16 | Assert.assertEquals("Response Code", // 17 | HttpStatus.OK_200, getResponseCode()); 18 | 19 | Assert.assertEquals("Code Type", // 20 | "EAN8", getHeader("X-Barcode-Type")); 21 | 22 | Assert.assertEquals("Code Data", // 23 | "12345670", getHeader("X-Barcode-Content")); 24 | } 25 | 26 | @Test 27 | public void testEan8_7Nums() { 28 | 29 | apiGet("8/1234567"); 30 | 31 | Assert.assertEquals("Response Code", // 32 | HttpStatus.OK_200, getResponseCode()); 33 | 34 | Assert.assertEquals("Code Type", // 35 | "EAN8", getHeader("X-Barcode-Type")); 36 | 37 | // Checksum is filled in 38 | Assert.assertEquals("Code Data", // 39 | "12345670", getHeader("X-Barcode-Content")); 40 | } 41 | 42 | @Test 43 | public void testEan8_8NumsInvalidChecksum() throws Exception { 44 | 45 | apiGet("8/12345678"); 46 | 47 | Assert.assertEquals("Response Code", // 48 | ExceptionType.CHECKSUM.getStatusCode(), getResponseCode()); 49 | 50 | Assert.assertEquals("Error Message", // 51 | "Invalid checksum: expected 0", getHeader("X-Error-Message")); 52 | } 53 | 54 | @Test 55 | public void testEan8_TooShort() throws Exception { 56 | 57 | apiGet("8/123456"); 58 | 59 | Assert.assertEquals("Response Code", // 60 | ExceptionType.INVALID.getStatusCode(), getResponseCode()); 61 | 62 | Assert.assertEquals("Response Message", // 63 | "Invalid data for selected code type.", getHeader("X-Error-Message")); 64 | } 65 | 66 | @Test 67 | public void testEan8_TooLong() throws Exception { 68 | 69 | apiGet("8/123456789"); 70 | 71 | Assert.assertEquals("Response Code", // 72 | ExceptionType.INVALID.getStatusCode(), getResponseCode()); 73 | 74 | Assert.assertEquals("Response Message", // 75 | "Invalid data for selected code type.", getHeader("X-Error-Message")); 76 | } 77 | 78 | @Test 79 | public void testEan8_WithLetters() throws Exception { 80 | 81 | apiGet("8/ABCDEFGH"); 82 | 83 | Assert.assertEquals("Response Code", // 84 | ExceptionType.INVALID.getStatusCode(), getResponseCode()); 85 | 86 | Assert.assertEquals("Response Message", // 87 | "Invalid data for selected code type.", getHeader("X-Error-Message")); 88 | } 89 | 90 | @Test 91 | public void testEan8_WithSymbols() throws Exception { 92 | 93 | apiGet("8/!@"); 94 | 95 | Assert.assertEquals("Response Code", // 96 | ExceptionType.INVALID.getStatusCode(), getResponseCode()); 97 | 98 | Assert.assertEquals("Response Message", // 99 | "Invalid data for selected code type.", getHeader("X-Error-Message")); 100 | } 101 | 102 | @Test 103 | public void testEan8_WithUnicode() throws Exception { 104 | 105 | apiGet("8/Ω"); 106 | 107 | Assert.assertEquals("Response Code", // 108 | ExceptionType.INVALID.getStatusCode(), getResponseCode()); 109 | 110 | Assert.assertEquals("Response Message", // 111 | "Invalid data for selected code type.", getHeader("X-Error-Message")); 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /src/test/java/org/barcodeapi/test/gen/types/TestUPCE.java: -------------------------------------------------------------------------------- 1 | package org.barcodeapi.test.gen.types; 2 | 3 | import org.barcodeapi.server.ServerTestBase; 4 | import org.barcodeapi.server.core.GenerationException.ExceptionType; 5 | import org.eclipse.jetty.http.HttpStatus; 6 | import org.junit.Assert; 7 | import org.junit.Test; 8 | 9 | public class TestUPCE extends ServerTestBase { 10 | 11 | @Test 12 | public void testUPCE_8Nums() { 13 | 14 | apiGet("e/01234565"); 15 | 16 | Assert.assertEquals("Response Code", // 17 | HttpStatus.OK_200, getResponseCode()); 18 | 19 | Assert.assertEquals("Code Type", // 20 | "UPC_E", getHeader("X-Barcode-Type")); 21 | 22 | Assert.assertEquals("Code Data", // 23 | "01234565", getHeader("X-Barcode-Content")); 24 | } 25 | 26 | @Test 27 | public void testUPCE_7Nums() { 28 | 29 | apiGet("e/0123456"); 30 | 31 | Assert.assertEquals("Response Code", // 32 | HttpStatus.OK_200, getResponseCode()); 33 | 34 | Assert.assertEquals("Code Type", // 35 | "UPC_E", getHeader("X-Barcode-Type")); 36 | 37 | // Checksum is filled in 38 | Assert.assertEquals("Code Data", // 39 | "01234565", getHeader("X-Barcode-Content")); 40 | } 41 | 42 | @Test 43 | public void testUPCE_8NumsInvalidChecksum() throws Exception { 44 | 45 | apiGet("e/01234567"); 46 | 47 | Assert.assertEquals("Response Code", // 48 | ExceptionType.CHECKSUM.getStatusCode(), getResponseCode()); 49 | 50 | Assert.assertEquals("Error Message", // 51 | "Invalid checksum: expected 5", getHeader("X-Error-Message")); 52 | } 53 | 54 | @Test 55 | public void testUPCE_TooShort() throws Exception { 56 | 57 | apiGet("e/012345"); 58 | 59 | Assert.assertEquals("Response Code", // 60 | ExceptionType.INVALID.getStatusCode(), getResponseCode()); 61 | 62 | Assert.assertEquals("Response Message", // 63 | "Invalid data for selected code type.", getHeader("X-Error-Message")); 64 | } 65 | 66 | @Test 67 | public void testUPCE_TooLong() throws Exception { 68 | 69 | apiGet("e/012345678"); 70 | 71 | Assert.assertEquals("Response Code", // 72 | ExceptionType.INVALID.getStatusCode(), getResponseCode()); 73 | 74 | Assert.assertEquals("Response Message", // 75 | "Invalid data for selected code type.", getHeader("X-Error-Message")); 76 | } 77 | 78 | @Test 79 | public void testUPCE_WithLetters() throws Exception { 80 | 81 | apiGet("e/ABCDEFGH"); 82 | 83 | Assert.assertEquals("Response Code", // 84 | ExceptionType.INVALID.getStatusCode(), getResponseCode()); 85 | 86 | Assert.assertEquals("Response Message", // 87 | "Invalid data for selected code type.", getHeader("X-Error-Message")); 88 | } 89 | 90 | @Test 91 | public void testUPCE_WithSymbols() throws Exception { 92 | 93 | apiGet("e/!@"); 94 | 95 | Assert.assertEquals("Response Code", // 96 | ExceptionType.INVALID.getStatusCode(), getResponseCode()); 97 | 98 | Assert.assertEquals("Response Message", // 99 | "Invalid data for selected code type.", getHeader("X-Error-Message")); 100 | } 101 | 102 | @Test 103 | public void testUPCE_WithUnicode() throws Exception { 104 | 105 | apiGet("e/Ω"); 106 | 107 | Assert.assertEquals("Response Code", // 108 | ExceptionType.INVALID.getStatusCode(), getResponseCode()); 109 | 110 | Assert.assertEquals("Response Message", // 111 | "Invalid data for selected code type.", getHeader("X-Error-Message")); 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /src/test/java/org/barcodeapi/test/gen/types/TestUPCA.java: -------------------------------------------------------------------------------- 1 | package org.barcodeapi.test.gen.types; 2 | 3 | import org.barcodeapi.server.ServerTestBase; 4 | import org.barcodeapi.server.core.GenerationException.ExceptionType; 5 | import org.eclipse.jetty.http.HttpStatus; 6 | import org.junit.Assert; 7 | import org.junit.Test; 8 | 9 | public class TestUPCA extends ServerTestBase { 10 | 11 | @Test 12 | public void testUPCA_12Nums() { 13 | 14 | apiGet("a/123456789012"); 15 | 16 | Assert.assertEquals("Response Code", // 17 | HttpStatus.OK_200, getResponseCode()); 18 | 19 | Assert.assertEquals("Code Type", // 20 | "UPC_A", getHeader("X-Barcode-Type")); 21 | 22 | Assert.assertEquals("Code Data", // 23 | "123456789012", getHeader("X-Barcode-Content")); 24 | } 25 | 26 | @Test 27 | public void testUPCA_11Nums() { 28 | 29 | apiGet("a/12345678901"); 30 | 31 | Assert.assertEquals("Response Code", // 32 | HttpStatus.OK_200, getResponseCode()); 33 | 34 | Assert.assertEquals("Code Type", // 35 | "UPC_A", getHeader("X-Barcode-Type")); 36 | 37 | // Checksum is filled in 38 | Assert.assertEquals("Code Data", // 39 | "123456789012", getHeader("X-Barcode-Content")); 40 | } 41 | 42 | @Test 43 | public void testUPCA_8NumsInvalidChecksum() throws Exception { 44 | 45 | apiGet("a/123456789013"); 46 | 47 | Assert.assertEquals("Response Code", // 48 | ExceptionType.CHECKSUM.getStatusCode(), getResponseCode()); 49 | 50 | Assert.assertEquals("Error Message", // 51 | "Invalid checksum: expected 2", getHeader("X-Error-Message")); 52 | } 53 | 54 | @Test 55 | public void testUPCA_TooShort() throws Exception { 56 | 57 | apiGet("a/1234567890"); 58 | 59 | Assert.assertEquals("Response Code", // 60 | ExceptionType.INVALID.getStatusCode(), getResponseCode()); 61 | 62 | Assert.assertEquals("Response Message", // 63 | "Invalid data for selected code type.", getHeader("X-Error-Message")); 64 | } 65 | 66 | @Test 67 | public void testUPCA_TooLong() throws Exception { 68 | 69 | apiGet("a/1234567890123"); 70 | 71 | Assert.assertEquals("Response Code", // 72 | ExceptionType.INVALID.getStatusCode(), getResponseCode()); 73 | 74 | Assert.assertEquals("Response Message", // 75 | "Invalid data for selected code type.", getHeader("X-Error-Message")); 76 | } 77 | 78 | @Test 79 | public void testUPCA_WithLetters() throws Exception { 80 | 81 | apiGet("a/ABCDEFGH"); 82 | 83 | Assert.assertEquals("Response Code", // 84 | ExceptionType.INVALID.getStatusCode(), getResponseCode()); 85 | 86 | Assert.assertEquals("Response Message", // 87 | "Invalid data for selected code type.", getHeader("X-Error-Message")); 88 | } 89 | 90 | @Test 91 | public void testUPCA_WithSymbols() throws Exception { 92 | 93 | apiGet("a/!@"); 94 | 95 | Assert.assertEquals("Response Code", // 96 | ExceptionType.INVALID.getStatusCode(), getResponseCode()); 97 | 98 | Assert.assertEquals("Response Message", // 99 | "Invalid data for selected code type.", getHeader("X-Error-Message")); 100 | } 101 | 102 | @Test 103 | public void testUPCA_WithUnicode() throws Exception { 104 | 105 | apiGet("a/Ω"); 106 | 107 | Assert.assertEquals("Response Code", // 108 | ExceptionType.INVALID.getStatusCode(), getResponseCode()); 109 | 110 | Assert.assertEquals("Response Message", // 111 | "Invalid data for selected code type.", getHeader("X-Error-Message")); 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /src/test/java/org/barcodeapi/test/gen/types/TestEan13.java: -------------------------------------------------------------------------------- 1 | package org.barcodeapi.test.gen.types; 2 | 3 | import org.barcodeapi.server.ServerTestBase; 4 | import org.barcodeapi.server.core.GenerationException.ExceptionType; 5 | import org.eclipse.jetty.http.HttpStatus; 6 | import org.junit.Assert; 7 | import org.junit.Test; 8 | 9 | public class TestEan13 extends ServerTestBase { 10 | 11 | @Test 12 | public void testEan13_13Nums() { 13 | 14 | apiGet("13/1234567890128"); 15 | 16 | Assert.assertEquals("Response Code", // 17 | HttpStatus.OK_200, getResponseCode()); 18 | 19 | Assert.assertEquals("Code Type", // 20 | "EAN13", getHeader("X-Barcode-Type")); 21 | 22 | Assert.assertEquals("Code Data", // 23 | "1234567890128", getHeader("X-Barcode-Content")); 24 | } 25 | 26 | @Test 27 | public void testEan13_12Nums() { 28 | 29 | apiGet("13/123456789012"); 30 | 31 | Assert.assertEquals("Response Code", // 32 | HttpStatus.OK_200, getResponseCode()); 33 | 34 | Assert.assertEquals("Code Type", // 35 | "EAN13", getHeader("X-Barcode-Type")); 36 | 37 | // Checksum is filled in 38 | Assert.assertEquals("Code Data", // 39 | "1234567890128", getHeader("X-Barcode-Content")); 40 | } 41 | 42 | @Test 43 | public void testEan13_13NumsInvalidChecksum() throws Exception { 44 | 45 | apiGet("13/1234567890123"); 46 | 47 | Assert.assertEquals("Response Code", // 48 | ExceptionType.CHECKSUM.getStatusCode(), getResponseCode()); 49 | 50 | Assert.assertEquals("Error Message", // 51 | "Invalid checksum: expected 8", getHeader("X-Error-Message")); 52 | } 53 | 54 | @Test 55 | public void testEan13_TooShort() throws Exception { 56 | 57 | apiGet("13/12345678901"); 58 | 59 | Assert.assertEquals("Response Code", // 60 | ExceptionType.INVALID.getStatusCode(), getResponseCode()); 61 | 62 | Assert.assertEquals("Response Message", // 63 | "Invalid data for selected code type.", getHeader("X-Error-Message")); 64 | } 65 | 66 | @Test 67 | public void testEan13_TooLong() throws Exception { 68 | 69 | apiGet("13/12345678901234"); 70 | 71 | Assert.assertEquals("Response Code", // 72 | ExceptionType.INVALID.getStatusCode(), getResponseCode()); 73 | 74 | Assert.assertEquals("Response Message", // 75 | "Invalid data for selected code type.", getHeader("X-Error-Message")); 76 | } 77 | 78 | @Test 79 | public void testEan13_WithLetters() throws Exception { 80 | 81 | apiGet("13/123456789O123"); 82 | 83 | Assert.assertEquals("Response Code", // 84 | ExceptionType.INVALID.getStatusCode(), getResponseCode()); 85 | 86 | Assert.assertEquals("Response Message", // 87 | "Invalid data for selected code type.", getHeader("X-Error-Message")); 88 | } 89 | 90 | @Test 91 | public void testEan13_WithSymbols() throws Exception { 92 | 93 | apiGet("13/!@"); 94 | 95 | Assert.assertEquals("Response Code", // 96 | ExceptionType.INVALID.getStatusCode(), getResponseCode()); 97 | 98 | Assert.assertEquals("Response Message", // 99 | "Invalid data for selected code type.", getHeader("X-Error-Message")); 100 | } 101 | 102 | @Test 103 | public void testEan13_WithUnicode() throws Exception { 104 | 105 | apiGet("13/Ω"); 106 | 107 | Assert.assertEquals("Response Code", // 108 | ExceptionType.INVALID.getStatusCode(), getResponseCode()); 109 | 110 | Assert.assertEquals("Response Message", // 111 | "Invalid data for selected code type.", getHeader("X-Error-Message")); 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /src/main/java/org/barcodeapi/server/cache/CachedSession.java: -------------------------------------------------------------------------------- 1 | package org.barcodeapi.server.cache; 2 | 3 | import java.util.Map; 4 | import java.util.UUID; 5 | import java.util.concurrent.ConcurrentHashMap; 6 | 7 | import javax.servlet.http.Cookie; 8 | 9 | import org.json.JSONArray; 10 | import org.json.JSONObject; 11 | 12 | /** 13 | * CachedSession.java 14 | * 15 | * A user session object allowing for the detailed tracking of individual user 16 | * usage activities across all handlers. 17 | * 18 | * @author Matthew R. Clark (BarcodeAPI.org, 2017-2024) 19 | */ 20 | public class CachedSession extends CachedObject { 21 | 22 | private static final long serialVersionUID = 20241222L; 23 | 24 | private final String key; 25 | 26 | private final Cookie cookie; 27 | 28 | private final ConcurrentHashMap sessionIPs; 29 | private final ConcurrentHashMap sessionRequests; 30 | 31 | public CachedSession() { 32 | super("session"); 33 | 34 | // Generate new random UUID 35 | this.key = UUID.randomUUID().toString(); 36 | 37 | // Create user cookie 38 | this.cookie = new Cookie("session", this.key); 39 | this.cookie.setMaxAge((int) (getStandardTimeout() / 1000)); 40 | this.cookie.setPath("/"); 41 | 42 | // Memory map for ip and request history 43 | this.sessionIPs = new ConcurrentHashMap(); 44 | this.sessionRequests = new ConcurrentHashMap(); 45 | } 46 | 47 | /** 48 | * Returns the key for the session. 49 | * 50 | * @return the key for the session 51 | */ 52 | public String getKey() { 53 | 54 | return key; 55 | } 56 | 57 | /** 58 | * Returns the browser cookie for the session. 59 | * 60 | * @return the browser cookie for the session 61 | */ 62 | public Cookie getCookie() { 63 | 64 | return cookie; 65 | } 66 | 67 | /** 68 | * Called when the user session is loaded when navigating across handlers. 69 | * Tracks a users usage across the site, viewable through the /session/ handler. 70 | * 71 | * @param data 72 | */ 73 | public void hit(String ip, String data) { 74 | this.touch(); 75 | 76 | // Count for IP 77 | int countIP = sessionIPs.containsKey(ip) ? sessionIPs.get(ip) : 0; 78 | sessionIPs.put(ip, (countIP + 1)); 79 | 80 | // Count for request 81 | int countReq = sessionRequests.containsKey(data) ? sessionRequests.get(data) : 0; 82 | sessionRequests.put(data, (countReq + 1)); 83 | } 84 | 85 | /** 86 | * Returns the user session as a JSON object. 87 | * 88 | * @return the user session in JSON format 89 | */ 90 | public JSONObject asJSON() { 91 | 92 | JSONArray addresses = new JSONArray(); 93 | for (Map.Entry entry : sessionIPs.entrySet()) { 94 | 95 | addresses.put(new JSONObject() // 96 | .put("ip", entry.getKey())// 97 | .put("hits", entry.getValue())); 98 | } 99 | 100 | JSONArray requests = new JSONArray(); 101 | for (Map.Entry entry : sessionRequests.entrySet()) { 102 | 103 | requests.put(new JSONObject()// 104 | .put("text", entry.getKey())// 105 | .put("hits", entry.getValue())); 106 | } 107 | 108 | return (new JSONObject()// 109 | .put("key", getKey())// 110 | .put("created", getTimeCreated())// 111 | .put("expires", getTimeExpires())// 112 | .put("last", getTimeLastTouched())// 113 | .put("count", getAccessCount())// 114 | .put("addresses", addresses)// 115 | .put("requests", requests)); 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /src/main/java/org/barcodeapi/server/cache/CachedBarcode.java: -------------------------------------------------------------------------------- 1 | package org.barcodeapi.server.cache; 2 | 3 | import java.util.Base64; 4 | 5 | import org.barcodeapi.core.utils.CodeUtils; 6 | import org.barcodeapi.server.core.CodeType; 7 | import org.json.JSONObject; 8 | 9 | /** 10 | * CachedBarcode.java 11 | * 12 | * @author Matthew R. Clark (BarcodeAPI.org, 2017-2024) 13 | */ 14 | public class CachedBarcode extends CachedObject { 15 | 16 | private static final long serialVersionUID = 20241222L; 17 | 18 | private final CodeType type; 19 | private final byte[] data; 20 | 21 | private final String strRaw; 22 | private final String strNice; 23 | private final String strEncoded; 24 | 25 | public CachedBarcode(CodeType type, String raw, byte[] data) { 26 | super("barcode"); 27 | 28 | // Fail if null data 29 | if (type == null || data == null) { 30 | throw new IllegalArgumentException(); 31 | } 32 | 33 | this.type = type; 34 | this.data = data; 35 | 36 | this.strRaw = raw; 37 | this.strNice = CodeUtils.stripIllegal(raw); 38 | this.strEncoded = CodeUtils.encodeURL(raw); 39 | } 40 | 41 | /** 42 | * Returns the name of the barcode type. 43 | * 44 | * @return the name of the barcode type 45 | */ 46 | public CodeType getBarcodeType() { 47 | 48 | return type; 49 | } 50 | 51 | /** 52 | * Returns the barcode image data. 53 | * 54 | * @return the barcode image data 55 | */ 56 | public byte[] getBarcodeData() { 57 | 58 | this.touch(); 59 | return data; 60 | } 61 | 62 | /** 63 | * Returns the raw data encoded in the barcode. 64 | * 65 | * @return the raw barcode text string 66 | */ 67 | public String getBarcodeStringRaw() { 68 | 69 | return strRaw; 70 | } 71 | 72 | /** 73 | * Returns a _nice_ string representing the data encoded in the barcode. 74 | * 75 | * @return a _nice_ barcode text string 76 | */ 77 | public String getBarcodeStringNice() { 78 | 79 | return strNice; 80 | } 81 | 82 | /** 83 | * Returns an encoded string representing the data encoded in the barcode. 84 | * 85 | * @return an encoded barcode text string 86 | */ 87 | public String getBarcodeStringEncoded() { 88 | 89 | return strEncoded; 90 | } 91 | 92 | /** 93 | * returns the number of bytes in the data array. 94 | * 95 | * @return number of data bytesS 96 | */ 97 | public int getBarcodeDataSize() { 98 | 99 | return data.length; 100 | } 101 | 102 | /** 103 | * Returns the barcode data encoded as Base64. 104 | * 105 | * @return barcode data in Base64 format 106 | */ 107 | public String encodeBase64() { 108 | 109 | return Base64.getEncoder()// 110 | .encodeToString(getBarcodeData()); 111 | } 112 | 113 | /** 114 | * Returns the barcode data encoded as Base64, wrapped in a JSON object. 115 | * 116 | * @return barcode data in JSON format 117 | */ 118 | public String encodeJSON() { 119 | 120 | return (new JSONObject()// 121 | .put("text", getBarcodeStringRaw())// 122 | .put("type", getBarcodeType().getName())// 123 | .put("encoded", getBarcodeStringEncoded())// 124 | .put("base64", encodeBase64())// 125 | ).toString(); 126 | } 127 | 128 | /** 129 | * Returns an HTML page with the barcode embedded as Base-64. 130 | * 131 | * @return barcode in an HTML format page 132 | */ 133 | public String encodeHTML() { 134 | return String.format(// 135 | "", encodeBase64()); 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /resources/ext/js/decode.js: -------------------------------------------------------------------------------- 1 | // 2 | // BarcodeAPI.org, 2017-2025 3 | // decode.js // decode.html 4 | // 5 | 6 | // 2MB max upload file size 7 | const MAX_SIZE = 2 * 1024 * 1024; 8 | 9 | // Supported file types for decoding 10 | const VALID_IMAGE_TYPES = ['image/jpeg', 'image/png', 'image/bmp']; 11 | 12 | function init() { 13 | const fileSelect = document.getElementById("decode-image-select"); 14 | fileSelect.addEventListener('change', handleFileSelection); 15 | 16 | const decodeButton = document.getElementById("decode-image-button"); 17 | decodeButton.addEventListener('click', submitForDecode); 18 | 19 | const clearButton = document.getElementById("decode-preview-clear"); 20 | clearButton.addEventListener("click", clearPreview); 21 | 22 | // Log tracking event 23 | var setupMillis = ((new Date()) - timeStart); 24 | trackingEvent("AppEvents", "AppLoad", "Decode", setupMillis); 25 | } 26 | 27 | function handleFileSelection() { 28 | const file = document.getElementById("decode-image-select").files[0]; 29 | 30 | if (!isFileSupported(file)) { 31 | clearPreview(); // Clear the preview if the file is unsupported 32 | return; 33 | } 34 | 35 | previewImage(file); 36 | toggleSubmitButton(!!this.value.length); 37 | } 38 | 39 | function isFileSupported(file) { 40 | if (!file) { 41 | console.warn("No file selected."); 42 | return false; 43 | } 44 | 45 | // Check file type 46 | if (!VALID_IMAGE_TYPES.includes(file.type)) { 47 | console.error("Unsupported file type."); 48 | return false; 49 | } 50 | 51 | // Check file size 52 | if (file.size > MAX_SIZE) { 53 | console.error("Image exceeds maximum size."); 54 | return false; 55 | } 56 | 57 | return true; 58 | } 59 | 60 | function previewImage(file) { 61 | const img = document.getElementById('decode-image-preview'); 62 | img.src = ""; // Clear previous preview 63 | 64 | const reader = new FileReader(); 65 | reader.onload = (e) => { 66 | img.src = e.target.result; 67 | img.style.display = 'block'; 68 | }; 69 | reader.readAsDataURL(file); 70 | 71 | // Log tracking event 72 | trackingEvent("Decode", "Preview"); 73 | } 74 | 75 | function clearPreview() { 76 | const img = document.getElementById('decode-image-preview'); 77 | img.src = ""; 78 | img.style.display = 'none'; 79 | document.getElementById("decode-image-select").value = ""; 80 | toggleSubmitButton(false); 81 | } 82 | 83 | function toggleSubmitButton(enabled) { 84 | const submitButton = document.getElementById("decode-image-button"); 85 | submitButton.disabled = !enabled; 86 | } 87 | 88 | function submitForDecode(e) { 89 | console.log("Submit for decoding:", e); 90 | const fileInput = document.getElementById("decode-image-select"); 91 | const file = fileInput.files[0]; 92 | 93 | if (!file) { 94 | console.error("No file selected for decoding."); 95 | return; 96 | } 97 | 98 | const formData = new FormData(); 99 | formData.append('image', file); 100 | 101 | fetch('/decode/', { 102 | method: 'POST', 103 | body: formData, 104 | }).then(response => { 105 | 106 | if (!response.ok) { 107 | throw new Error(`Error: ${response.statusText}`); 108 | } 109 | return response.json(); 110 | }).then(data => { 111 | 112 | console.log("Decoded data:", data); 113 | }).catch(error => { 114 | 115 | console.error("Error during file upload:", error); 116 | }); 117 | 118 | // Log tracking event 119 | trackingEvent("Decode", "Upload"); 120 | } 121 | -------------------------------------------------------------------------------- /resources/limits.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | 8 | 9 | BarcodeAPI.org - Rate Limits 10 | 11 | 13 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 |
    29 | 30 |
    31 | 32 |
    33 |

    34 |
    FEEDBACK & SUPPORT
    35 |
    support@barcodeapi.org
    36 |

    37 |
    38 |
    39 |
    40 | 41 |
    42 |
    43 |

    Rate Limits

    44 |
    45 | Token Limit/day  47 |
    48 | Tokens Remaining 49 |
    50 |
    51 | Note: Rate limits are not currently enforced. 52 |
    53 |
    54 |
    55 | 56 |
    57 |
    58 | Unlock the full potential of BarcodeAPI.org with our API pricing plans! 59 |
    60 |
    61 | Our token per day 62 | FREE plan should be sufficient for most users. 63 |
    64 |
    65 | 66 |
    67 |
    68 |

    Free Tier

    69 |
    70 | ALWAYS FREE 71 |

    72 | 73 |

    74 | 75 |
    76 |
    77 |
    78 | 79 |
    80 |
    81 |

    82 | 83 |

    84 | 85 |

    86 | 87 |
    88 |
    89 | 90 |
    91 | 99 | 100 |
    101 |
    Note: Abusers of the API will be IP banned. (More) 102 |
    103 |
    104 |
    105 | 106 | 107 | --------------------------------------------------------------------------------