From dfa146e1396fd73319b815009c01d531a3087ff8 Mon Sep 17 00:00:00 2001 From: sanjana Date: Thu, 4 Jan 2018 15:50:59 +0530 Subject: [PATCH 1/6] Separate captcha classes and add LabelCaptcha LabelCaptcha creates a pair of known and unknown images. BlurCaptcha is modified to use a better filter. --- src/main/scala/BlurCaptcha.scala | 31 +++++++++++++ src/main/scala/LabelCaptcha.scala | 73 +++++++++++++++++++++++++++++++ src/main/scala/Main.scala | 47 ++++++++++---------- 3 files changed, 128 insertions(+), 23 deletions(-) create mode 100644 src/main/scala/BlurCaptcha.scala create mode 100644 src/main/scala/LabelCaptcha.scala diff --git a/src/main/scala/BlurCaptcha.scala b/src/main/scala/BlurCaptcha.scala new file mode 100644 index 0000000..aabd9af --- /dev/null +++ b/src/main/scala/BlurCaptcha.scala @@ -0,0 +1,31 @@ +import com.sksamuel.scrimage._ +import com.sksamuel.scrimage.filter.GaussianBlurFilter +import java.io._ + +class BlurCaptcha extends CaptchaProvider { + val tokenAnswer = scala.collection.mutable.Map[String, String]() + val imageFiles = new File("known").listFiles.toList + def getChallenge(): (Challenge) = { + val r = scala.util.Random.nextInt(imageFiles.length) + val chosenImage = imageFiles(r) + var image = Image.fromStream(new FileInputStream(chosenImage)) + val blur = new GaussianBlurFilter(5) + blur.apply(image) + val s = scala.util.Random + val token = s.nextInt(1000).toString + val challenge = new Challenge(token, image) + val answer = "about" + tokenAnswer += token -> answer + challenge + } + def checkAnswer(token: String, input: String): Boolean = { + if(tokenAnswer(token)==input) + { + true + } + else + { + false + } + } +} diff --git a/src/main/scala/LabelCaptcha.scala b/src/main/scala/LabelCaptcha.scala new file mode 100644 index 0000000..40bfaad --- /dev/null +++ b/src/main/scala/LabelCaptcha.scala @@ -0,0 +1,73 @@ +import java.io.File +import javax.imageio.ImageIO +import scala.collection.mutable.Map +import java.nio.file.{Files,Path,StandardCopyOption} +import java.awt.image.BufferedImage +import java.awt.{Graphics2D,Color} + +class LabelCaptcha extends CaptchaProvider { + var knownFiles = new File("known").list.toList + var unknownFiles = new File("unknown").list.toList + val tokenImagePair = Map[String, ImagePair]() + val unknownAnswers = Map[String, Map[String, Int]]() + val total = Map[String, Int]() + for(file <- unknownFiles) { + unknownAnswers += file -> Map[String, Int]() + total += file -> 0 + } + def getChallenge(): Challenge = synchronized { + val r = scala.util.Random.nextInt(knownFiles.length) + val s = scala.util.Random.nextInt(unknownFiles.length) + val knownImageFile = knownFiles(r) + val unknownImageFile = unknownFiles(s) + val ip = new ImagePair(knownImageFile, unknownImageFile) + val token = scala.util.Random.nextInt(10000).toString + tokenImagePair += token -> ip + var knownImage = ImageIO.read(new File("known/"+knownImageFile)) + var unknownImage = ImageIO.read(new File("unknown/"+unknownImageFile)) + val width = knownImage.getWidth()+unknownImage.getWidth() + val height = List(knownImage.getHeight(), unknownImage.getHeight()).max + val imageType = knownImage.getType() + val finalImage = new BufferedImage(width, height, imageType) + val g = finalImage.createGraphics() + g.setColor(Color.WHITE) + g.fillRect(0, 0, finalImage.getWidth(), finalImage.getHeight()) + g.drawImage(knownImage, null, 0, 0) + g.drawImage(unknownImage, null, knownImage.getWidth(), 0) + g.dispose() + val challenge = new Challenge(token, finalImage) + challenge + } + def checkAnswer(token: String, input: String): Boolean = synchronized { + val expectedAnswer = tokenImagePair(token).known.split('.')(0) + val userAnswer = input.split(' ') + if(userAnswer(0)==expectedAnswer) { + val unknownFile = tokenImagePair(token).unknown + if((unknownAnswers(unknownFile)).contains(userAnswer(1))) + { + unknownAnswers(unknownFile)(userAnswer(1)) += 1 + total(unknownFile) += 1 + } + else + { + unknownAnswers(unknownFile)+=(userAnswer(1)) -> 1 + total(unknownFile) += 1 + } + if(total(unknownFile)>=3) { + if((unknownAnswers(unknownFile)(userAnswer(1))/total(unknownFile))>=0.9) + { + unknownAnswers -= unknownFile + Files.move(new File("unknown/"+unknownFile).toPath, new File("known/"+userAnswer(1)+".png").toPath, StandardCopyOption.REPLACE_EXISTING) + knownFiles = new File("known").list.toList + unknownFiles = new File("unknown").list.toList + } + } + true + } + else { + false + } + } +} + +class ImagePair(val known: String, val unknown: String) diff --git a/src/main/scala/Main.scala b/src/main/scala/Main.scala index 6b2080e..cd99b56 100644 --- a/src/main/scala/Main.scala +++ b/src/main/scala/Main.scala @@ -2,45 +2,46 @@ import com.sksamuel.scrimage._ import java.io._ class CaptchaLibrary { - var tokenAnswer = scala.collection.mutable.Map[String, String]() + val captchas = List(new BlurCaptcha, new LabelCaptcha) + var tokenCaptcha = scala.collection.mutable.Map[String, CaptchaProvider]() def init = {} def shutdown = {} def getChallenge(): Challenge = { //choose a captcha provider randomly - val blurCaptcha = new BlurCaptcha - val (challenge, answer) = blurCaptcha.getChallenge() - tokenAnswer += challenge.token->answer + val r = scala.util.Random.nextInt(2) + val captchaInstance = captchas(r) + val challenge = captchaInstance.getChallenge() + tokenCaptcha += challenge.token -> captchaInstance challenge } def checkAnswer(token: String, input: String): Boolean = { - if (tokenAnswer(token) == input) { - true - } - else { - false - } + val result = tokenCaptcha(token).checkAnswer(token, input) + result } } trait CaptchaProvider { - def getChallenge(): (Challenge, String) + def getChallenge(): (Challenge) + def checkAnswer(token: String, input: String): Boolean } class Challenge(val token: String, val image: Image) class Answer(val token: String, val input: String) -class BlurCaptcha extends CaptchaProvider { - def getChallenge(): (Challenge, String) = { - val inFileName = "image2.png" - var image = Image.fromStream(new FileInputStream(inFileName)) - image = image.filter(com.sksamuel.scrimage.filter.BlurFilter) - image.output(new File("blur.png")) - - val r = scala.util.Random - val token = r.nextInt(1000).toString - val challenge = new Challenge(token, image) - val answer = "about" - (challenge, answer) +object LibreCaptcha { + def main(args: Array[String]) { + val captcha = new CaptchaLibrary + var a = 0 + for (a <- 1 to 3) + { + val challenge = captcha.getChallenge() + println(s"Token: ${challenge.token}") + challenge.image.output(new File("Captcha.png")) + println("Enter your answer: ") + val input = scala.io.StdIn.readLine() + val result = captcha.checkAnswer(challenge.token, input) + println(s"Result: $result") + } } } From 40d0fbd0f78d7480d5441312daed10fd359052e1 Mon Sep 17 00:00:00 2001 From: sanjana Date: Sat, 6 Jan 2018 10:10:43 +0530 Subject: [PATCH 2/6] Add FilterCaptcha FilterCaptcha generates a random string, creates an image of the string, and applies a series of filters to make the string hard to read. --- src/main/scala/BlurCaptcha.scala | 2 +- src/main/scala/FilterCaptcha.scala | 68 ++++++++++++++++++++++++++++++ src/main/scala/Main.scala | 6 +-- 3 files changed, 70 insertions(+), 6 deletions(-) create mode 100644 src/main/scala/FilterCaptcha.scala diff --git a/src/main/scala/BlurCaptcha.scala b/src/main/scala/BlurCaptcha.scala index aabd9af..fa4a784 100644 --- a/src/main/scala/BlurCaptcha.scala +++ b/src/main/scala/BlurCaptcha.scala @@ -12,7 +12,7 @@ class BlurCaptcha extends CaptchaProvider { val blur = new GaussianBlurFilter(5) blur.apply(image) val s = scala.util.Random - val token = s.nextInt(1000).toString + val token = s.nextInt(10000).toString val challenge = new Challenge(token, image) val answer = "about" tokenAnswer += token -> answer diff --git a/src/main/scala/FilterCaptcha.scala b/src/main/scala/FilterCaptcha.scala new file mode 100644 index 0000000..e3edf34 --- /dev/null +++ b/src/main/scala/FilterCaptcha.scala @@ -0,0 +1,68 @@ +import com.sksamuel.scrimage._ +import com.sksamuel.scrimage.filter._ +import java.awt.image.BufferedImage +import java.awt.{Graphics2D,Color,Font} + +class FilterCaptcha extends CaptchaProvider { + val tokenAnswer = scala.collection.mutable.Map[String, String]() + def getChallenge(): Challenge = { + val filterTypes = List(new FilterType1, new FilterType2) + val r = new scala.util.Random + val alphabet = "abcdefghijklmnopqrstuvwxyz" + val n = 8 + val answer = Stream.continually(r.nextInt(alphabet.size)).map(alphabet).take(n).mkString + val token = scala.util.Random.nextInt(10000).toString + tokenAnswer += token -> answer + val canvas = new BufferedImage(225, 50, BufferedImage.TYPE_INT_RGB) + val g = canvas.createGraphics() + g.setColor(Color.WHITE) + g.fillRect(0, 0, canvas.getWidth, canvas.getHeight) + g.setColor(Color.BLACK) + g.setFont(new Font("Serif", Font.PLAIN, 30)) + g.drawString(answer, 5, 30) + g.dispose() + var image = new Image(canvas, ImageMetadata.empty) + val s = scala.util.Random.nextInt(2) + image = filterTypes(s).applyFilter(image) + val challenge = new Challenge(token, image) + challenge + } + def checkAnswer(token: String, input: String): Boolean = { + if(tokenAnswer(token)==input) + { + true + } + else + { + false + } + } +} + +trait FilterType { + def applyFilter(image: Image): Image +} + +class FilterType1 extends FilterType { + override def applyFilter(image: Image): Image = { + val blur = new GaussianBlurFilter(2) + val smear = new SmearFilter(com.sksamuel.scrimage.filter.SmearType.Circles, 10, 10, 10, 0, 1) + val diffuse = new DiffuseFilter(2) + blur.apply(image) + diffuse.apply(image) + smear.apply(image) + image + } +} + +class FilterType2 extends FilterType { + override def applyFilter(image: Image): Image = { + val smear = new SmearFilter(com.sksamuel.scrimage.filter.SmearType.Circles, 10, 10, 10, 0, 1) + val diffuse = new DiffuseFilter(1) + val ripple = new RippleFilter(com.sksamuel.scrimage.filter.RippleType.Noise, 1, 1, 0.005.toFloat, 0.005.toFloat) + diffuse.apply(image) + ripple.apply(image) + smear.apply(image) + image + } +} diff --git a/src/main/scala/Main.scala b/src/main/scala/Main.scala index cd99b56..a684bd6 100644 --- a/src/main/scala/Main.scala +++ b/src/main/scala/Main.scala @@ -2,7 +2,7 @@ import com.sksamuel.scrimage._ import java.io._ class CaptchaLibrary { - val captchas = List(new BlurCaptcha, new LabelCaptcha) + val captchas = List(new BlurCaptcha, new LabelCaptcha, new FilterCaptcha) var tokenCaptcha = scala.collection.mutable.Map[String, CaptchaProvider]() def init = {} def shutdown = {} @@ -32,9 +32,6 @@ class Answer(val token: String, val input: String) object LibreCaptcha { def main(args: Array[String]) { val captcha = new CaptchaLibrary - var a = 0 - for (a <- 1 to 3) - { val challenge = captcha.getChallenge() println(s"Token: ${challenge.token}") challenge.image.output(new File("Captcha.png")) @@ -42,6 +39,5 @@ object LibreCaptcha { val input = scala.io.StdIn.readLine() val result = captcha.checkAnswer(challenge.token, input) println(s"Result: $result") - } } } From 3ad3a0e73e83131d7a041422405eafcc58948155 Mon Sep 17 00:00:00 2001 From: sanjana Date: Sun, 7 Jan 2018 09:59:32 +0530 Subject: [PATCH 3/6] Minor changes: coding style and thread safety --- src/main/scala/BlurCaptcha.scala | 9 +-------- src/main/scala/FilterCaptcha.scala | 13 ++++--------- src/main/scala/LabelCaptcha.scala | 9 +++------ 3 files changed, 8 insertions(+), 23 deletions(-) diff --git a/src/main/scala/BlurCaptcha.scala b/src/main/scala/BlurCaptcha.scala index fa4a784..e753250 100644 --- a/src/main/scala/BlurCaptcha.scala +++ b/src/main/scala/BlurCaptcha.scala @@ -19,13 +19,6 @@ class BlurCaptcha extends CaptchaProvider { challenge } def checkAnswer(token: String, input: String): Boolean = { - if(tokenAnswer(token)==input) - { - true - } - else - { - false - } + tokenAnswer(token) == input } } diff --git a/src/main/scala/FilterCaptcha.scala b/src/main/scala/FilterCaptcha.scala index e3edf34..b1889ae 100644 --- a/src/main/scala/FilterCaptcha.scala +++ b/src/main/scala/FilterCaptcha.scala @@ -12,7 +12,9 @@ class FilterCaptcha extends CaptchaProvider { val n = 8 val answer = Stream.continually(r.nextInt(alphabet.size)).map(alphabet).take(n).mkString val token = scala.util.Random.nextInt(10000).toString - tokenAnswer += token -> answer + tokenAnswer += synchronized { + token -> answer + } val canvas = new BufferedImage(225, 50, BufferedImage.TYPE_INT_RGB) val g = canvas.createGraphics() g.setColor(Color.WHITE) @@ -28,14 +30,7 @@ class FilterCaptcha extends CaptchaProvider { challenge } def checkAnswer(token: String, input: String): Boolean = { - if(tokenAnswer(token)==input) - { - true - } - else - { - false - } + tokenAnswer(token) == input } } diff --git a/src/main/scala/LabelCaptcha.scala b/src/main/scala/LabelCaptcha.scala index 40bfaad..6318cc4 100644 --- a/src/main/scala/LabelCaptcha.scala +++ b/src/main/scala/LabelCaptcha.scala @@ -43,19 +43,16 @@ class LabelCaptcha extends CaptchaProvider { val userAnswer = input.split(' ') if(userAnswer(0)==expectedAnswer) { val unknownFile = tokenImagePair(token).unknown - if((unknownAnswers(unknownFile)).contains(userAnswer(1))) - { + if((unknownAnswers(unknownFile)).contains(userAnswer(1))) { unknownAnswers(unknownFile)(userAnswer(1)) += 1 total(unknownFile) += 1 } - else - { + else { unknownAnswers(unknownFile)+=(userAnswer(1)) -> 1 total(unknownFile) += 1 } if(total(unknownFile)>=3) { - if((unknownAnswers(unknownFile)(userAnswer(1))/total(unknownFile))>=0.9) - { + if((unknownAnswers(unknownFile)(userAnswer(1))/total(unknownFile))>=0.9) { unknownAnswers -= unknownFile Files.move(new File("unknown/"+unknownFile).toPath, new File("known/"+userAnswer(1)+".png").toPath, StandardCopyOption.REPLACE_EXISTING) knownFiles = new File("known").list.toList From 93399ea9807cc06ee276603d5f18f35f42533ccc Mon Sep 17 00:00:00 2001 From: sanjana Date: Sun, 7 Jan 2018 10:04:19 +0530 Subject: [PATCH 4/6] Add description for example Captchas in README --- README.md | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 0b864b6..a926078 100644 --- a/README.md +++ b/README.md @@ -1 +1,13 @@ -# lc-core \ No newline at end of file +# lc-core +a Captcha framework + +Currently, the following example Captchas are provided by LibreCaptcha: + +`BlurCaptcha` +An image of a word is blurred before being shown to the user. + +`LabelCaptcha` +An image that has a pair of words is created. The answer to one of the words is known and to that of the other is unknown. The user is tested on the known word, and their answer to the unknown word is recorded. If a sufficient number of users agree on their answer to the unknown word, it is transferred to the list of known words. + +`FilterCaptcha` +An image of a random string of alphabets is created. Then a series of image filters that add effecs such as Smear, Diffuse, and Ripple are applied to the image to make it less readable. From d2dbb36fd1d6bd0c6cfcf0fc0b0da27d9afca661 Mon Sep 17 00:00:00 2001 From: sanjana Date: Sun, 7 Jan 2018 13:34:53 +0530 Subject: [PATCH 5/6] Formatting and correction in thread safety --- src/main/scala/FilterCaptcha.scala | 4 ++-- src/main/scala/LabelCaptcha.scala | 6 ++---- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/src/main/scala/FilterCaptcha.scala b/src/main/scala/FilterCaptcha.scala index b1889ae..3ae2c86 100644 --- a/src/main/scala/FilterCaptcha.scala +++ b/src/main/scala/FilterCaptcha.scala @@ -12,8 +12,8 @@ class FilterCaptcha extends CaptchaProvider { val n = 8 val answer = Stream.continually(r.nextInt(alphabet.size)).map(alphabet).take(n).mkString val token = scala.util.Random.nextInt(10000).toString - tokenAnswer += synchronized { - token -> answer + synchronized { + tokenAnswer += token -> answer } val canvas = new BufferedImage(225, 50, BufferedImage.TYPE_INT_RGB) val g = canvas.createGraphics() diff --git a/src/main/scala/LabelCaptcha.scala b/src/main/scala/LabelCaptcha.scala index 6318cc4..bcfab82 100644 --- a/src/main/scala/LabelCaptcha.scala +++ b/src/main/scala/LabelCaptcha.scala @@ -46,8 +46,7 @@ class LabelCaptcha extends CaptchaProvider { if((unknownAnswers(unknownFile)).contains(userAnswer(1))) { unknownAnswers(unknownFile)(userAnswer(1)) += 1 total(unknownFile) += 1 - } - else { + } else { unknownAnswers(unknownFile)+=(userAnswer(1)) -> 1 total(unknownFile) += 1 } @@ -60,8 +59,7 @@ class LabelCaptcha extends CaptchaProvider { } } true - } - else { + } else { false } } From 03c1e825f5dfd5fb7979e7728fcb12fb7ccc147f Mon Sep 17 00:00:00 2001 From: sanjana Date: Sun, 7 Jan 2018 13:38:27 +0530 Subject: [PATCH 6/6] .gitignore file --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 9c07d4a..5b226d9 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ *.class *.log +/bin/