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 java-version: 1.8
- name: Run tests - name: Run tests
run: sbt test 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 - 2.13.2
script: script:
- sbt ++$TRAVIS_SCALA_VERSION compile - sbt ++$TRAVIS_SCALA_VERSION compile
- sbt "scalafixAll --check"

View File

@ -1,21 +1,27 @@
lazy val root = (project in file(".")). lazy val root = (project in file(".")).settings(
settings( inThisBuild(
inThisBuild(List( List(
organization := "com.example", organization := "com.example",
scalaVersion := "2.13.3", scalaVersion := "2.13.3",
version := "0.1.0-SNAPSHOT")), version := "0.1.0-SNAPSHOT",
semanticdbEnabled := true,
semanticdbVersion := scalafixSemanticdb.revision,
scalafixScalaBinaryVersion := "2.13"
)
),
name := "LibreCaptcha", name := "LibreCaptcha",
libraryDependencies += "com.sksamuel.scrimage" % "scrimage-core" % "4.0.5", libraryDependencies += "com.sksamuel.scrimage" % "scrimage-core" % "4.0.5",
libraryDependencies += "com.sksamuel.scrimage" % "scrimage-filters" % "4.0.5", libraryDependencies += "com.sksamuel.scrimage" % "scrimage-filters" % "4.0.5",
libraryDependencies += "org.json4s" % "json4s-jackson_2.13" % "3.6.9" 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" javacOptions += "-g:none"
scalafmtOnCompile := true
compileOrder := CompileOrder.JavaThenScala compileOrder := CompileOrder.JavaThenScala
fork in run := true 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

@ -18,18 +18,19 @@ public class FontFunCaptcha implements ChallengeProvider{
private String getFontName(String path, String level) { private String getFontName(String path, String level) {
File file = new File(path + level + "/"); File file = new File(path + level + "/");
FilenameFilter txtFileFilter = new FilenameFilter() { FilenameFilter txtFileFilter =
new FilenameFilter() {
@Override @Override
public boolean accept(File dir, String name) public boolean accept(File dir, String name) {
{ if (name.endsWith(".ttf")) return true;
if(name.endsWith(".ttf")) else return false;
return true;
else
return false;
} }
}; };
File[] files = file.listFiles(txtFileFilter); 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) { private Font loadCustomFont(String level, String path) {
@ -54,7 +55,8 @@ public class FontFunCaptcha implements ChallengeProvider{
FontMetrics fontMetrics = graphics2D.getFontMetrics(); FontMetrics fontMetrics = graphics2D.getFontMetrics();
HelperFunctions.setRenderingHints(graphics2D); HelperFunctions.setRenderingHints(graphics2D);
graphics2D.setColor(Color.decode(colors[HelperFunctions.randomNumber(0, 3)])); graphics2D.setColor(Color.decode(colors[HelperFunctions.randomNumber(0, 3)]));
graphics2D.drawString(String.valueOf(captchaText.charAt(i)), (i * 48), fontMetrics.getAscent()); graphics2D.drawString(
String.valueOf(captchaText.charAt(i)), (i * 48), fontMetrics.getAscent());
} }
graphics2D.dispose(); graphics2D.dispose();
ByteArrayOutputStream baos = new ByteArrayOutputStream(); ByteArrayOutputStream baos = new ByteArrayOutputStream();

View File

@ -21,7 +21,8 @@ public class GifCaptcha implements ChallengeProvider{
Font font = new Font("Bradley Hand", Font.ROMAN_BASELINE, 48); Font font = new Font("Bradley Hand", Font.ROMAN_BASELINE, 48);
Graphics2D graphics2D = img.createGraphics(); Graphics2D graphics2D = img.createGraphics();
graphics2D.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); 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.setFont(font);
graphics2D.setColor(new Color((int) (Math.random() * 0x1000000))); graphics2D.setColor(new Color((int) (Math.random() * 0x1000000)));
graphics2D.drawString(text, 45, 45); graphics2D.drawString(text, 45, 45);

View File

@ -29,7 +29,8 @@ public class ShadowTextCaptcha implements ChallengeProvider{
Font font = new Font("Arial", Font.ROMAN_BASELINE, 48); Font font = new Font("Arial", Font.ROMAN_BASELINE, 48);
Graphics2D graphics2D = img.createGraphics(); Graphics2D graphics2D = img.createGraphics();
graphics2D.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); 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()); TextLayout textLayout = new TextLayout(text, font, graphics2D.getFontRenderContext());
HelperFunctions.setRenderingHints(graphics2D); HelperFunctions.setRenderingHints(graphics2D);
@ -43,8 +44,7 @@ public class ShadowTextCaptcha implements ChallengeProvider{
1f / 9f, 1f / 9f, 1f / 9f, 1f / 9f, 1f / 9f, 1f / 9f,
1f / 9f, 1f / 9f, 1f / 9f 1f / 9f, 1f / 9f, 1f / 9f
}; };
ConvolveOp op = new ConvolveOp(new Kernel(3, 3, kernel), ConvolveOp op = new ConvolveOp(new Kernel(3, 3, kernel), ConvolveOp.EDGE_NO_OP, null);
ConvolveOp.EDGE_NO_OP, null);
BufferedImage img2 = op.filter(img, null); BufferedImage img2 = op.filter(img, null);
Graphics2D g2d = img2.createGraphics(); Graphics2D g2d = img2.createGraphics();
HelperFunctions.setRenderingHints(g2d); HelperFunctions.setRenderingHints(g2d);

View File

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

View File

@ -5,10 +5,10 @@ import java.awt.*;
public class HelperFunctions { public class HelperFunctions {
public static void setRenderingHints(Graphics2D g2d) { public static void setRenderingHints(Graphics2D g2d) {
g2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, g2d.setRenderingHint(
RenderingHints.VALUE_TEXT_ANTIALIAS_ON); RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
g2d.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS, g2d.setRenderingHint(
RenderingHints.VALUE_FRACTIONALMETRICS_ON); RenderingHints.KEY_FRACTIONALMETRICS, RenderingHints.VALUE_FRACTIONALMETRICS_ON);
} }
public static String randomString(int n) { public static String randomString(int n) {

File diff suppressed because it is too large Load Diff

View File

@ -1,16 +1,11 @@
package lc 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.core.{Captcha, CaptchaProviders}
import lc.server.Server import lc.server.Server
import lc.background.BackgroundTask import lc.background.BackgroundTask
object LCFramework { object LCFramework {
def main(args: scala.Array[String]) { def main(args: scala.Array[String]): Unit = {
val captcha = new Captcha() val captcha = new Captcha()
val server = new Server(8888, captcha) val server = new Server(8888, captcha)
val backgroudTask = new BackgroundTask(captcha, 10) val backgroudTask = new BackgroundTask(captcha, 10)
@ -20,9 +15,10 @@ object LCFramework{
} }
object MakeSamples { object MakeSamples {
def main(args: scala.Array[String]) { def main(args: scala.Array[String]): Unit = {
val samples = CaptchaProviders.generateChallengeSamples() val samples = CaptchaProviders.generateChallengeSamples()
samples.foreach {case (key, sample) => samples.foreach {
case (key, sample) =>
val extensionMap = Map("image/png" -> "png", "image/gif" -> "gif") val extensionMap = Map("image/png" -> "png", "image/gif" -> "gif")
println(key + ": " + sample) println(key + ": " + sample)

View File

@ -5,7 +5,6 @@ import java.util.concurrent.{ScheduledThreadPoolExecutor, TimeUnit}
import lc.core.Captcha import lc.core.Captcha
import lc.core.{Parameters, Size} import lc.core.{Parameters, Size}
class BackgroundTask(captcha: Captcha, throttle: Int) { class BackgroundTask(captcha: Captcha, throttle: Int) {
private val task = new Runnable { private val task = new Runnable {
@ -32,7 +31,7 @@ class BackgroundTask(captcha: Captcha, throttle: Int) {
def beginThread(delay: Int): Unit = { def beginThread(delay: Int): Unit = {
val ex = new ScheduledThreadPoolExecutor(1) 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.ChallengeProvider
import lc.captchas.interfaces.Challenge import lc.captchas.interfaces.Challenge
class FilterChallenge extends ChallengeProvider { class FilterChallenge extends ChallengeProvider {
def getId = "FilterChallenge" def getId = "FilterChallenge"
def returnChallenge(): Challenge = { def returnChallenge(): Challenge = {
@ -62,4 +61,3 @@ class FilterType2 extends FilterType {
image image
} }
} }

View File

@ -4,17 +4,17 @@ import java.io.File
import java.io.ByteArrayOutputStream import java.io.ByteArrayOutputStream
import javax.imageio.ImageIO import javax.imageio.ImageIO
import scala.collection.mutable.Map 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.image.BufferedImage
import java.awt.{Graphics2D,Color} import java.awt.Color
import lc.captchas.interfaces.ChallengeProvider import lc.captchas.interfaces.ChallengeProvider
import lc.captchas.interfaces.Challenge import lc.captchas.interfaces.Challenge
class LabelCaptcha extends ChallengeProvider { class LabelCaptcha extends ChallengeProvider {
private var knownFiles = new File("known").list.toList private var knownFiles = new File("known").list.toList
private var unknownFiles = new File("unknown").list.toList private var unknownFiles = new File("unknown").list.toList
private var unknownAnswers = Map[String, Map[String, Int]]() private val unknownAnswers = Map[String, Map[String, Int]]()
private var total = Map[String, Int]() private val total = Map[String, Int]()
for (file <- unknownFiles) { for (file <- unknownFiles) {
unknownAnswers += file -> Map[String, Int]() unknownAnswers += file -> Map[String, Int]()
@ -23,15 +23,15 @@ class LabelCaptcha extends ChallengeProvider {
def getId = "LabelCaptcha" def getId = "LabelCaptcha"
def returnChallenge(): Challenge = synchronized { def returnChallenge(): Challenge =
synchronized {
val r = scala.util.Random.nextInt(knownFiles.length) val r = scala.util.Random.nextInt(knownFiles.length)
val s = scala.util.Random.nextInt(unknownFiles.length) val s = scala.util.Random.nextInt(unknownFiles.length)
val knownImageFile = knownFiles(r) val knownImageFile = knownFiles(r)
val unknownImageFile = unknownFiles(s) val unknownImageFile = unknownFiles(s)
val ip = new ImagePair(knownImageFile, unknownImageFile)
var knownImage = ImageIO.read(new File("known/"+knownImageFile)) val knownImage = ImageIO.read(new File("known/" + knownImageFile))
var unknownImage = ImageIO.read(new File("unknown/"+unknownImageFile)) val unknownImage = ImageIO.read(new File("unknown/" + unknownImageFile))
val mergedImage = merge(knownImage, unknownImage) val mergedImage = merge(knownImage, unknownImage)
val token = encrypt(knownImageFile + "," + unknownImageFile) val token = encrypt(knownImageFile + "," + unknownImageFile)
@ -55,7 +55,8 @@ class LabelCaptcha extends ChallengeProvider {
finalImage finalImage
} }
def checkAnswer(token: String, input: String): Boolean = synchronized { def checkAnswer(token: String, input: String): Boolean =
synchronized {
val parts = decrypt(token).split(",") val parts = decrypt(token).split(",")
val knownImage = parts(0) val knownImage = parts(0)
val unknownImage = parts(1) val unknownImage = parts(1)
@ -73,7 +74,11 @@ class LabelCaptcha extends ChallengeProvider {
if (total(unknownFile) >= 3) { if (total(unknownFile) >= 3) {
if ((unknownAnswers(unknownFile)(userAnswer(1)) / total(unknownFile)) >= 0.9) { if ((unknownAnswers(unknownFile)(userAnswer(1)) / total(unknownFile)) >= 0.9) {
unknownAnswers -= unknownFile 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 knownFiles = new File("known").list.toList
unknownFiles = new File("unknown").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
import java.awt.font.TextAttribute import java.awt.font.TextAttribute
import java.awt.Color import java.awt.Color
import java.io.ByteArrayOutputStream import java.io.ByteArrayOutputStream;
import javax.imageio.ImageIO
import javax.imageio.stream.ImageOutputStream;
import javax.imageio.stream.MemoryCacheImageOutputStream; import javax.imageio.stream.MemoryCacheImageOutputStream;
import lc.captchas.interfaces.ChallengeProvider import lc.captchas.interfaces.ChallengeProvider
import lc.captchas.interfaces.Challenge import lc.captchas.interfaces.Challenge
@ -19,7 +17,7 @@ class Drop {
var yOffset = 0 var yOffset = 0
var color = 0 var color = 0
var colorChange = 10 var colorChange = 10
def mkColor = { def mkColor: Color = {
new Color(color, color, math.min(200, color + 100)) new Color(color, color, math.min(200, color + 100))
} }
} }
@ -60,8 +58,11 @@ class RainDropsCP extends ChallengeProvider {
d.colorChange *= -1 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 baseFont = new Font(Font.MONOSPACED, Font.BOLD, 80)
val attributes = new java.util.HashMap[TextAttribute, Object]() val attributes = new java.util.HashMap[TextAttribute, Object]()
@ -72,7 +73,7 @@ class RainDropsCP extends ChallengeProvider {
val baos = new ByteArrayOutputStream(); val baos = new ByteArrayOutputStream();
val ios = new MemoryCacheImageOutputStream(baos); val ios = new MemoryCacheImageOutputStream(baos);
val writer = new GifSequenceWriter(ios, imgType, 60, true); val writer = new GifSequenceWriter(ios, imgType, 60, true);
for(i <- 0 until 60){ for (_ <- 0 until 60) {
// val yOffset = 5+r.nextInt(5) // val yOffset = 5+r.nextInt(5)
val canvas = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB) val canvas = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB)
val g = canvas.createGraphics() val g = canvas.createGraphics()

View File

@ -1,6 +1,5 @@
package lc.core package lc.core
import org.json4s.JsonAST.JValue
import java.sql.{Blob, ResultSet} import java.sql.{Blob, ResultSet}
import java.util.UUID import java.util.UUID
import java.io.ByteArrayInputStream import java.io.ByteArrayInputStream
@ -23,7 +22,8 @@ class Captcha {
} }
} }
image image
} catch { case e: Exception => } catch {
case e: Exception =>
println(e) println(e)
image image
} }
@ -64,7 +64,8 @@ class Captcha {
updateAttemptedPstmt.setString(1, uuid) updateAttemptedPstmt.setString(1, uuid)
updateAttemptedPstmt.executeUpdate() updateAttemptedPstmt.executeUpdate()
Id(uuid) Id(uuid)
} catch {case e: Exception => } catch {
case e: Exception =>
println(e) println(e)
Id(getUUID(-1)) Id(getUUID(-1))
} }

View File

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

View File

@ -2,30 +2,108 @@ package lc.database
import lc.database.DBConn import lc.database.DBConn
import java.sql.Statement import java.sql.Statement
import java.sql.PreparedStatement
class Statements(dbConn: DBConn) { class Statements(dbConn: DBConn) {
private val stmt = dbConn.getStatement() 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(
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)") "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 insertPstmt: PreparedStatement = dbConn.con.prepareStatement(
val mapPstmt = dbConn.con.prepareStatement("INSERT INTO mapId(uuid, token, lastServed) VALUES (?, ?, CURRENT_TIMESTAMP)") "INSERT INTO " +
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 = ?") "challenge(id, secret, provider, contentType, image) " +
val imagePstmt = dbConn.con.prepareStatement("SELECT image FROM challenge c, mapId m WHERE c.token=m.token AND m.uuid = ?") "VALUES (?, ?, ?, ?, ?)",
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 = ?)") Statement.RETURN_GENERATED_KEYS
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 mapPstmt: PreparedStatement =
val mapIdGCPstmt = dbConn.con.prepareStatement("DELETE FROM mapId WHERE DATEDIFF(MINUTE, CURRENT_TIMESTAMP, DATEADD(MINUTE, 1, lastServed)) < 0") 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 { object Statements {
private val dbConn: DBConn = new DBConn() 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,14 +7,15 @@ import lc.core.Captcha
import lc.core.{Parameters, Id, Answer} import lc.core.{Parameters, Id, Answer}
import lc.server.HTTPServer import lc.server.HTTPServer
class Server(port: Int, captcha: Captcha) { class Server(port: Int, captcha: Captcha) {
val server = new HTTPServer(port) 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 body = req.getJson()
val json = parse(body) val json = parse(body)
val param = json.extract[Parameters] val param = json.extract[Parameters]
@ -22,18 +23,26 @@ class Server(port: Int, captcha: Captcha){
resp.getHeaders().add("Content-Type", "application/json") resp.getHeaders().add("Content-Type", "application/json")
resp.send(200, write(id)) resp.send(200, write(id))
0 0
},"POST") },
"POST"
)
host.addContext("/v1/media",(req, resp) => { host.addContext(
"/v1/media",
(req, resp) => {
val params = req.getParams() val params = req.getParams()
val id = Id(params.get("id")) val id = Id(params.get("id"))
val image = captcha.getCaptcha(id) val image = captcha.getCaptcha(id)
resp.getHeaders().add("Content-Type", "image/png") resp.getHeaders().add("Content-Type", "image/png")
resp.send(200, image) resp.send(200, image)
0 0
},"GET") },
"GET"
)
host.addContext("/v1/answer",(req, resp) => { host.addContext(
"/v1/answer",
(req, resp) => {
val body = req.getJson() val body = req.getJson()
val json = parse(body) val json = parse(body)
val answer = json.extract[Answer] val answer = json.extract[Answer]
@ -41,8 +50,9 @@ class Server(port: Int, captcha: Captcha){
resp.getHeaders().add("Content-Type", "application/json") resp.getHeaders().add("Content-Type", "application/json")
resp.send(200, write(result)) resp.send(200, write(result))
0 0
},"POST") },
"POST"
)
def start(): Unit = { def start(): Unit = {
println("Starting server on port:" + port) println("Starting server on port:" + port)