diff --git a/src/main/java/lc/captchas/FontFunCaptcha.java b/src/main/java/lc/captchas/FontFunCaptcha.java index 8dbea52..6cf533c 100644 --- a/src/main/java/lc/captchas/FontFunCaptcha.java +++ b/src/main/java/lc/captchas/FontFunCaptcha.java @@ -81,7 +81,7 @@ public class FontFunCaptcha implements ChallengeProvider { return baos.toByteArray(); } - public Challenge returnChallenge() { + public Challenge returnChallenge(String level, String size) { String secret = HelperFunctions.randomString(7); String path = "./lib/fonts/"; return new Challenge(fontFun(secret, "medium", path), "image/png", secret.toLowerCase()); diff --git a/src/main/java/lc/captchas/PoppingCharactersCaptcha.java b/src/main/java/lc/captchas/PoppingCharactersCaptcha.java index 0ffa4df..5027891 100644 --- a/src/main/java/lc/captchas/PoppingCharactersCaptcha.java +++ b/src/main/java/lc/captchas/PoppingCharactersCaptcha.java @@ -100,7 +100,7 @@ public class PoppingCharactersCaptcha implements ChallengeProvider { "supportedInputType", List.of("text")); } - public Challenge returnChallenge() { + public Challenge returnChallenge(String level, String size) { final var secret = HelperFunctions.randomString(6); return new Challenge(gifCaptcha(secret), "image/gif", secret.toLowerCase()); } diff --git a/src/main/java/lc/captchas/ShadowTextCaptcha.java b/src/main/java/lc/captchas/ShadowTextCaptcha.java index a797c50..7e9b71b 100644 --- a/src/main/java/lc/captchas/ShadowTextCaptcha.java +++ b/src/main/java/lc/captchas/ShadowTextCaptcha.java @@ -74,7 +74,7 @@ public class ShadowTextCaptcha implements ChallengeProvider { return baos.toByteArray(); } - public Challenge returnChallenge() { + public Challenge returnChallenge(String level, String size) { String secret = HelperFunctions.randomString(6); return new Challenge(shadowText(secret), "image/png", secret.toLowerCase()); } diff --git a/src/main/java/lc/captchas/interfaces/ChallengeProvider.java b/src/main/java/lc/captchas/interfaces/ChallengeProvider.java index a3a70e2..f445a7d 100644 --- a/src/main/java/lc/captchas/interfaces/ChallengeProvider.java +++ b/src/main/java/lc/captchas/interfaces/ChallengeProvider.java @@ -6,7 +6,7 @@ import java.util.List; public interface ChallengeProvider { public String getId(); - public Challenge returnChallenge(); + public Challenge returnChallenge(String level, String size); public boolean checkAnswer(String secret, String answer); diff --git a/src/main/resources/index.html b/src/main/resources/index.html index db1a33b..9321607 100644 --- a/src/main/resources/index.html +++ b/src/main/resources/index.html @@ -20,9 +20,10 @@ const levelInput = document.getElementById("levelInput").value const mediaInput = document.getElementById("mediaInput").value const typeInput = document.getElementById("typeInput").value - fetch("/v1/captcha", { + const sizeInput = document.getElementById("sizeInput").value + fetch("/v2/captcha", { method: 'POST', - body: JSON.stringify({level: levelInput, media: mediaInput, "input_type" : typeInput}) + body: JSON.stringify({level: levelInput, media: mediaInput, "input_type" : typeInput, "size": sizeInput}) }).then(async function(resp) { const respJson = await resp.json() if (resp.ok) { @@ -30,7 +31,7 @@ const resultDiv = document.getElementById("result") const result = `

Id: ${id}

-

+

@@ -43,7 +44,7 @@ } async function submitAnswer(id) { const ans = document.getElementById("answerInput").value; - const resp = await fetch("/v1/answer", { + const resp = await fetch("/v2/answer", { method: 'POST', body: JSON.stringify({id: id, answer: ans}) }) @@ -70,6 +71,10 @@ Input Type
+
+ Input Size + +
diff --git a/src/main/scala/lc/background/taskThread.scala b/src/main/scala/lc/background/taskThread.scala index 431c93e..98b05b9 100644 --- a/src/main/scala/lc/background/taskThread.scala +++ b/src/main/scala/lc/background/taskThread.scala @@ -45,8 +45,10 @@ class BackgroundTask(config: Config, captchaManager: CaptchaManager) { (config.captchaConfig).flatMap {captcha => (captcha.allowedLevels).flatMap {level => (captcha.allowedMedia).flatMap {media => - (captcha.allowedInputType).map {inputType => - Parameters(level, media, inputType, Some(Size(0, 0))) + (captcha.allowedInputType).flatMap {inputType => + (captcha.allowedSizes).map {size => + Parameters(level, media, inputType, size) + } } } } @@ -58,8 +60,9 @@ class BackgroundTask(config: Config, captchaManager: CaptchaManager) { val level = pickRandom(captcha.allowedLevels) val media = pickRandom(captcha.allowedMedia) val inputType = pickRandom(captcha.allowedInputType) + val size = pickRandom(captcha.allowedSizes) - Parameters(level, media, inputType, Some(Size(0, 0))) + Parameters(level, media, inputType, size) } private def pickRandom[T](list: List[T]): T = { diff --git a/src/main/scala/lc/captchas/DebugCaptcha.scala b/src/main/scala/lc/captchas/DebugCaptcha.scala index b0ca809..4f2bbb0 100644 --- a/src/main/scala/lc/captchas/DebugCaptcha.scala +++ b/src/main/scala/lc/captchas/DebugCaptcha.scala @@ -66,7 +66,7 @@ class DebugCaptcha extends ChallengeProvider { baos.toByteArray() } - def returnChallenge(): Challenge = { + def returnChallenge(level: String, size: String): Challenge = { val secret = HelperFunctions.randomString(6, HelperFunctions.safeAlphabets) new Challenge(simpleText(secret), "image/png", secret.toLowerCase()) } diff --git a/src/main/scala/lc/captchas/FilterChallenge.scala b/src/main/scala/lc/captchas/FilterChallenge.scala index 5f83ff6..ef3bbc2 100644 --- a/src/main/scala/lc/captchas/FilterChallenge.scala +++ b/src/main/scala/lc/captchas/FilterChallenge.scala @@ -29,7 +29,7 @@ class FilterChallenge extends ChallengeProvider { ) } - def returnChallenge(): Challenge = { + def returnChallenge(level: String, size: String): Challenge = { val filterTypes = List(new FilterType1, new FilterType2) val r = new scala.util.Random val alphabet = "abcdefghijklmnopqrstuvwxyz" diff --git a/src/main/scala/lc/captchas/LabelCaptcha.scala b/src/main/scala/lc/captchas/LabelCaptcha.scala index 5173346..3a02770 100644 --- a/src/main/scala/lc/captchas/LabelCaptcha.scala +++ b/src/main/scala/lc/captchas/LabelCaptcha.scala @@ -40,7 +40,7 @@ class LabelCaptcha extends ChallengeProvider { ) } - def returnChallenge(): Challenge = + def returnChallenge(level: String, size: String): Challenge = synchronized { val r = scala.util.Random.nextInt(knownFiles.length) val s = scala.util.Random.nextInt(unknownFiles.length) diff --git a/src/main/scala/lc/captchas/RainDropsCaptcha.scala b/src/main/scala/lc/captchas/RainDropsCaptcha.scala index 9e8f6f3..02dfd88 100644 --- a/src/main/scala/lc/captchas/RainDropsCaptcha.scala +++ b/src/main/scala/lc/captchas/RainDropsCaptcha.scala @@ -56,7 +56,7 @@ class RainDropsCP extends ChallengeProvider { }) } - def returnChallenge(): Challenge = { + def returnChallenge(level: String, size: String): Challenge = { val r = new scala.util.Random val secret = LazyList.continually(r.nextInt(alphabet.size)).map(alphabet).take(n).mkString val width = 450 diff --git a/src/main/scala/lc/core/captchaFields.scala b/src/main/scala/lc/core/captchaFields.scala index acc9f00..ee0285c 100644 --- a/src/main/scala/lc/core/captchaFields.scala +++ b/src/main/scala/lc/core/captchaFields.scala @@ -10,7 +10,7 @@ object ParametersEnum extends Enumeration { val ALLOWEDLEVELS: Value = Value("allowedLevels") val ALLOWEDMEDIA: Value = Value("allowedMedia") val ALLOWEDINPUTTYPE: Value = Value("allowedInputType") - + val ALLOWEDSIZES: Value = Value("allowedSizes") } object AttributesEnum extends Enumeration { diff --git a/src/main/scala/lc/core/captchaManager.scala b/src/main/scala/lc/core/captchaManager.scala index 96620bf..2df9c42 100644 --- a/src/main/scala/lc/core/captchaManager.scala +++ b/src/main/scala/lc/core/captchaManager.scala @@ -36,11 +36,11 @@ class CaptchaManager(config: Config, captchaProviders: CaptchaProviders) { def generateChallenge(param: Parameters): Option[Int] = { captchaProviders.getProvider(param).flatMap { provider => val providerId = provider.getId() - val challenge = provider.returnChallenge() - val blob = new ByteArrayInputStream(challenge.content) + val challenge = provider.returnChallenge(param.level, param.size) + val blob = new ByteArrayInputStream(challenge.content) val token = insertCaptcha(provider, challenge, providerId, param, blob) - // println("Added new challenge: " + token.toString) - token.map(_.toInt) + // println("Added new challenge: " + token.toString) + token.map(_.toInt) } } @@ -58,7 +58,8 @@ class CaptchaManager(config: Config, captchaProviders: CaptchaProviders) { insertPstmt.setString(4, challenge.contentType) insertPstmt.setString(5, param.level) insertPstmt.setString(6, param.input_type) - insertPstmt.setBlob(7, blob) + insertPstmt.setString(7, param.size) + insertPstmt.setBlob(8, blob) insertPstmt.executeUpdate() val rs: ResultSet = insertPstmt.getGeneratedKeys() if (rs.next()) { @@ -106,6 +107,7 @@ class CaptchaManager(config: Config, captchaProviders: CaptchaProviders) { countPstmt.setString(1, param.level) countPstmt.setString(2, param.media) countPstmt.setString(3, param.input_type) + countPstmt.setString(4, param.size.toString()) val rs = countPstmt.executeQuery() if (rs.next()) { Some(rs.getInt("count")) @@ -123,7 +125,8 @@ class CaptchaManager(config: Config, captchaProviders: CaptchaProviders) { tokenPstmt.setString(1, param.level) tokenPstmt.setString(2, param.media) tokenPstmt.setString(3, param.input_type) - tokenPstmt.setInt(4, count) + tokenPstmt.setString(4, param.size) + tokenPstmt.setInt(5, count) val rs = tokenPstmt.executeQuery() if (rs.next()) { Some(rs.getInt("token")) diff --git a/src/main/scala/lc/core/captchaProviders.scala b/src/main/scala/lc/core/captchaProviders.scala index e2ed404..058d535 100644 --- a/src/main/scala/lc/core/captchaProviders.scala +++ b/src/main/scala/lc/core/captchaProviders.scala @@ -19,7 +19,7 @@ class CaptchaProviders(config: Config) { def generateChallengeSamples(): Map[String, Challenge] = { providers.map { case (key, provider) => - (key, provider.returnChallenge()) + (key, provider.returnChallenge("easy", "350x100")) } } @@ -35,6 +35,7 @@ class CaptchaProviders(config: Config) { if configValue.allowedLevels.contains(param.level) if configValue.allowedMedia.contains(param.media) if configValue.allowedInputType.contains(param.input_type) + if configValue.allowedSizes.contains(param.size) } yield (configValue.name, configValue.config) val providerFilter = for { diff --git a/src/main/scala/lc/core/config.scala b/src/main/scala/lc/core/config.scala index 04241ba..dfbab88 100644 --- a/src/main/scala/lc/core/config.scala +++ b/src/main/scala/lc/core/config.scala @@ -81,6 +81,7 @@ class Config(configFilePath: String) { (ParametersEnum.ALLOWEDLEVELS.toString -> List("medium", "hard")) ~ (ParametersEnum.ALLOWEDMEDIA.toString -> List("image/png")) ~ (ParametersEnum.ALLOWEDINPUTTYPE.toString -> List("text")) ~ + (ParametersEnum.ALLOWEDSIZES.toString -> List("350x100")) ~ (AttributesEnum.CONFIG.toString -> JObject()) ), ( @@ -88,6 +89,7 @@ class Config(configFilePath: String) { (ParametersEnum.ALLOWEDLEVELS.toString -> List("hard")) ~ (ParametersEnum.ALLOWEDMEDIA.toString -> List("image/gif")) ~ (ParametersEnum.ALLOWEDINPUTTYPE.toString -> List("text")) ~ + (ParametersEnum.ALLOWEDSIZES.toString -> List("350x100")) ~ (AttributesEnum.CONFIG.toString -> JObject()) ), ( @@ -95,6 +97,7 @@ class Config(configFilePath: String) { (ParametersEnum.ALLOWEDLEVELS.toString -> List("easy")) ~ (ParametersEnum.ALLOWEDMEDIA.toString -> List("image/png")) ~ (ParametersEnum.ALLOWEDINPUTTYPE.toString -> List("text")) ~ + (ParametersEnum.ALLOWEDSIZES.toString -> List("350x100")) ~ (AttributesEnum.CONFIG.toString -> JObject()) ), ( @@ -102,6 +105,7 @@ class Config(configFilePath: String) { (ParametersEnum.ALLOWEDLEVELS.toString -> List("easy", "medium")) ~ (ParametersEnum.ALLOWEDMEDIA.toString -> List("image/gif")) ~ (ParametersEnum.ALLOWEDINPUTTYPE.toString -> List("text")) ~ + (ParametersEnum.ALLOWEDSIZES.toString -> List("350x100")) ~ (AttributesEnum.CONFIG.toString -> JObject()) ) )) diff --git a/src/main/scala/lc/core/models.scala b/src/main/scala/lc/core/models.scala index d2941d7..f496d57 100644 --- a/src/main/scala/lc/core/models.scala +++ b/src/main/scala/lc/core/models.scala @@ -4,8 +4,9 @@ import org.json4s.jackson.Serialization.write import lc.core.Config.formats trait ByteConvert { def toBytes(): Array[Byte] } +// case class Size(height: Int, width: Int) case class Size(height: Int, width: Int) -case class Parameters(level: String, media: String, input_type: String, size: Option[Size]) +case class Parameters(level: String, media: String, input_type: String, size: String) case class Id(id: String) extends ByteConvert { def toBytes(): Array[Byte] = { write(this).getBytes } } case class Image(image: Array[Byte]) extends ByteConvert { def toBytes(): Array[Byte] = { image } } case class Answer(answer: String, id: String) @@ -16,6 +17,7 @@ case class CaptchaConfig( allowedLevels: List[String], allowedMedia: List[String], allowedInputType: List[String], + allowedSizes: List[String], config: String ) case class ConfigField( diff --git a/src/main/scala/lc/database/DB.scala b/src/main/scala/lc/database/DB.scala index ee6532f..3acc3ce 100644 --- a/src/main/scala/lc/database/DB.scala +++ b/src/main/scala/lc/database/DB.scala @@ -3,7 +3,7 @@ package lc.database import java.sql.{Connection, DriverManager, Statement} class DBConn() { - val con: Connection = DriverManager.getConnection("jdbc:h2:./data/H2/captcha2;MAX_COMPACT_TIME=8000;DB_CLOSE_ON_EXIT=FALSE", "sa", "") + val con: Connection = DriverManager.getConnection("jdbc:h2:./data/H2/captcha3;MAX_COMPACT_TIME=8000;DB_CLOSE_ON_EXIT=FALSE", "sa", "") def getStatement(): Statement = { con.createStatement() diff --git a/src/main/scala/lc/database/statements.scala b/src/main/scala/lc/database/statements.scala index bc89fb4..599e876 100644 --- a/src/main/scala/lc/database/statements.scala +++ b/src/main/scala/lc/database/statements.scala @@ -17,6 +17,7 @@ class Statements(dbConn: DBConn, maxAttempts: Int) { "contentType varchar, " + "contentLevel varchar, " + "contentInput varchar, " + + "size varchar, " + "image blob, " + "attempted int default 0, " + "PRIMARY KEY(token));" + @@ -37,8 +38,8 @@ class Statements(dbConn: DBConn, maxAttempts: Int) { val insertPstmt: PreparedStatement = dbConn.con.prepareStatement( "INSERT INTO " + - "challenge(id, secret, provider, contentType, contentLevel, contentInput, image) " + - "VALUES (?, ?, ?, ?, ?, ?, ?)", + "challenge(id, secret, provider, contentType, contentLevel, contentInput, size, image) " + + "VALUES (?, ?, ?, ?, ?, ?, ?, ?)", Statement.RETURN_GENERATED_KEYS ) @@ -77,7 +78,8 @@ class Statements(dbConn: DBConn, maxAttempts: Int) { WHERE attempted < $maxAttempts AND contentLevel = ? AND contentType = ? AND - contentInput = ? + contentInput = ? AND + size = ? """ ) @@ -88,7 +90,8 @@ class Statements(dbConn: DBConn, maxAttempts: Int) { WHERE attempted < $maxAttempts AND contentLevel = ? AND contentType = ? AND - contentInput = ? + contentInput = ? AND + size = ? LIMIT 1 OFFSET FLOOR(RAND()*?) """ diff --git a/src/main/scala/lc/server/Server.scala b/src/main/scala/lc/server/Server.scala index da8f3cf..12cc416 100644 --- a/src/main/scala/lc/server/Server.scala +++ b/src/main/scala/lc/server/Server.scala @@ -23,7 +23,7 @@ class Server(address: String, port: Int, captchaManager: CaptchaManager, playgro .address(new InetSocketAddress(address, port)) .backlog(32) .POST( - "/v1/captcha", + "/v2/captcha", (request) => { val json = parse(request.getBodyString()) val param = json.extract[Parameters] @@ -32,7 +32,7 @@ class Server(address: String, port: Int, captchaManager: CaptchaManager, playgro } ) .GET( - "/v1/media", + "/v2/media", (request) => { val params = request.getQueryParams() val result = if (params.containsKey("id")) { @@ -46,7 +46,7 @@ class Server(address: String, port: Int, captchaManager: CaptchaManager, playgro } ) .POST( - "/v1/answer", + "/v2/answer", (request) => { val json = parse(request.getBodyString()) val answer = json.extract[Answer] @@ -70,7 +70,7 @@ class Server(address: String, port: Int, captchaManager: CaptchaManager, playgro

Welcome to LibreCaptcha server

Link to Demo

-

API is served at /v1/

+

API is served at /v2/

""" new StringResponse(200, str)