Linter and Formatter support (#58)

* Add scala linter and formatter

Signed-off-by: Rahul Rudragoudar <rr83019@gmail.com>

* Add java formatter

Signed-off-by: Rahul Rudragoudar <rr83019@gmail.com>

* Add linter support

Signed-off-by: Rahul Rudragoudar <rr83019@gmail.com>

* Increase maxColumn limit

Signed-off-by: Rahul Rudragoudar <rr83019@gmail.com>

* Reformat and lint

Signed-off-by: Rahul Rudragoudar <rr83019@gmail.com>

* Minor reformatting

Signed-off-by: Rahul Rudragoudar <rr83019@gmail.com>

* Add scala formatter on compile option

Signed-off-by: Rahul Rudragoudar <rr83019@gmail.com>

* Enable scala linter for CI

Signed-off-by: Rahul Rudragoudar <rr83019@gmail.com>
This commit is contained in:
Rahul Rudragoudar 2021-02-25 23:49:39 +05:30 committed by GitHub
parent 6d04cdc3b4
commit de50d8123e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
24 changed files with 3445 additions and 3410 deletions

View File

@ -19,3 +19,5 @@ jobs:
java-version: 1.8
- name: Run tests
run: sbt test
- name: Run linter
run: sbt "scalafixAll --check"

8
.scalafix.conf Normal file
View File

@ -0,0 +1,8 @@
rules=[
ExplicitResultTypes,
RemoveUnused,
DisableSyntax,
LeakingImplicitClassVal,
NoValInForComprehension,
ProcedureSyntax
]

2
.scalafmt.conf Normal file
View File

@ -0,0 +1,2 @@
version=2.5.2
maxColumn = 120

View File

@ -4,3 +4,4 @@ scala:
- 2.13.2
script:
- sbt ++$TRAVIS_SCALA_VERSION compile
- sbt "scalafixAll --check"

View File

@ -1,21 +1,27 @@
lazy val root = (project in file(".")).
settings(
inThisBuild(List(
lazy val root = (project in file(".")).settings(
inThisBuild(
List(
organization := "com.example",
scalaVersion := "2.13.3",
version := "0.1.0-SNAPSHOT")),
version := "0.1.0-SNAPSHOT",
semanticdbEnabled := true,
semanticdbVersion := scalafixSemanticdb.revision,
scalafixScalaBinaryVersion := "2.13"
)
),
name := "LibreCaptcha",
libraryDependencies += "com.sksamuel.scrimage" % "scrimage-core" % "4.0.5",
libraryDependencies += "com.sksamuel.scrimage" % "scrimage-filters" % "4.0.5",
libraryDependencies += "org.json4s" % "json4s-jackson_2.13" % "3.6.9"
)
unmanagedResourceDirectories in Compile += {baseDirectory.value / "lib"}
unmanagedResourceDirectories in Compile += { baseDirectory.value / "lib" }
scalacOptions ++= List(
"-Yrangepos",
"-Ywarn-unused"
)
javacOptions += "-g:none"
scalafmtOnCompile := true
compileOrder := CompileOrder.JavaThenScala
fork in run := true

3
project/plugins.sbt Normal file
View File

@ -0,0 +1,3 @@
addSbtPlugin("ch.epfl.scala" % "sbt-scalafix" % "0.9.25")
addSbtPlugin("org.scalameta" % "sbt-scalafmt" % "2.4.0")
addSbtPlugin("com.lightbend.sbt" % "sbt-java-formatter" % "0.6.0")

View File

@ -10,57 +10,59 @@ import lc.captchas.interfaces.Challenge;
import lc.captchas.interfaces.ChallengeProvider;
import lc.misc.HelperFunctions;
public class FontFunCaptcha implements ChallengeProvider{
public class FontFunCaptcha implements ChallengeProvider {
public String getId() {
return "FontFunCaptcha";
}
private String getFontName(String path, String level){
File file = new File(path+level+"/");
FilenameFilter txtFileFilter = new FilenameFilter() {
private String getFontName(String path, String level) {
File file = new File(path + level + "/");
FilenameFilter txtFileFilter =
new FilenameFilter() {
@Override
public boolean accept(File dir, String name)
{
if(name.endsWith(".ttf"))
return true;
else
return false;
public boolean accept(File dir, String name) {
if (name.endsWith(".ttf")) return true;
else return false;
}
};
File[] files = file.listFiles(txtFileFilter);
return path+level.toLowerCase()+"/"+files[HelperFunctions.randomNumber(0,files.length-1)].getName();
return path
+ level.toLowerCase()
+ "/"
+ files[HelperFunctions.randomNumber(0, files.length - 1)].getName();
}
private Font loadCustomFont(String level, String path) {
String fontName = getFontName(path,level);
try{
String fontName = getFontName(path, level);
try {
Font font = Font.createFont(Font.TRUETYPE_FONT, new File(fontName));
font = font.deriveFont(Font.PLAIN, 48f);
return font;
} catch (Exception e){
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
private byte[] fontFun(String captchaText, String level, String path){
String[] colors = {"#f68787","#f8a978","#f1eb9a","#a4f6a5"};
private byte[] fontFun(String captchaText, String level, String path) {
String[] colors = {"#f68787", "#f8a978", "#f1eb9a", "#a4f6a5"};
BufferedImage img = new BufferedImage(350, 100, BufferedImage.TYPE_INT_RGB);
Graphics2D graphics2D = img.createGraphics();
for(int i=0; i< captchaText.length(); i++) {
Font font = loadCustomFont(level,path);
for (int i = 0; i < captchaText.length(); i++) {
Font font = loadCustomFont(level, path);
graphics2D.setFont(font);
FontMetrics fontMetrics = graphics2D.getFontMetrics();
HelperFunctions.setRenderingHints(graphics2D);
graphics2D.setColor(Color.decode(colors[HelperFunctions.randomNumber(0,3)]));
graphics2D.drawString(String.valueOf(captchaText.charAt(i)), (i * 48), fontMetrics.getAscent());
graphics2D.setColor(Color.decode(colors[HelperFunctions.randomNumber(0, 3)]));
graphics2D.drawString(
String.valueOf(captchaText.charAt(i)), (i * 48), fontMetrics.getAscent());
}
graphics2D.dispose();
ByteArrayOutputStream baos = new ByteArrayOutputStream();
try {
ImageIO.write(img,"png",baos);
}catch (Exception e){
ImageIO.write(img, "png", baos);
} catch (Exception e) {
e.printStackTrace();
}
return baos.toByteArray();
@ -69,10 +71,10 @@ public class FontFunCaptcha implements ChallengeProvider{
public Challenge returnChallenge() {
String secret = HelperFunctions.randomString(7);
String path = "./lib/fonts/";
return new Challenge(fontFun(secret,"medium",path),"image/png",secret.toLowerCase());
return new Challenge(fontFun(secret, "medium", path), "image/png", secret.toLowerCase());
}
public boolean checkAnswer(String secret, String answer){
public boolean checkAnswer(String secret, String answer) {
return answer.toLowerCase().equals(secret);
}
}

View File

@ -14,34 +14,35 @@ import lc.captchas.interfaces.ChallengeProvider;
import lc.misc.HelperFunctions;
import lc.misc.GifSequenceWriter;
public class GifCaptcha implements ChallengeProvider{
public class GifCaptcha implements ChallengeProvider {
private BufferedImage charToImg(String text){
private BufferedImage charToImg(String text) {
BufferedImage img = new BufferedImage(250, 100, BufferedImage.TYPE_INT_RGB);
Font font = new Font("Bradley Hand", Font.ROMAN_BASELINE, 48);
Graphics2D graphics2D = img.createGraphics();
graphics2D.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
graphics2D.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
graphics2D.setRenderingHint(
RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
graphics2D.setFont(font);
graphics2D.setColor(new Color((int)(Math.random() * 0x1000000)));
graphics2D.drawString( text , 45, 45);
graphics2D.setColor(new Color((int) (Math.random() * 0x1000000)));
graphics2D.drawString(text, 45, 45);
graphics2D.dispose();
return img;
}
private byte[] gifCaptcha(String text){
private byte[] gifCaptcha(String text) {
try {
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
ImageOutputStream output = new MemoryCacheImageOutputStream(byteArrayOutputStream);
GifSequenceWriter writer = new GifSequenceWriter( output, 1,1000, true );
for(int i=0; i< text.length(); i++){
GifSequenceWriter writer = new GifSequenceWriter(output, 1, 1000, true);
for (int i = 0; i < text.length(); i++) {
BufferedImage nextImage = charToImg(String.valueOf(text.charAt(i)));
writer.writeToSequence(nextImage);
}
writer.close();
output.close();
return byteArrayOutputStream.toByteArray();
} catch (IOException e){
} catch (IOException e) {
e.printStackTrace();
}
return null;
@ -49,7 +50,7 @@ public class GifCaptcha implements ChallengeProvider{
public Challenge returnChallenge() {
String secret = HelperFunctions.randomString(6);
return new Challenge(gifCaptcha(secret),"image/gif",secret.toLowerCase());
return new Challenge(gifCaptcha(secret), "image/gif", secret.toLowerCase());
}
public boolean checkAnswer(String secret, String answer) {

View File

@ -14,7 +14,7 @@ import lc.misc.HelperFunctions;
import lc.captchas.interfaces.Challenge;
import lc.captchas.interfaces.ChallengeProvider;
public class ShadowTextCaptcha implements ChallengeProvider{
public class ShadowTextCaptcha implements ChallengeProvider {
public String getId() {
return "ShadowTextCaptcha";
@ -24,12 +24,13 @@ public class ShadowTextCaptcha implements ChallengeProvider{
return answer.toLowerCase().equals(secret);
}
private byte[] shadowText(String text){
private byte[] shadowText(String text) {
BufferedImage img = new BufferedImage(350, 100, BufferedImage.TYPE_INT_RGB);
Font font = new Font("Arial",Font.ROMAN_BASELINE ,48);
Font font = new Font("Arial", Font.ROMAN_BASELINE, 48);
Graphics2D graphics2D = img.createGraphics();
graphics2D.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
graphics2D.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
graphics2D.setRenderingHint(
RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
TextLayout textLayout = new TextLayout(text, font, graphics2D.getFontRenderContext());
HelperFunctions.setRenderingHints(graphics2D);
@ -43,8 +44,7 @@ public class ShadowTextCaptcha implements ChallengeProvider{
1f / 9f, 1f / 9f, 1f / 9f,
1f / 9f, 1f / 9f, 1f / 9f
};
ConvolveOp op = new ConvolveOp(new Kernel(3, 3, kernel),
ConvolveOp.EDGE_NO_OP, null);
ConvolveOp op = new ConvolveOp(new Kernel(3, 3, kernel), ConvolveOp.EDGE_NO_OP, null);
BufferedImage img2 = op.filter(img, null);
Graphics2D g2d = img2.createGraphics();
HelperFunctions.setRenderingHints(g2d);
@ -53,8 +53,8 @@ public class ShadowTextCaptcha implements ChallengeProvider{
g2d.dispose();
ByteArrayOutputStream baos = new ByteArrayOutputStream();
try {
ImageIO.write(img2,"png",baos);
}catch(Exception e){
ImageIO.write(img2, "png", baos);
} catch (Exception e) {
e.printStackTrace();
}
return baos.toByteArray();
@ -62,6 +62,6 @@ public class ShadowTextCaptcha implements ChallengeProvider{
public Challenge returnChallenge() {
String secret = HelperFunctions.randomString(6);
return new Challenge(shadowText(secret),"image/png",secret.toLowerCase());
return new Challenge(shadowText(secret), "image/png", secret.toLowerCase());
}
}

View File

@ -2,8 +2,10 @@ package lc.captchas.interfaces;
public interface ChallengeProvider {
public String getId();
public Challenge returnChallenge();
public boolean checkAnswer(String secret, String answer);
//TODO: def configure(): Unit
// TODO: def configure(): Unit
}

View File

@ -2,6 +2,7 @@
// It was available under CC By 3.0
package lc.misc;
import javax.imageio.*;
import javax.imageio.metadata.*;
import javax.imageio.stream.*;
@ -22,51 +23,39 @@ public class GifSequenceWriter {
* @param timeBetweenFramesMS the time between frames in miliseconds
* @param loopContinuously wether the gif should loop repeatedly
* @throws IIOException if no gif ImageWriters are found
*
* @author Elliot Kroo (elliot[at]kroo[dot]net)
*/
public GifSequenceWriter(
ImageOutputStream outputStream,
int imageType,
int timeBetweenFramesMS,
boolean loopContinuously) throws IIOException, IOException {
boolean loopContinuously)
throws IIOException, IOException {
// my method to create a writer
gifWriter = getWriter();
imageWriteParam = gifWriter.getDefaultWriteParam();
ImageTypeSpecifier imageTypeSpecifier =
ImageTypeSpecifier.createFromBufferedImageType(imageType);
imageMetaData =
gifWriter.getDefaultImageMetadata(imageTypeSpecifier,
imageWriteParam);
imageMetaData = gifWriter.getDefaultImageMetadata(imageTypeSpecifier, imageWriteParam);
String metaFormatName = imageMetaData.getNativeMetadataFormatName();
IIOMetadataNode root = (IIOMetadataNode)
imageMetaData.getAsTree(metaFormatName);
IIOMetadataNode root = (IIOMetadataNode) imageMetaData.getAsTree(metaFormatName);
IIOMetadataNode graphicsControlExtensionNode = getNode(
root,
"GraphicControlExtension");
IIOMetadataNode graphicsControlExtensionNode = getNode(root, "GraphicControlExtension");
graphicsControlExtensionNode.setAttribute("disposalMethod", "none");
graphicsControlExtensionNode.setAttribute("userInputFlag", "FALSE");
graphicsControlExtensionNode.setAttribute("transparentColorFlag", "FALSE");
graphicsControlExtensionNode.setAttribute(
"transparentColorFlag",
"FALSE");
graphicsControlExtensionNode.setAttribute(
"delayTime",
Integer.toString(timeBetweenFramesMS / 10));
graphicsControlExtensionNode.setAttribute(
"transparentColorIndex",
"0");
"delayTime", Integer.toString(timeBetweenFramesMS / 10));
graphicsControlExtensionNode.setAttribute("transparentColorIndex", "0");
IIOMetadataNode commentsNode = getNode(root, "CommentExtensions");
commentsNode.setAttribute("CommentExtension", "Created by MAH");
IIOMetadataNode appEntensionsNode = getNode(
root,
"ApplicationExtensions");
IIOMetadataNode appEntensionsNode = getNode(root, "ApplicationExtensions");
IIOMetadataNode child = new IIOMetadataNode("ApplicationExtension");
@ -75,8 +64,7 @@ public class GifSequenceWriter {
int loop = loopContinuously ? 0 : 1;
child.setUserObject(new byte[]{ 0x1, (byte) (loop & 0xFF), (byte)
((loop >> 8) & 0xFF)});
child.setUserObject(new byte[] {0x1, (byte) (loop & 0xFF), (byte) ((loop >> 8) & 0xFF)});
appEntensionsNode.appendChild(child);
imageMetaData.setFromTree(metaFormatName, root);
@ -87,32 +75,26 @@ public class GifSequenceWriter {
}
public void writeToSequence(RenderedImage img) throws IOException {
gifWriter.writeToSequence(
new IIOImage(
img,
null,
imageMetaData),
imageWriteParam);
gifWriter.writeToSequence(new IIOImage(img, null, imageMetaData), imageWriteParam);
}
/**
* Close this GifSequenceWriter object. This does not close the underlying
* stream, just finishes off the GIF.
* Close this GifSequenceWriter object. This does not close the underlying stream, just finishes
* off the GIF.
*/
public void close() throws IOException {
gifWriter.endWriteSequence();
}
/**
* Returns the first available GIF ImageWriter using
* ImageIO.getImageWritersBySuffix("gif").
* Returns the first available GIF ImageWriter using ImageIO.getImageWritersBySuffix("gif").
*
* @return a GIF ImageWriter object
* @throws IIOException if no GIF image writers are returned
*/
private static ImageWriter getWriter() throws IIOException {
Iterator<ImageWriter> iter = ImageIO.getImageWritersBySuffix("gif");
if(!iter.hasNext()) {
if (!iter.hasNext()) {
throw new IIOException("No GIF Image Writers Exist");
} else {
return iter.next();
@ -120,26 +102,22 @@ public class GifSequenceWriter {
}
/**
* Returns an existing child node, or creates and returns a new child node (if
* the requested node does not exist).
* Returns an existing child node, or creates and returns a new child node (if the requested node
* does not exist).
*
* @param rootNode the <tt>IIOMetadataNode</tt> to search for the child node.
* @param nodeName the name of the child node.
*
* @return the child node, if found or a new node created with the given name.
*/
private static IIOMetadataNode getNode(
IIOMetadataNode rootNode,
String nodeName) {
private static IIOMetadataNode getNode(IIOMetadataNode rootNode, String nodeName) {
int nNodes = rootNode.getLength();
for (int i = 0; i < nNodes; i++) {
if (rootNode.item(i).getNodeName().compareToIgnoreCase(nodeName)
== 0) {
return((IIOMetadataNode) rootNode.item(i));
if (rootNode.item(i).getNodeName().compareToIgnoreCase(nodeName) == 0) {
return ((IIOMetadataNode) rootNode.item(i));
}
}
IIOMetadataNode node = new IIOMetadataNode(nodeName);
rootNode.appendChild(node);
return(node);
return (node);
}
}

View File

@ -4,24 +4,24 @@ import java.awt.*;
public class HelperFunctions {
public static void setRenderingHints(Graphics2D g2d){
g2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING,
RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
g2d.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS,
RenderingHints.VALUE_FRACTIONALMETRICS_ON);
public static void setRenderingHints(Graphics2D g2d) {
g2d.setRenderingHint(
RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
g2d.setRenderingHint(
RenderingHints.KEY_FRACTIONALMETRICS, RenderingHints.VALUE_FRACTIONALMETRICS_ON);
}
public static String randomString(int n){
public static String randomString(int n) {
String characters = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz23456789$#%@&?";
StringBuilder stringBuilder = new StringBuilder();
for(int i=0; i<n; i++){
int index = (int)(characters.length() * Math.random());
for (int i = 0; i < n; i++) {
int index = (int) (characters.length() * Math.random());
stringBuilder.append(characters.charAt(index));
}
return stringBuilder.toString();
}
public static int randomNumber(int min,int max){
return (int)(Math.random() * ((max - min) +1)) + min;
public static int randomNumber(int min, int max) {
return (int) (Math.random() * ((max - min) + 1)) + min;
}
}

File diff suppressed because it is too large Load Diff

View File

@ -1,16 +1,11 @@
package lc
import org.json4s.DefaultFormats
import org.json4s.jackson.JsonMethods._
import scala.io.Source.fromFile
import lc.database.Statements
import lc.core.{Captcha, CaptchaProviders}
import lc.server.Server
import lc.background.BackgroundTask
object LCFramework{
def main(args: scala.Array[String]) {
object LCFramework {
def main(args: scala.Array[String]): Unit = {
val captcha = new Captcha()
val server = new Server(8888, captcha)
val backgroudTask = new BackgroundTask(captcha, 10)
@ -20,13 +15,14 @@ object LCFramework{
}
object MakeSamples {
def main(args: scala.Array[String]) {
def main(args: scala.Array[String]): Unit = {
val samples = CaptchaProviders.generateChallengeSamples()
samples.foreach {case (key, sample) =>
samples.foreach {
case (key, sample) =>
val extensionMap = Map("image/png" -> "png", "image/gif" -> "gif")
println(key + ": " + sample)
val outStream = new java.io.FileOutputStream("samples/"+key+"."+extensionMap(sample.contentType))
val outStream = new java.io.FileOutputStream("samples/" + key + "." + extensionMap(sample.contentType))
outStream.write(sample.content)
outStream.close
}

View File

@ -5,7 +5,6 @@ import java.util.concurrent.{ScheduledThreadPoolExecutor, TimeUnit}
import lc.core.Captcha
import lc.core.{Parameters, Size}
class BackgroundTask(captcha: Captcha, throttle: Int) {
private val task = new Runnable {
@ -19,20 +18,20 @@ class BackgroundTask(captcha: Captcha, throttle: Int) {
challengeGCPstmt.executeUpdate()
val imageNum = Statements.tlStmts.get.getCountChallengeTable.executeQuery()
var throttleIn = (throttle*1.1).toInt
if(imageNum.next())
throttleIn = (throttleIn-imageNum.getInt("total"))
while(0 < throttleIn){
captcha.generateChallenge(Parameters("","","",Option(Size(0,0))))
var throttleIn = (throttle * 1.1).toInt
if (imageNum.next())
throttleIn = (throttleIn - imageNum.getInt("total"))
while (0 < throttleIn) {
captcha.generateChallenge(Parameters("", "", "", Option(Size(0, 0))))
throttleIn -= 1
}
} catch { case e: Exception => println(e) }
}
}
def beginThread(delay: Int) : Unit = {
def beginThread(delay: Int): Unit = {
val ex = new ScheduledThreadPoolExecutor(1)
val thread = ex.scheduleWithFixedDelay(task, 1, delay, TimeUnit.SECONDS)
ex.scheduleWithFixedDelay(task, 1, delay, TimeUnit.SECONDS)
}
}

View File

@ -8,7 +8,6 @@ import java.awt.Color
import lc.captchas.interfaces.ChallengeProvider
import lc.captchas.interfaces.Challenge
class FilterChallenge extends ChallengeProvider {
def getId = "FilterChallenge"
def returnChallenge(): Challenge = {
@ -62,4 +61,3 @@ class FilterType2 extends FilterType {
image
}
}

View File

@ -4,45 +4,45 @@ import java.io.File
import java.io.ByteArrayOutputStream
import javax.imageio.ImageIO
import scala.collection.mutable.Map
import java.nio.file.{Files,Path,StandardCopyOption}
import java.nio.file.{Files, StandardCopyOption}
import java.awt.image.BufferedImage
import java.awt.{Graphics2D,Color}
import java.awt.Color
import lc.captchas.interfaces.ChallengeProvider
import lc.captchas.interfaces.Challenge
class LabelCaptcha extends ChallengeProvider {
private var knownFiles = new File("known").list.toList
private var unknownFiles = new File("unknown").list.toList
private var unknownAnswers = Map[String, Map[String, Int]]()
private var total = Map[String, Int]()
private val unknownAnswers = Map[String, Map[String, Int]]()
private val total = Map[String, Int]()
for(file <- unknownFiles) {
for (file <- unknownFiles) {
unknownAnswers += file -> Map[String, Int]()
total += file -> 0
}
def getId = "LabelCaptcha"
def returnChallenge(): Challenge = synchronized {
def returnChallenge(): 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)
var knownImage = ImageIO.read(new File("known/"+knownImageFile))
var unknownImage = ImageIO.read(new File("unknown/"+unknownImageFile))
val knownImage = ImageIO.read(new File("known/" + knownImageFile))
val unknownImage = ImageIO.read(new File("unknown/" + unknownImageFile))
val mergedImage = merge(knownImage, unknownImage)
val token = encrypt(knownImageFile + "," + unknownImageFile)
val baos = new ByteArrayOutputStream()
ImageIO.write(mergedImage,"png",baos)
ImageIO.write(mergedImage, "png", baos)
new Challenge(baos.toByteArray(), "image/png", token)
}
private def merge(knownImage: BufferedImage, unknownImage: BufferedImage) = {
val width = knownImage.getWidth()+unknownImage.getWidth()
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)
@ -55,25 +55,30 @@ class LabelCaptcha extends ChallengeProvider {
finalImage
}
def checkAnswer(token: String, input: String): Boolean = synchronized {
def checkAnswer(token: String, input: String): Boolean =
synchronized {
val parts = decrypt(token).split(",")
val knownImage = parts(0)
val unknownImage = parts(1)
val expectedAnswer = knownImage.split('.')(0)
val userAnswer = input.split(' ')
if(userAnswer(0)==expectedAnswer) {
if (userAnswer(0) == expectedAnswer) {
val unknownFile = unknownImage
if((unknownAnswers(unknownFile)).contains(userAnswer(1))) {
if ((unknownAnswers(unknownFile)).contains(userAnswer(1))) {
unknownAnswers(unknownFile)(userAnswer(1)) += 1
total(unknownFile) += 1
} else {
unknownAnswers(unknownFile)+=(userAnswer(1)) -> 1
unknownAnswers(unknownFile) += (userAnswer(1)) -> 1
total(unknownFile) += 1
}
if(total(unknownFile)>=3) {
if((unknownAnswers(unknownFile)(userAnswer(1))/total(unknownFile))>=0.9) {
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)
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
}

View File

@ -5,9 +5,7 @@ import java.awt.RenderingHints
import java.awt.Font
import java.awt.font.TextAttribute
import java.awt.Color
import java.io.ByteArrayOutputStream
import javax.imageio.ImageIO
import javax.imageio.stream.ImageOutputStream;
import java.io.ByteArrayOutputStream;
import javax.imageio.stream.MemoryCacheImageOutputStream;
import lc.captchas.interfaces.ChallengeProvider
import lc.captchas.interfaces.Challenge
@ -19,8 +17,8 @@ class Drop {
var yOffset = 0
var color = 0
var colorChange = 10
def mkColor = {
new Color(color, color, math.min(200, color+100))
def mkColor: Color = {
new Color(color, color, math.min(200, color + 100))
}
}
@ -36,8 +34,8 @@ class RainDropsCP extends ChallengeProvider {
private def extendDrops(drops: Array[Drop], steps: Int, xOffset: Int) = {
drops.map(d => {
val nd = new Drop()
nd.x + xOffset*steps
nd.y + d.yOffset*steps
nd.x + xOffset * steps
nd.y + d.yOffset * steps
nd
})
}
@ -48,20 +46,23 @@ class RainDropsCP extends ChallengeProvider {
val width = 450
val height = 100
val imgType = BufferedImage.TYPE_INT_RGB
val xOffset = 2+r.nextInt(3)
val xOffset = 2 + r.nextInt(3)
val xBias = (height / 10) - 2
val dropsOrig = Array.fill[Drop](2000)( new Drop())
val dropsOrig = Array.fill[Drop](2000)(new Drop())
for (d <- dropsOrig) {
d.x = r.nextInt(width) - (xBias/2)*xOffset
d.yOffset = 6+r.nextInt(6)
d.x = r.nextInt(width) - (xBias / 2) * xOffset
d.yOffset = 6 + r.nextInt(6)
d.y = r.nextInt(height)
d.color = r.nextInt(240)
if (d.color > 128) {
d.colorChange *= -1
}
}
val drops = dropsOrig ++ extendDrops(dropsOrig, 1, xOffset) ++ extendDrops(dropsOrig, 2, xOffset) ++ extendDrops(dropsOrig, 3, xOffset)
val drops = dropsOrig ++ extendDrops(dropsOrig, 1, xOffset) ++ extendDrops(dropsOrig, 2, xOffset) ++ extendDrops(
dropsOrig,
3,
xOffset
)
val baseFont = new Font(Font.MONOSPACED, Font.BOLD, 80)
val attributes = new java.util.HashMap[TextAttribute, Object]()
@ -72,7 +73,7 @@ class RainDropsCP extends ChallengeProvider {
val baos = new ByteArrayOutputStream();
val ios = new MemoryCacheImageOutputStream(baos);
val writer = new GifSequenceWriter(ios, imgType, 60, true);
for(i <- 0 until 60){
for (_ <- 0 until 60) {
// val yOffset = 5+r.nextInt(5)
val canvas = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB)
val g = canvas.createGraphics()
@ -85,14 +86,14 @@ class RainDropsCP extends ChallengeProvider {
// paint the rain
for (d <- drops) {
g.setColor(d.mkColor)
g.drawLine(d.x, d.y, d.x+xOffset, d.y+d.yOffset)
d.x += xOffset/2
d.y += d.yOffset/2
g.drawLine(d.x, d.y, d.x + xOffset, d.y + d.yOffset)
d.x += xOffset / 2
d.y += d.yOffset / 2
d.color += d.colorChange
if (d.x > width || d.y > height) {
val ySteps = (height / d.yOffset) + 1
d.x -= xOffset*ySteps
d.y -= d.yOffset*ySteps
d.x -= xOffset * ySteps
d.y -= d.yOffset * ySteps
}
if (d.color > 200 || d.color < 21) {
@ -103,7 +104,7 @@ class RainDropsCP extends ChallengeProvider {
// center the text
g.setFont(spacedFont)
val textWidth = g.getFontMetrics().charsWidth(secret.toCharArray, 0, secret.toCharArray.length)
val textX = (width - textWidth)/2
val textX = (width - textWidth) / 2
// paint the top outline
g.setColor(textHighlightColor)

View File

@ -1,6 +1,5 @@
package lc.core
import org.json4s.JsonAST.JValue
import java.sql.{Blob, ResultSet}
import java.util.UUID
import java.io.ByteArrayInputStream
@ -10,20 +9,21 @@ import lc.core.CaptchaProviders
class Captcha {
def getCaptcha(id: Id): Array[Byte] = {
var image :Array[Byte] = null
var image: Array[Byte] = null
var blob: Blob = null
try {
val imagePstmt = Statements.tlStmts.get.imagePstmt
imagePstmt.setString(1, id.id)
val rs: ResultSet = imagePstmt.executeQuery()
if(rs.next()){
if (rs.next()) {
blob = rs.getBlob("image")
if(blob != null){
if (blob != null) {
image = blob.getBytes(1, blob.length().toInt)
}
}
image
} catch { case e: Exception =>
} catch {
case e: Exception =>
println(e)
image
}
@ -43,10 +43,10 @@ class Captcha {
insertPstmt.setBlob(5, blob)
insertPstmt.executeUpdate()
val rs: ResultSet = insertPstmt.getGeneratedKeys()
val token = if(rs.next()){
val token = if (rs.next()) {
rs.getInt("token")
}
println("Added new challenge: "+ token.toString)
println("Added new challenge: " + token.toString)
token.asInstanceOf[Int]
}
@ -54,7 +54,7 @@ class Captcha {
try {
val tokenPstmt = Statements.tlStmts.get.tokenPstmt
val rs = tokenPstmt.executeQuery()
val tokenOpt = if(rs.next()) {
val tokenOpt = if (rs.next()) {
Some(rs.getInt("token"))
} else {
None
@ -64,7 +64,8 @@ class Captcha {
updateAttemptedPstmt.setString(1, uuid)
updateAttemptedPstmt.executeUpdate()
Id(uuid)
} catch {case e: Exception =>
} catch {
case e: Exception =>
println(e)
Id(getUUID(-1))
}
@ -73,8 +74,8 @@ class Captcha {
private def getUUID(id: Int): String = {
val uuid = UUID.randomUUID().toString
val mapPstmt = Statements.tlStmts.get.mapPstmt
mapPstmt.setString(1,uuid)
mapPstmt.setInt(2,id)
mapPstmt.setString(1, uuid)
mapPstmt.setInt(2, id)
mapPstmt.executeUpdate()
uuid
}
@ -87,7 +88,7 @@ class Captcha {
val secret = rs.getString("secret")
val provider = rs.getString("provider")
val check = CaptchaProviders.getProviderById(provider).checkAnswer(secret, answer.answer)
val result = if(check) "TRUE" else "FALSE"
val result = if (check) "TRUE" else "FALSE"
result
} else {
"EXPIRED"
@ -101,7 +102,7 @@ class Captcha {
def display(): Unit = {
val rs: ResultSet = Statements.tlStmts.get.getChallengeTable.executeQuery()
println("token\t\tid\t\tsecret\t\tattempted")
while(rs.next()) {
while (rs.next()) {
val token = rs.getInt("token")
val id = rs.getString("id")
val secret = rs.getString("secret")
@ -111,7 +112,7 @@ class Captcha {
val rss: ResultSet = Statements.tlStmts.get.getMapIdTable.executeQuery()
println("uuid\t\ttoken\t\tlastServed")
while(rss.next()){
while (rss.next()) {
val uuid = rss.getString("uuid")
val token = rss.getInt("token")
val lastServed = rss.getTimestamp("lastServed")

View File

@ -2,6 +2,7 @@ package lc.core
import lc.captchas._
import lc.captchas.interfaces.ChallengeProvider
import lc.captchas.interfaces.Challenge
object CaptchaProviders {
private val providers = Map(
@ -9,20 +10,22 @@ object CaptchaProviders {
//"FontFunCaptcha" -> new FontFunCaptcha,
"GifCaptcha" -> new GifCaptcha,
"ShadowTextCaptcha" -> new ShadowTextCaptcha,
"RainDropsCaptcha" -> new RainDropsCP,
"RainDropsCaptcha" -> new RainDropsCP
//"LabelCaptcha" -> new LabelCaptcha
)
def generateChallengeSamples() = {
providers.map {case (key, provider) =>
def generateChallengeSamples(): Map[String, Challenge] = {
providers.map {
case (key, provider) =>
(key, provider.returnChallenge())
}
}
private val seed = System.currentTimeMillis.toString.substring(2,6).toInt
private val seed = System.currentTimeMillis.toString.substring(2, 6).toInt
private val random = new scala.util.Random(seed)
private def getNextRandomInt(max: Int) = random.synchronized {
private def getNextRandomInt(max: Int) =
random.synchronized {
random.nextInt(max)
}

View File

@ -2,7 +2,7 @@ package lc.database
import java.sql._
class DBConn(){
class DBConn() {
val con: Connection = DriverManager.getConnection("jdbc:h2:./data/H2/captcha", "sa", "")
def getStatement(): Statement = {

View File

@ -2,30 +2,108 @@ package lc.database
import lc.database.DBConn
import java.sql.Statement
import java.sql.PreparedStatement
class Statements(dbConn: DBConn) {
private val stmt = dbConn.getStatement()
stmt.execute("CREATE TABLE IF NOT EXISTS challenge(token int auto_increment, id varchar, secret varchar, provider varchar, contentType varchar, image blob, attempted int default 0, PRIMARY KEY(token))")
stmt.execute("CREATE TABLE IF NOT EXISTS mapId(uuid varchar, token int, lastServed timestamp, PRIMARY KEY(uuid), FOREIGN KEY(token) REFERENCES challenge(token) ON DELETE CASCADE)")
stmt.execute(
"CREATE TABLE IF NOT EXISTS challenge" +
"(token int auto_increment, " +
"id varchar, " +
"secret varchar, " +
"provider varchar, " +
"contentType varchar, " +
"image blob, " +
"attempted int default 0, " +
"PRIMARY KEY(token))"
)
stmt.execute(
"CREATE TABLE IF NOT EXISTS mapId" +
"(uuid varchar, " +
"token int, " +
"lastServed timestamp, " +
"PRIMARY KEY(uuid), " +
"FOREIGN KEY(token) " +
"REFERENCES challenge(token) " +
"ON DELETE CASCADE)"
)
val insertPstmt = dbConn.con.prepareStatement("INSERT INTO challenge(id, secret, provider, contentType, image) VALUES (?, ?, ?, ?, ?)", Statement.RETURN_GENERATED_KEYS )
val mapPstmt = dbConn.con.prepareStatement("INSERT INTO mapId(uuid, token, lastServed) VALUES (?, ?, CURRENT_TIMESTAMP)")
val selectPstmt = dbConn.con.prepareStatement("SELECT c.secret, c.provider FROM challenge c, mapId m WHERE m.token=c.token AND DATEDIFF(MINUTE, CURRENT_TIMESTAMP, DATEADD(MINUTE, 1, m.lastServed)) > 0 AND m.uuid = ?")
val imagePstmt = dbConn.con.prepareStatement("SELECT image FROM challenge c, mapId m WHERE c.token=m.token AND m.uuid = ?")
val updateAttemptedPstmt = dbConn.con.prepareStatement("UPDATE challenge SET attempted = attempted+1 WHERE token = (SELECT m.token FROM mapId m, challenge c WHERE m.token=c.token AND m.uuid = ?)")
val tokenPstmt = dbConn.con.prepareStatement("SELECT token FROM challenge WHERE attempted < 10 ORDER BY RAND() LIMIT 1")
val deleteAnswerPstmt = dbConn.con.prepareStatement("DELETE FROM mapId WHERE uuid = ?")
val challengeGCPstmt = dbConn.con.prepareStatement("DELETE FROM challenge WHERE attempted >= 10 AND token NOT IN (SELECT token FROM mapId)")
val mapIdGCPstmt = dbConn.con.prepareStatement("DELETE FROM mapId WHERE DATEDIFF(MINUTE, CURRENT_TIMESTAMP, DATEADD(MINUTE, 1, lastServed)) < 0")
val insertPstmt: PreparedStatement = dbConn.con.prepareStatement(
"INSERT INTO " +
"challenge(id, secret, provider, contentType, image) " +
"VALUES (?, ?, ?, ?, ?)",
Statement.RETURN_GENERATED_KEYS
)
val mapPstmt: PreparedStatement =
dbConn.con.prepareStatement(
"INSERT INTO " +
"mapId(uuid, token, lastServed) " +
"VALUES (?, ?, CURRENT_TIMESTAMP)"
)
val selectPstmt: PreparedStatement = dbConn.con.prepareStatement(
"SELECT c.secret, c.provider " +
"FROM challenge c, mapId m " +
"WHERE m.token=c.token AND " +
"DATEDIFF(MINUTE, CURRENT_TIMESTAMP, DATEADD(MINUTE, 1, m.lastServed)) > 0 AND " +
"m.uuid = ?"
)
val imagePstmt: PreparedStatement = dbConn.con.prepareStatement(
"SELECT image " +
"FROM challenge c, mapId m " +
"WHERE c.token=m.token AND " +
"m.uuid = ?"
)
val updateAttemptedPstmt: PreparedStatement = dbConn.con.prepareStatement(
"UPDATE challenge " +
"SET attempted = attempted+1 " +
"WHERE token = (SELECT m.token " +
"FROM mapId m, challenge c " +
"WHERE m.token=c.token AND " +
"m.uuid = ?)"
)
val tokenPstmt: PreparedStatement = dbConn.con.prepareStatement(
"SELECT token " +
"FROM challenge " +
"WHERE attempted < 10 " +
"ORDER BY RAND() LIMIT 1"
)
val deleteAnswerPstmt: PreparedStatement = dbConn.con.prepareStatement(
"DELETE FROM mapId WHERE uuid = ?"
)
val challengeGCPstmt: PreparedStatement = dbConn.con.prepareStatement(
"DELETE FROM challenge " +
"WHERE attempted >= 10 AND " +
"token NOT IN (SELECT token FROM mapId)"
)
val mapIdGCPstmt: PreparedStatement = dbConn.con.prepareStatement(
"DELETE FROM mapId WHERE DATEDIFF(MINUTE, CURRENT_TIMESTAMP, DATEADD(MINUTE, 1, lastServed)) < 0"
)
val getCountChallengeTable: PreparedStatement = dbConn.con.prepareStatement(
"SELECT COUNT(*) AS total FROM challenge"
)
val getChallengeTable: PreparedStatement = dbConn.con.prepareStatement(
"SELECT * FROM challenge"
)
val getMapIdTable: PreparedStatement = dbConn.con.prepareStatement(
"SELECT * FROM mapId"
)
val getCountChallengeTable = dbConn.con.prepareStatement("SELECT COUNT(*) AS total FROM challenge")
val getChallengeTable = dbConn.con.prepareStatement("SELECT * FROM challenge")
val getMapIdTable = dbConn.con.prepareStatement("SELECT * FROM mapId")
}
object Statements {
private val dbConn: DBConn = new DBConn()
val tlStmts = ThreadLocal.withInitial(() => new Statements(dbConn))
val tlStmts: ThreadLocal[Statements] = ThreadLocal.withInitial(() => new Statements(dbConn))
}

View File

@ -7,42 +7,52 @@ import lc.core.Captcha
import lc.core.{Parameters, Id, Answer}
import lc.server.HTTPServer
class Server(port: Int, captcha: Captcha){
class Server(port: Int, captcha: Captcha) {
val server = new HTTPServer(port)
val host = server.getVirtualHost(null)
val host: HTTPServer.VirtualHost = server.getVirtualHost(null)
implicit val formats = DefaultFormats
implicit val formats: DefaultFormats.type = DefaultFormats
host.addContext("/v1/captcha",(req, resp) => {
host.addContext(
"/v1/captcha",
(req, resp) => {
val body = req.getJson()
val json = parse(body)
val param = json.extract[Parameters]
val id = captcha.getChallenge(param)
resp.getHeaders().add("Content-Type","application/json")
resp.getHeaders().add("Content-Type", "application/json")
resp.send(200, write(id))
0
},"POST")
},
"POST"
)
host.addContext("/v1/media",(req, resp) => {
host.addContext(
"/v1/media",
(req, resp) => {
val params = req.getParams()
val id = Id(params.get("id"))
val image = captcha.getCaptcha(id)
resp.getHeaders().add("Content-Type","image/png")
resp.getHeaders().add("Content-Type", "image/png")
resp.send(200, image)
0
},"GET")
},
"GET"
)
host.addContext("/v1/answer",(req, resp) => {
host.addContext(
"/v1/answer",
(req, resp) => {
val body = req.getJson()
val json = parse(body)
val answer = json.extract[Answer]
val result = captcha.checkAnswer(answer)
resp.getHeaders().add("Content-Type","application/json")
resp.getHeaders().add("Content-Type", "application/json")
resp.send(200, write(result))
0
},"POST")
},
"POST"
)
def start(): Unit = {
println("Starting server on port:" + port)