Compare commits

..

271 Commits

Author SHA1 Message Date
hrj a53903a757 update scala version in scripts 2024-12-29 18:59:53 +05:30
hrj 12ddf59bd1
Merge pull request #260 from scala-steward/update/scala3-library-3.6.2
Update scala3-library to 3.6.2
2024-12-18 13:31:22 +05:30
hrj 5a5c9a73d9 setup sbt explicitly 2024-12-17 21:14:34 +05:30
Scala Steward a8f50f6098
Update scala3-library to 3.6.2
Signed-off-by: Scala Steward <scala_steward@virtuslab.com>
2024-12-11 17:38:27 +00:00
hrj ab1387814c
Merge pull request #259 from scala-steward/update/scrimage-core-4.3.0
Update scrimage-core, scrimage-filters to 4.3.0
2024-12-04 08:47:38 +05:30
Scala Steward f0d2ddda44
Update scrimage-core, scrimage-filters to 4.3.0
Signed-off-by: Scala Steward <scala_steward@virtuslab.com>
2024-12-03 20:03:13 +00:00
hrj 2fefc62ba2
Merge pull request #258 from scala-steward/update/sbt-1.10.6
Update sbt, scripted-plugin to 1.10.6
2024-12-02 09:56:05 +05:30
Scala Steward 2ec93c4b95
Update sbt, scripted-plugin to 1.10.6
Signed-off-by: Scala Steward <scala_steward@virtuslab.com>
2024-12-01 20:13:15 +00:00
hrj f60cb8dd05 Merge remote-tracking branch 'origin/master' 2024-11-06 06:54:59 +05:30
hrj 5c73a0fea0 scala steward config to signoff commits 2024-11-06 06:54:17 +05:30
hrj 45299fb8eb
Merge pull request #257 from scala-steward/update/sbt-1.10.5
Update sbt, scripted-plugin to 1.10.5
2024-11-06 06:50:50 +05:30
Scala Steward 3c6bbe8c9f
Update sbt, scripted-plugin to 1.10.5 2024-11-05 19:38:15 +00:00
hrj e122976878
Merge pull request #256 from scala-steward/update/sbt-1.10.4
Update sbt, scripted-plugin to 1.10.4
2024-10-31 11:32:55 +05:30
Scala Steward f702a22dcd
Update sbt, scripted-plugin to 1.10.4 2024-10-28 17:16:37 +00:00
hrj b9bd305599 update scala version in test script 2024-10-25 10:45:44 +05:30
hrj f4737424f7 update scala version in scripts 2024-10-25 10:03:18 +05:30
hrj 355e8c553a
Merge pull request #255 from scala-steward/update/scala3-library-3.5.2
Update scala3-library to 3.5.2
2024-10-25 09:52:14 +05:30
Scala Steward 777be7cf08
Update scala3-library to 3.5.2 2024-10-24 18:24:36 +00:00
hrj c7c89a6396
Merge pull request #252 from scala-steward/update/sbt-scalafix-0.13.0
Update sbt-scalafix to 0.13.0
2024-10-24 16:46:43 +05:30
hrj 471a5828cd
Merge pull request #254 from scala-steward/update/sbt-1.10.3
Update sbt, scripted-plugin to 1.10.3
2024-10-22 09:13:04 +05:30
Scala Steward c7327f07ee
Update sbt, scripted-plugin to 1.10.3 2024-10-20 17:55:01 +00:00
hrj aa31b2d9cf
Merge pull request #253 from scala-steward/update/sbt-assembly-2.3.0
Update sbt-assembly to 2.3.0
2024-10-20 22:36:51 +05:30
Scala Steward 592a85257e
Update sbt-assembly to 2.3.0 2024-10-08 17:02:29 +00:00
Scala Steward 5c3160157a
Update sbt-scalafix to 0.13.0 2024-09-28 19:03:49 +00:00
hrj 24ac9c5ee7
Merge pull request #249 from scala-steward/update/sbt-1.10.2
Update sbt to 1.10.2
2024-09-20 22:55:14 +05:30
Scala Steward 6cf0b322fc
Update sbt to 1.10.2 2024-09-16 16:11:09 +00:00
hrj 967145b73b update scala in scripts 2024-08-24 11:17:08 +05:30
hrj 332aac57f0
Merge pull request #248 from scala-steward/update/scala3-library-3.5.0
Update scala3-library to 3.5.0
2024-08-24 11:12:34 +05:30
Scala Steward f6e0130a53
Update scala3-library to 3.5.0 2024-08-23 15:33:05 +00:00
hrj 20f32bdabb update scripts for scala 3.4.3 2024-08-16 10:55:07 +05:30
hrj 660d18d6f2
Merge pull request #247 from scala-steward/update/scala3-library-3.4.3
Update scala3-library to 3.4.3
2024-08-16 10:53:11 +05:30
Scala Steward b786330bff
Update scala3-library to 3.4.3 2024-08-15 16:10:43 +00:00
hrj dbb675b975
Merge pull request #244 from scala-steward/update/scalafmt-core-3.8.3
Update scalafmt-core to 3.8.3
2024-08-15 15:00:11 +05:30
hrj 220eec02b0
Merge pull request #245 from scala-steward/update/scrimage-core-4.2.0
Update scrimage-core, scrimage-filters to 4.2.0
2024-08-13 11:47:38 +05:30
Scala Steward 3e822ef02a
Update scrimage-core, scrimage-filters to 4.2.0 2024-08-12 23:02:17 +00:00
Scala Steward 1c273c1996
Update scalafmt-core to 3.8.3 2024-07-29 18:24:04 +00:00
hrj 7887622040
Merge pull request #243 from scala-steward/update/sbt-1.10.1
Update sbt to 1.10.1
2024-07-11 08:38:27 +05:30
Scala Steward bf303108aa
Update sbt to 1.10.1 2024-07-10 16:58:41 +00:00
hrj cc7e1232f2
Merge pull request #242 from scala-steward/update/scalafmt-core-3.8.2
Update scalafmt-core to 3.8.2
2024-06-14 23:13:16 +05:30
Scala Steward a9b148c1a9
Update scalafmt-core to 3.8.2 2024-06-14 16:56:30 +00:00
hrj 13459fbf21 update scala version in scripts 2024-06-01 16:07:15 +05:30
hrj fa366be315
Merge pull request #241 from scala-steward/update/scala3-library-3.4.2
Update scala3-library to 3.4.2
2024-06-01 15:47:13 +05:30
Scala Steward 9e39f6d293
Update scala3-library to 3.4.2 2024-05-17 16:20:08 +00:00
hrj f0050494a1
Merge pull request #239 from scala-steward/update/sbt-scalafix-0.12.1
Update sbt-scalafix to 0.12.1
2024-05-08 12:24:38 +05:30
hrj 52156b1677
Merge pull request #240 from scala-steward/update/sbt-1.10.0
Update sbt to 1.10.0
2024-05-08 11:33:41 +05:30
Scala Steward 4749486112
Update sbt to 1.10.0 2024-05-07 21:49:03 +00:00
Scala Steward 7c7d7f6633
Update sbt-scalafix to 0.12.1 2024-05-05 16:32:23 +00:00
hrj 17db5767f4
Merge pull request #238 from scala-steward/update/scrimage-core-4.1.3
Update scrimage-core, scrimage-filters to 4.1.3
2024-04-27 21:57:21 +05:30
Scala Steward 3d7b7e8bd9
Update scrimage-core, scrimage-filters to 4.1.3 2024-04-27 16:16:50 +00:00
hrj 1991375d6b
Merge pull request #236 from scala-steward/update/scalafmt-core-3.8.1
Update scalafmt-core to 3.8.1
2024-04-26 08:45:39 +05:30
hrj b77bd94ae6
Merge pull request #237 from scala-steward/update/scrimage-core-4.1.2
Update scrimage-core, scrimage-filters to 4.1.2
2024-04-26 08:45:17 +05:30
Scala Steward b14415add5
Update scrimage-core, scrimage-filters to 4.1.2 2024-04-25 16:16:42 +00:00
hrj ac44980f50 update scripts to scala 3.4.1 2024-04-17 22:59:58 +05:30
hrj fba5b10e77
Merge pull request #235 from scala-steward/update/scala3-library-3.4.1
Update scala3-library to 3.4.1
2024-04-17 22:57:30 +05:30
Scala Steward c4515d3f77
Update scalafmt-core to 3.8.1 2024-03-30 19:11:05 +00:00
Scala Steward 7c394b4359
Update scala3-library to 3.4.1 2024-03-28 17:44:28 +00:00
hrj 6db9934bec
Merge pull request #234 from scala-steward/update/sbt-assembly-2.2.0
Update sbt-assembly to 2.2.0
2024-03-15 09:03:31 +05:30
Scala Steward 39b776fde8
Update sbt-assembly to 2.2.0 2024-03-14 17:32:47 +00:00
hrj 4c3a7ad0fa update dejava font package name 2024-03-04 09:32:39 +05:30
hrj 7f70d60dbb try 17 jre jammy docker image 2024-03-04 09:27:07 +05:30
hrj 4d79567227 use eclipse-temurin containers from adoptium 2024-03-04 09:12:57 +05:30
hrj f6b7278ed1 update scripts to scala 3.3.3 2024-03-04 08:32:35 +05:30
hrj 984bbacb69
Merge pull request #233 from scala-steward/update/scala3-library-3.3.3
Update scala3-library to 3.3.3
2024-03-04 08:27:44 +05:30
hrj 0dd00f5d3e
Merge pull request #232 from scala-steward/update/sbt-scalafix-0.12.0
Update sbt-scalafix to 0.12.0
2024-03-04 08:27:14 +05:30
Scala Steward d355333bd4
Update scala3-library to 3.3.3 2024-03-02 18:55:20 +00:00
Scala Steward 55064c8532
Update sbt-scalafix to 0.12.0 2024-03-02 18:55:14 +00:00
hrj 1742772de1 update jdk version in dockerfile 2024-02-24 09:13:16 +05:30
hrj 1234bd4acb
Merge pull request #230 from scala-steward/update/scalafmt-core-3.8.0
Update scalafmt-core to 3.8.0
2024-02-24 08:49:23 +05:30
hrj df82593d90
Merge pull request #231 from scala-steward/update/sbt-1.9.9
Update sbt to 1.9.9
2024-02-24 08:49:05 +05:30
Scala Steward b782ded765
Update sbt to 1.9.9 2024-02-23 19:34:34 +00:00
Scala Steward aa6a6a1015
Update scalafmt-core to 3.8.0 2024-02-19 23:16:43 +00:00
hrj 157054d9d8
Merge pull request #229 from scala-steward/update/sbt-1.9.8
Update sbt to 1.9.8
2024-01-14 17:03:50 +05:30
Scala Steward daa8903933
Update sbt to 1.9.8 2024-01-12 23:23:08 +00:00
hrj de3a2bad6f
Merge pull request #228 from scala-steward/update/sbt-1.9.7
Update sbt to 1.9.7
2023-12-04 08:03:30 +05:30
hrj 121b190c32
Merge pull request #227 from scala-steward/update/json4s-jackson-4.0.7
Update json4s-jackson to 4.0.7
2023-12-04 08:02:53 +05:30
Scala Steward b95350b1e2
Update sbt to 1.9.7 2023-12-03 16:35:11 +00:00
Scala Steward 26bc159342
Update json4s-jackson to 4.0.7 2023-12-03 16:35:07 +00:00
hrj 476ef499e3
Merge pull request #226 from scala-steward/update/sbt-scalafmt-2.5.2
Update sbt-scalafmt to 2.5.2
2023-12-02 08:26:37 +05:30
hrj 3b925adfde
Merge branch 'master' into update/sbt-scalafmt-2.5.2 2023-12-02 08:15:55 +05:30
hrj 16a8282206
Merge pull request #225 from scala-steward/update/sbt-1.8.3
Update sbt to 1.8.3
2023-12-02 08:14:13 +05:30
hrj 4cedff874d
Merge pull request #224 from scala-steward/update/sbt-assembly-2.1.5
Update sbt-assembly to 2.1.5
2023-12-02 08:13:38 +05:30
hrj d8360c6192
Merge pull request #223 from scala-steward/update/sbt-scalafix-0.11.1
Update sbt-scalafix to 0.11.1
2023-12-02 08:12:46 +05:30
Scala Steward e81e8a77e8
Update sbt-scalafmt to 2.5.2 2023-12-01 17:39:20 +00:00
Scala Steward c045682708
Update sbt to 1.8.3 2023-12-01 17:39:15 +00:00
Scala Steward 414c07beaf
Update sbt-assembly to 2.1.5 2023-12-01 17:39:02 +00:00
Scala Steward aa6607237a
Update sbt-scalafix to 0.11.1 2023-12-01 17:38:57 +00:00
hrj 931ab0a086
Merge pull request #222 from scala-steward/update/scalafmt-core-3.7.17
Update scalafmt-core to 3.7.17
2023-11-19 08:07:43 +05:30
Scala Steward bc4acb1bae
Update scalafmt-core to 3.7.17 2023-11-17 20:29:03 +00:00
hrj 2dc2066ad8
Merge pull request #220 from scala-steward/update/scalafmt-core-3.7.15
Update scalafmt-core to 3.7.15
2023-10-25 07:50:44 +05:30
Scala Steward bc8c6ad227
Update scalafmt-core to 3.7.15 2023-10-24 21:15:37 +00:00
hrj be0612b94d
Merge pull request #219 from scala-steward/update/scrimage-core-4.1.1
Update scrimage-core, scrimage-filters to 4.1.1
2023-10-17 06:59:21 +05:30
Scala Steward fe2315fc89
Update scrimage-core, scrimage-filters to 4.1.1 2023-10-16 22:35:56 +00:00
hrj 1696050702
Merge pull request #218 from scala-steward/update/scrimage-core-4.1.0
Update scrimage-core, scrimage-filters to 4.1.0
2023-09-21 13:47:45 +05:30
Scala Steward 5ddfab95d1
Update scrimage-core, scrimage-filters to 4.1.0 2023-09-20 16:34:22 +00:00
hrj 15c95747e8
Merge pull request #217 from scala-steward/update/scrimage-core-4.0.42
Update scrimage-core, scrimage-filters to 4.0.42
2023-09-20 08:16:41 +05:30
Scala Steward 12bb1dd1e7
Update scrimage-core, scrimage-filters to 4.0.42 2023-09-18 18:01:05 +00:00
hrj 1de046362d update scala version in scripts 2023-09-09 08:31:18 +05:30
hrj f9a089bed1
Merge pull request #216 from scala-steward/update/scalafmt-core-3.7.14
Update scalafmt-core to 3.7.14
2023-09-09 08:27:40 +05:30
hrj caac0552c3
Merge pull request #215 from scala-steward/update/scala3-library-3.3.1
Update scala3-library to 3.3.1
2023-09-09 08:27:02 +05:30
Scala Steward 1701a497b6
Update scalafmt-core to 3.7.14 2023-09-08 22:21:34 +00:00
Scala Steward ecabb6baae
Update scala3-library to 3.3.1 2023-09-08 22:21:25 +00:00
hrj 065748e339
Merge pull request #213 from scala-steward/update/scalafmt-core-3.7.12
Update scalafmt-core to 3.7.12
2023-08-07 09:19:35 +05:30
Scala Steward 707d02a71f
Update scalafmt-core to 3.7.12 2023-08-05 16:15:44 +00:00
hrj a69bd1afaf
Merge pull request #212 from scala-steward/update/scalafmt-core-3.7.11
Update scalafmt-core to 3.7.11
2023-07-31 23:23:56 +05:30
Scala Steward f0f62eb4fc
Update scalafmt-core to 3.7.11 2023-07-30 16:52:14 +00:00
hrj fbaa0f24a8
Merge pull request #211 from scala-steward/update/scalafmt-core-3.7.10
Update scalafmt-core to 3.7.10
2023-07-17 21:47:17 +05:30
hrj ddd4054db1
Merge pull request #210 from scala-steward/update/scrimage-core-4.0.38
Update scrimage-core, scrimage-filters to 4.0.38
2023-07-17 21:46:57 +05:30
Scala Steward 66c93c5069
Update scalafmt-core to 3.7.10 2023-07-16 16:43:51 +00:00
Scala Steward 75ac7b2f88
Update scrimage-core, scrimage-filters to 4.0.38 2023-07-16 16:43:44 +00:00
hrj 367eef011f
Merge pull request #209 from scala-steward/update/scalafmt-core-3.7.8
Update scalafmt-core to 3.7.8
2023-07-13 20:06:36 +05:30
Scala Steward 8be4ad2be8
Update scalafmt-core to 3.7.8 2023-07-12 18:34:15 +00:00
hrj f7568ffd5f
Merge pull request #208 from scala-steward/update/scalafmt-core-3.7.7
Update scalafmt-core to 3.7.7
2023-07-09 18:00:07 +05:30
Scala Steward 061dc82c68
Update scalafmt-core to 3.7.7 2023-07-08 20:45:06 +00:00
hrj 6ef321c418
Merge pull request #207 from scala-steward/update/scalafmt-core-3.7.6
Update scalafmt-core to 3.7.6
2023-07-06 09:09:47 +05:30
Scala Steward d3048b3b4a
Update scalafmt-core to 3.7.6 2023-07-04 17:50:54 +00:00
hrj 33fbd9a49b
Merge pull request #206 from scala-steward/update/scalafmt-core-3.7.5
Update scalafmt-core to 3.7.5
2023-07-01 10:23:01 +05:30
Scala Steward f9e626415d
Update scalafmt-core to 3.7.5 2023-06-30 19:19:09 +00:00
hrj ddb333a1df
Merge pull request #205 from scala-steward/update/scrimage-core-4.0.37
Update scrimage-core, scrimage-filters to 4.0.37
2023-06-22 11:10:23 +05:30
Scala Steward 62c0c5c363
Update scrimage-core, scrimage-filters to 4.0.37 2023-06-20 16:18:54 +00:00
hrj cff297a48f
Merge pull request #204 from scala-steward/update/scrimage-core-4.0.36
Update scrimage-core, scrimage-filters to 4.0.36
2023-06-14 08:01:52 +05:30
Scala Steward 10ca28ba07
Update scrimage-core, scrimage-filters to 4.0.36 2023-06-12 16:41:50 +00:00
hrj afab1b4803 update scala version in scripts 2023-06-02 17:34:13 +05:30
hrj 205e652e1e
Merge pull request #203 from scala-steward/update/scalafmt-core-3.7.4
Update scalafmt-core to 3.7.4
2023-06-02 17:27:53 +05:30
hrj 03ac5e5e83
Merge pull request #202 from scala-steward/update/scala3-library-3.3.0
Update scala3-library to 3.3.0
2023-06-02 17:26:50 +05:30
Scala Steward ce71407d74
Update scalafmt-core to 3.7.4 2023-05-31 19:36:09 +00:00
Scala Steward 2a33fe1e90
Update scala3-library to 3.3.0 2023-05-31 19:36:00 +00:00
hrj 9399adf2c3
Merge pull request #201 from scala-steward/update/scalafmt-core-3.7.3
Update scalafmt-core to 3.7.3
2023-04-01 07:35:30 +05:30
Scala Steward ef19bbc0c1
Update scalafmt-core to 3.7.3 2023-03-30 18:55:16 +00:00
hrj 1c95665f67
Merge pull request #200 from scala-steward/update/scrimage-core-4.0.34
Update scrimage-core, scrimage-filters to 4.0.34
2023-03-27 22:36:48 +05:30
Scala Steward 6bb9fbd7ea
Update scrimage-core, scrimage-filters to 4.0.34 2023-03-26 15:20:14 +00:00
hrj 6e6daf6c36
Merge pull request #198 from scala-steward/update/scalafmt-core-3.7.2
Update scalafmt-core to 3.7.2
2023-02-27 16:06:13 +05:30
Scala Steward daa6c49f8a
Update scalafmt-core to 3.7.2 2023-02-26 16:53:56 +00:00
hrj cb9b0d361b
Merge pull request #197 from scala-steward/update/sbt-assembly-2.1.1
Update sbt-assembly to 2.1.1
2023-02-15 06:41:07 +05:30
Scala Steward ae9163d106
Update sbt-assembly to 2.1.1 2023-02-14 16:36:59 +00:00
hrj ba21b133fc update scala to 3.2.2 2023-02-01 08:41:21 +05:30
hrj 3b0b464109
Merge pull request #196 from scala-steward/update/scala3-library-3.2.2
Update scala3-library to 3.2.2
2023-02-01 08:39:41 +05:30
Scala Steward 1663e2b306
Update scala3-library to 3.2.2 2023-01-31 18:39:59 +00:00
hrj d77bc658a0
Merge pull request #195 from scala-steward/update/scalafmt-core-3.7.1
Update scalafmt-core to 3.7.1
2023-01-26 08:04:51 +05:30
Scala Steward 33ad07c768
Update scalafmt-core to 3.7.1 2023-01-25 18:06:58 +00:00
hrj beccf4dff6 Merge remote-tracking branch 'origin/master' 2023-01-22 22:52:09 +05:30
hrj e2688f7e51
Merge pull request #194 from scala-steward/update/scalafmt-core-3.7.0
Update scalafmt-core to 3.7.0
2023-01-22 14:25:33 +05:30
Scala Steward ef4d1690e1
Update scalafmt-core to 3.7.0 2023-01-21 16:51:30 +00:00
hrj 43291222a2 README: explain podman usage and publish ports in example 2023-01-18 20:07:01 +05:30
hrj 044d39ad44
Merge pull request #191 from scala-steward/update/scrimage-core-4.0.33
Update scrimage-core, scrimage-filters to 4.0.33
2023-01-18 07:40:53 +05:30
Scala Steward b0f75677c9
Update scrimage-core, scrimage-filters to 4.0.33 2023-01-17 18:16:23 +00:00
hrj cda8bf6aa5 In Runner, use apt instead of apk 2023-01-15 08:21:03 +05:30
hrj 57b43e0f31 In Runner dockerfile use the same docker base as normal Dockerfile 2023-01-15 08:15:04 +05:30
hrj bfa2cfdc88
Merge pull request #190 from gheorghiuradu/feature/multi-platform-docker-support
Add multi platform docker support
2023-01-14 22:44:28 +05:30
gheorghiuradu 5cbb23b714
add arm platforms to build 2023-01-13 15:37:43 +02:00
gheorghiuradu a1739e5288
Update to use multi-platform images 2023-01-13 15:35:27 +02:00
hrj d5f7fdcad1
Merge pull request #189 from scala-steward/update/sbt-1.8.2
Update sbt to 1.8.2
2023-01-08 17:54:16 +05:30
Scala Steward 5950e42973
Update sbt to 1.8.2 2023-01-07 20:14:36 +00:00
hrj 59934539a7
Merge pull request #188 from scala-steward/update/sbt-assembly-2.1.0
Update sbt-assembly to 2.1.0
2022-12-11 07:39:50 +05:30
Scala Steward 399ee06b62
Update sbt-assembly to 2.1.0 2022-12-10 16:41:58 +00:00
hrj 5d7ccbb1c5 update scala version in scripts 2022-11-30 09:51:52 +05:30
hrj 25fb901e9e bump scala to 3.2.1 2022-11-30 09:49:04 +05:30
hrj dd48e9e4e7
Merge pull request #187 from scala-steward/update/sbt-scalafmt-2.5.0
Update sbt-scalafmt to 2.5.0
2022-11-15 06:20:15 +05:30
hrj 43cb991e16
Merge pull request #186 from scala-steward/update/sbt-1.8.0
Update sbt to 1.8.0
2022-11-15 06:19:48 +05:30
Scala Steward 737ebd76ae
Update sbt-scalafmt to 2.5.0 2022-11-14 22:59:30 +00:00
Scala Steward 30c978ea1c
Update sbt to 1.8.0 2022-11-14 22:59:24 +00:00
hrj 7721df414b
Merge pull request #184 from scala-steward/update/scalafmt-core-3.6.1
Update scalafmt-core to 3.6.1
2022-11-03 09:22:49 +05:30
hrj 901759e06e
Merge pull request #183 from scala-steward/update/sbt-1.7.3
Update sbt to 1.7.3
2022-11-03 09:22:06 +05:30
Scala Steward 2dab3e0638
Add 'Reformat with scalafmt 3.6.1' to .git-blame-ignore-revs 2022-11-02 18:33:05 +00:00
Scala Steward f2b19baca8
Reformat with scalafmt 3.6.1
Executed command: scalafmt --non-interactive
2022-11-02 18:33:05 +00:00
Scala Steward 56b42801d9
Update scalafmt-core to 3.6.1 2022-11-02 18:32:55 +00:00
Scala Steward 448bb5fe9f
Update sbt to 1.7.3 2022-11-02 18:32:47 +00:00
hrj fadfffafb0
Merge pull request #182 from scala-steward/update/scalafmt-core-3.6.0
Update scalafmt-core to 3.6.0
2022-10-24 10:45:01 +05:30
hrj f7d38f5f88
Merge pull request #181 from scala-steward/update/sbt-1.7.2
Update sbt to 1.7.2
2022-10-24 10:43:20 +05:30
hrj 85cfdf642c
Merge pull request #180 from scala-steward/update/json4s-jackson-4.0.6
Update json4s-jackson to 4.0.6
2022-10-24 10:42:55 +05:30
hrj 7dea087e2d
Merge pull request #179 from scala-steward/update/sbt-java-formatter-0.8.0
Update sbt-java-formatter to 0.8.0
2022-10-24 10:42:30 +05:30
hrj f7d03ac5cf
Merge branch 'master' into update/sbt-java-formatter-0.8.0 2022-10-24 10:12:59 +05:30
hrj a40b33d492
Merge pull request #178 from scala-steward/update/sbt-assembly-2.0.0
Update sbt-assembly to 2.0.0
2022-10-24 10:11:01 +05:30
Scala Steward 6eb38929d2
Update scalafmt-core to 3.6.0 2022-10-23 18:04:51 +00:00
Scala Steward a88068b865
Update sbt to 1.7.2 2022-10-23 18:04:45 +00:00
Scala Steward 5e9c9e2d5d
Update json4s-jackson to 4.0.6 2022-10-23 18:04:37 +00:00
Scala Steward f9ae1e3970
Add 'Reformat with sbt-java-formatter 0.8.0' to .git-blame-ignore-revs 2022-10-23 18:04:16 +00:00
Scala Steward 57ce691a00
Reformat with sbt-java-formatter 0.8.0
Executed command: sbt javafmtAll
2022-10-23 18:04:16 +00:00
Scala Steward 55e68d23f4
Update sbt-java-formatter to 0.8.0 2022-10-23 18:03:11 +00:00
Scala Steward b27bcc8e52
Update sbt-assembly to 2.0.0 2022-10-23 18:03:00 +00:00
hrj 6ce5bff8c5
Merge pull request #177 from scala-steward/update/sbt-scalafix-0.10.4
Update sbt-scalafix to 0.10.4
2022-10-15 07:14:49 +05:30
Scala Steward 9ef3d162f2
Update sbt-scalafix to 0.10.4 2022-10-15 00:52:24 +00:00
hrj 515782ea8b update docker image version in readme 2022-10-11 10:52:16 +05:30
hrj 70ca5673d1 fix #162: update h2 2022-10-11 10:23:01 +05:30
hrj 2a092b777e fix jar path in Runner.Dockerfile 2022-10-11 07:38:09 +05:30
hrj a2fc659eda fix jar paths in dockerfile and tests 2022-10-11 07:36:35 +05:30
hrj a020426329 update sbt in dockerfile 2022-10-11 07:36:21 +05:30
hrj 907210f229 show a message when playground is enabled 2022-10-11 07:30:58 +05:30
hrj 58c6e96dc9 fix #175: playgroundEnabled config option was not effective 2022-10-11 07:30:46 +05:30
hrj 7beecd7b80
Merge pull request #174 from scala-steward/update/scala3-library-3.2.0
Update scala3-library to 3.2.0
2022-09-08 10:13:57 +05:30
Scala Steward 2f18edef10
Update scala3-library to 3.2.0 2022-09-07 17:37:10 +00:00
hrj 76dcd1b41e
Merge pull request #173 from scala-steward/update/scrimage-core-4.0.32
Update scrimage-core, scrimage-filters to 4.0.32
2022-08-29 09:06:37 +05:30
Scala Steward 32ac8f5baa
Update scrimage-core, scrimage-filters to 4.0.32 2022-08-28 14:32:06 +00:00
hrj 5794693f51
Merge pull request #172 from scala-steward/update/scalafmt-core-3.5.9
Update scalafmt-core to 3.5.9
2022-08-12 22:13:39 +05:30
Scala Steward c02f284cc9
Update scalafmt-core to 3.5.9 2022-08-12 16:09:22 +00:00
hrj 055525bd74
Merge pull request #171 from scala-steward/update/sbt-1.7.1
Update sbt to 1.7.1
2022-07-14 18:44:49 +05:30
Scala Steward dc5d9d86ff
Update sbt to 1.7.1 2022-07-13 22:14:53 +00:00
hrj ffe3142fbf
Merge pull request #168 from scala-steward/update/scala3-library-3.1.3
Update scala3-library to 3.1.3
2022-07-10 12:50:48 +05:30
hrj 5537d907a1
Merge pull request #169 from scala-steward/update/scalafmt-core-3.5.8
Update scalafmt-core to 3.5.8
2022-07-10 12:18:34 +05:30
hrj 15dfc1576e
Merge pull request #167 from scala-steward/update/sbt-scalafix-0.10.1
Update sbt-scalafix to 0.10.1
2022-07-10 09:20:46 +05:30
Scala Steward 603e6eda22
Update scalafmt-core to 3.5.8 2022-07-09 22:40:38 +00:00
Scala Steward 3532f2ec29
Update scala3-library to 3.1.3 2022-07-09 22:40:33 +00:00
Scala Steward 0a4ca07ed4
Update sbt-scalafix to 0.10.1 2022-07-09 22:40:23 +00:00
hrj 95a7ee1710
Merge pull request #166 from scala-steward/update/scalafmt-core-3.5.3
Update scalafmt-core to 3.5.3
2022-05-15 06:53:46 +05:30
Scala Steward aa484e4bc0
Update scalafmt-core to 3.5.3 2022-05-15 00:38:11 +02:00
hrj 0549d9cfcf
Merge pull request #165 from scala-steward/update/scalafmt-core-3.5.2
Update scalafmt-core to 3.5.2
2022-05-01 06:04:55 +05:30
Scala Steward 94dddd99c0
Update scalafmt-core to 3.5.2 2022-04-30 21:44:09 +02:00
hrj 353ecaa1eb
Merge pull request #164 from scala-steward/update/scalafmt-core-3.5.1
Update scalafmt-core to 3.5.1
2022-04-17 00:20:09 +05:30
Scala Steward b402d34be8
Reformat with scalafmt 3.5.1 2022-04-16 20:42:16 +02:00
Scala Steward 37d840b365
Update scalafmt-core to 3.5.1 2022-04-16 20:42:09 +02:00
hrj a9b630724d update to scala 3.1.2 2022-04-13 07:34:11 +05:30
hrj b50df7ec2f
Merge pull request #161 from scala-steward/update/sbt-scalafix-0.10.0
Update sbt-scalafix to 0.10.0
2022-04-08 08:38:55 +05:30
Scala Steward b3c0a83594
Update sbt-scalafix to 0.10.0 2022-04-08 02:47:20 +02:00
hrj 35909261ce
Merge pull request #160 from vinceh121/patch-1
Update curl example for v2 changes
2022-04-07 21:48:01 +05:30
Vincent 073740b98b
Update curl example for v2 changes
Signed-off-by: vinceh121 <contact@vinceh121.me>
2022-04-07 16:08:40 +02:00
hrj c7726edc1c set next release version 2022-04-06 20:29:04 +05:30
hrj e35d569cbb
Merge pull request #155 from scala-steward/update/json4s-jackson-4.0.5
Update json4s-jackson to 4.0.5
2022-04-05 23:17:17 +05:30
hrj e6b09b8d33
Merge pull request #158 from JacobPozaic/master
Updated javascript example usage in README.md for 2.0.0 changes.
2022-04-05 22:48:49 +05:30
JacobPozaic a43b18587e Updated javascript example usage in README.md for 2.0.0 changes.
Signed-off-by: jacobp <jacobpozaic@gmail.com>
2022-04-05 10:30:31 -04:00
Scala Steward fb1bfbd881
Update json4s-jackson to 4.0.5 2022-04-05 14:45:59 +02:00
hrj 41afb10cb5 update version in sbt 2022-04-05 10:41:15 +05:30
hrj e1a88735e3 update test config to match newest config options 2022-04-05 10:15:21 +05:30
hrj 9b56759c80 match size in demo to size in default config 2022-04-05 10:14:28 +05:30
hrj 8df6039a7d define maxAttemptsRatio instead of maxAttempts 2022-04-05 10:13:58 +05:30
hrj a4df7a346d rename throttle to `bufferCount` in captcha fields 2022-04-05 10:13:25 +05:30
hrj 5218653f56 shadow text: center text 2022-04-05 10:12:38 +05:30
hrj 8c38ba2a21 minor cleanup 2022-04-05 09:54:03 +05:30
hrj cd6c8d790a renamed throttle config option to `bufferCount` 2022-04-05 09:53:58 +05:30
hrj 44432cbd5a Update `size` description 2022-04-04 21:47:49 +05:30
hrj c71fdbb8de
Merge pull request #153 from librecaptcha/fix98SizeParam
Respect size param
2022-04-04 21:42:11 +05:30
hrj 7ef308e556 tests: specify size parameter 2022-04-04 20:30:28 +05:30
hrj e26bd32b2f set size in test config 2022-04-04 20:28:13 +05:30
hrj cbb8abd352 functional tests: use new api path 2022-04-04 20:27:35 +05:30
hrj ba796fddf7 locust tests: use new api path 2022-04-04 19:48:28 +05:30
hrj c809872b8d Merge remote-tracking branch 'origin/master' into fix98SizeParam 2022-04-04 19:27:04 +05:30
hrj caf03d7a48 raindrops: vary length based on level, and use safe alphaNumeric characters 2022-04-04 11:13:25 +05:30
hrj 3aeb258851 rain drops: scale text to fit image size 2022-04-04 11:07:50 +05:30
hrj f8626e3670 shadow text: adapt text to size dynamically 2022-04-03 23:22:45 +05:30
hrj 83b0eb069e shadow text: dynamic kernel size 2022-04-03 23:09:45 +05:30
hrj 832053f6e9 minor, simplification 2022-04-03 23:01:40 +05:30
hrj 6480da09ff helper setRenderingHints: set antialiasing on 2022-04-03 23:01:26 +05:30
hrj 23a6a43d2d shadow text: simplify text drawing 2022-04-03 22:58:12 +05:30
hrj 7f77f819dd popping characters: adjust height to size 2022-04-03 22:49:35 +05:30
hrj bfc7174e2a FilterChallenge: use bold font 2022-04-03 16:45:53 +05:30
hrj 0c48f8fbd1 filter challenge: ensure that text fits within image width 2022-04-03 16:45:33 +05:30
hrj 595815921f
Merge pull request #152 from scala-steward/update/scalafmt-core-3.5.0
Update scalafmt-core to 3.5.0
2022-04-03 07:15:01 +05:30
hrj b33dd8adcf filter challenge: adjust filter effect based on difficult level 2022-04-02 23:18:46 +05:30
hrj d797acdcac FilterChallenge: adjust difficult based on parameter 2022-04-02 23:07:38 +05:30
hrj 444982e65f added HelperFunctions.safeAlphaNum 2022-04-02 23:06:57 +05:30
Scala Steward c396fe5c4a
Reformat with scalafmt 3.5.0 2022-04-02 19:26:52 +02:00
Scala Steward 2ee331e6b5
Update scalafmt-core to 3.5.0 2022-04-02 19:26:44 +02:00
hrj cc8addb0c7 filter challenge: scale font height based on image size 2022-04-02 22:51:45 +05:30
hrj d7c1f9a4cc handle exceptions thrown by captcha provider 2022-04-02 22:51:19 +05:30
hrj 07cce460b7
Merge pull request #151 from Korkman/patch-2
Point docker-compose.yml to "latest", add "build"
2022-03-02 17:50:52 +05:30
Korkman 7da5a5b9af
Quotes 2022-03-02 12:57:34 +01:00
Korkman 9f069e1402
Commented 2022-03-02 12:57:12 +01:00
hrj 70f8e42d93
Merge pull request #150 from Korkman/patch-1
Bump version numbers, fixes #149
2022-03-02 17:04:14 +05:30
Korkman c3eb078635
Point docker-compose.yml to "latest"
More sane to point docker-compose.yml to "latest" instead of ageing version numbers. Also added commented-out build option which should be obvious but still helps.
2022-03-02 12:25:24 +01:00
Korkman 8f34d58521
Bump version numbers, fixes #149 2022-03-02 12:19:00 +01:00
hrj 4f3bec0bc6 each challenge provider now respects the size parameter when construction image 2022-03-01 23:03:43 +05:30
hrj edd211fb52 respect size and level parameters
* Add size and level parameters to ChallengeProvider.returnChallenge()
* Add size column to challenge table
* Pass size and level parameters to all relevant calls
* Add size field to demo app
2022-03-01 22:51:38 +05:30
hrj f0659da3eb add a welcome message to root path 2022-03-01 19:11:13 +05:30
hrj c8db914cfd minor simplification 2022-03-01 17:21:30 +05:30
hrj a54947cfc2 shutdown server gracefully 2022-03-01 13:23:29 +05:30
hrj 2d9fd3c68e update h2 db to 2.1.210 2022-03-01 13:21:06 +05:30
hrj 226d3bfa74
Merge pull request #148 from librecaptcha/fix145
Fix for #145: frequent repetition of CAPTCHA challenge
2022-03-01 11:12:55 +05:30
hrj cb0c244779 in background task, divide total required by number of combinations, and create in multiple iterations
Signed-off-by: hrj <harshad.rj@gmail.com>
2022-03-01 09:37:33 +05:30
hrj 8316084ee7 create equal number of captchas for each parameter combination in background task
Signed-off-by: hrj <harshad.rj@gmail.com>
2022-03-01 09:37:33 +05:30
hrj e44a1df22e select random captchas
Signed-off-by: hrj <harshad.rj@gmail.com>
2022-03-01 09:37:33 +05:30
hrj d2a4a92463
Merge pull request #147 from scala-steward/update/scrimage-core-4.0.31
Update scrimage-core, scrimage-filters to 4.0.31
2022-02-27 15:21:03 +05:30
Scala Steward d3056699eb
Update scrimage-core, scrimage-filters to 4.0.31 2022-02-27 00:19:16 +01:00
hrj 3b6a09649d
Merge pull request #144 from librecaptcha/simplification
Simplification and minor refactoring
2022-02-24 20:48:12 +05:30
40 changed files with 477 additions and 261 deletions

5
.git-blame-ignore-revs Normal file
View File

@ -0,0 +1,5 @@
# Scala Steward: Reformat with sbt-java-formatter 0.8.0
57ce691a00babb03e0cae03a26fe56d63fc609af
# Scala Steward: Reformat with scalafmt 3.6.1
f2b19baca828a4d88b46bc009aef6d7115e63924

3
.github/scala-steward.conf vendored Normal file
View File

@ -0,0 +1,3 @@
# If true, Scala Steward will sign off all commits (e.g. `git --signoff`).
# Default: false
signoffCommits = true

View File

@ -16,6 +16,7 @@ jobs:
uses: actions/setup-java@v1 uses: actions/setup-java@v1
with: with:
java-version: 1.11 java-version: 1.11
- uses: sbt/setup-sbt@v1
- name: Run tests - name: Run tests
run: sbt test assembly run: sbt test assembly
- name: Run linter - name: Run linter

View File

@ -54,6 +54,10 @@ jobs:
push: ${{ github.event_name != 'pull_request' }} push: ${{ github.event_name != 'pull_request' }}
tags: ${{ steps.meta.outputs.tags }} tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }} labels: ${{ steps.meta.outputs.labels }}
platforms: |
linux/amd64
linux/arm64
linux/arm/v7
- name: Image digest - name: Image digest
run: echo ${{ steps.docker_build.outputs.digest }} run: echo ${{ steps.docker_build.outputs.digest }}

View File

@ -1,3 +1,3 @@
version="3.4.3" version="3.8.3"
maxColumn = 120 maxColumn = 120
runner.dialect = scala3 runner.dialect = scala3

View File

@ -1,9 +1,10 @@
FROM adoptopenjdk/openjdk16:alpine AS base-builder FROM eclipse-temurin:17-jre-jammy AS base-builder
ARG SBT_VERSION=1.3.13 ARG SBT_VERSION=1.7.1
RUN apk add --no-cache bash
ENV JAVA_HOME="/usr/lib/jvm/default-jvm/" ENV JAVA_HOME="/usr/lib/jvm/default-jvm/"
ENV PATH=$PATH:${JAVA_HOME}/bin ENV PATH=$PATH:${JAVA_HOME}/bin
RUN \ RUN \
apt update && \
apt install -y wget && \
wget -O sbt-$SBT_VERSION.tgz https://github.com/sbt/sbt/releases/download/v$SBT_VERSION/sbt-$SBT_VERSION.tgz && \ wget -O sbt-$SBT_VERSION.tgz https://github.com/sbt/sbt/releases/download/v$SBT_VERSION/sbt-$SBT_VERSION.tgz && \
tar -xzvf sbt-$SBT_VERSION.tgz && \ tar -xzvf sbt-$SBT_VERSION.tgz && \
rm sbt-$SBT_VERSION.tgz rm sbt-$SBT_VERSION.tgz
@ -22,15 +23,15 @@ FROM sbt-builder as builder
COPY src/ src/ COPY src/ src/
RUN sbt assembly RUN sbt assembly
FROM adoptopenjdk/openjdk16:alpine-jre AS base-core FROM eclipse-temurin:17-jre-jammy AS base-core
ENV JAVA_HOME="/usr/lib/jvm/default-jvm/" ENV JAVA_HOME="/usr/lib/jvm/default-jvm/"
RUN apk add --update ttf-dejavu RUN apt update && apt install -y fonts-dejavu
ENV PATH=$PATH:${JAVA_HOME}/bin ENV PATH=$PATH:${JAVA_HOME}/bin
FROM base-core FROM base-core
WORKDIR /lc-core WORKDIR /lc-core
COPY --from=builder /build/target/scala-2.13/LibreCaptcha.jar . COPY --from=builder /build/target/scala-3.6.2/LibreCaptcha.jar .
RUN mkdir data/ RUN mkdir data/
EXPOSE 8888 EXPOSE 8888

View File

@ -47,18 +47,25 @@ docker-compose up
Using `docker`: Using `docker`:
``` ```
docker run -v lcdata:/lc-core/data librecaptcha/lc-core:1.1.0-stable docker run -p=8888:8888 -v ./lcdata:/lc-core/data librecaptcha/lc-core:2.0
``` ```
A default `config.json` is automatically created in the mounted volume. A default `config.json` is automatically created in the mounted volume.
The above commands should work with `podman` as well, if docker.io registry is pre-configured. Otherwise,
you can manually specify the repository like so:
```
podman run -p=8888:8888 -v ./lcdata:/lc-core/data docker.io/librecaptcha/lc-core:2.0
```
## Quick test ## Quick test
Open [localhost:8888/demo/index.html](http://localhost:8888/demo/index.html) in browser. Open [localhost:8888/demo/index.html](http://localhost:8888/demo/index.html) in browser.
Alternatively, on the command line, try: Alternatively, on the command line, try:
``` ```
> $ curl -d '{"media":"image/png","level":"easy","input_type":"text"}' localhost:8888/v1/captcha > $ curl -d '{"media":"image/png","level":"easy","input_type":"text","size":"350x100"}' localhost:8888/v2/captcha
{"id":"3bf928ce-a1e7-4616-b34f-8252d777855d"} {"id":"3bf928ce-a1e7-4616-b34f-8252d777855d"}
> $ curl "localhost:8888/v1/media?id=3bf928ce-a1e7-4616-b34f-8252d777855d" -o sample.png > $ curl "localhost:8888/v1/media?id=3bf928ce-a1e7-4616-b34f-8252d777855d" -o sample.png
@ -139,10 +146,10 @@ The service can be accessed using a simple HTTP API.
- image/png - image/png
- image/gif - image/gif
- (More to come) - (More to come)
- `size`: `Map` - - `size`: String -
The dimensions of a captcha (Optional). It needs two more fields nested in this parameter The dimensions of a captcha. It needs to be a string in the format `"widthxheight"` in pixels, and will be matched
- `height`: `Int` with the `allowedSizes` config setting. Example: `size: "450x200"` which requests an image of width 450 and height
- `width`: `Int` 200 pixels.
- Returns: - Returns:
- `id`: `String` - The uuid of the captcha generated - `id`: `String` - The uuid of the captcha generated
@ -173,9 +180,9 @@ The service can be accessed using a simple HTTP API.
In javascript: In javascript:
```js ```js
const resp = await fetch("/v1/captcha", { const resp = await fetch("/v2/captcha", {
method: 'POST', method: 'POST',
body: JSON.stringify({level: "easy", media: "image/png", "input_type" : "text"}) body: JSON.stringify({level: "easy", media: "image/png", "input_type" : "text", size: "350x100"})
}) })
const respJson = await resp.json(); const respJson = await resp.json();
@ -193,7 +200,7 @@ if (resp.ok) {
// When user submits an answer it can be sent to the server for verification thusly: // When user submits an answer it can be sent to the server for verification thusly:
const resp = await fetch("/v1/answer", { const resp = await fetch("/v2/answer", {
method: 'POST', method: 'POST',
body: JSON.stringify({id: captchaId, answer: "user input"}) body: JSON.stringify({id: captchaId, answer: "user input"})
}); });

View File

@ -1,12 +1,12 @@
FROM adoptopenjdk/openjdk16:alpine-jre AS base-core FROM eclipse-temurin:17-jre-jammy AS base-core
ENV JAVA_HOME="/usr/lib/jvm/default-jvm/" ENV JAVA_HOME="/usr/lib/jvm/default-jvm/"
RUN apk add --update ttf-dejavu RUN apt update && apt install -y fonts-dejavu
ENV PATH=$PATH:${JAVA_HOME}/bin ENV PATH=$PATH:${JAVA_HOME}/bin
FROM base-core FROM base-core
RUN mkdir /lc-core RUN mkdir /lc-core
COPY target/scala-3.1.1/LibreCaptcha.jar /lc-core COPY target/scala-3.6.2/LibreCaptcha.jar /lc-core
WORKDIR /lc-core WORKDIR /lc-core
RUN mkdir data/ RUN mkdir data/

View File

@ -2,8 +2,8 @@ lazy val root = (project in file(".")).settings(
inThisBuild( inThisBuild(
List( List(
organization := "com.example", organization := "com.example",
scalaVersion := "3.1.1", scalaVersion := "3.6.2",
version := "0.1.0-SNAPSHOT", version := "0.2.1-snapshot",
semanticdbEnabled := true, semanticdbEnabled := true,
semanticdbVersion := scalafixSemanticdb.revision semanticdbVersion := scalafixSemanticdb.revision
@ -12,9 +12,9 @@ lazy val root = (project in file(".")).settings(
) )
), ),
name := "LibreCaptcha", name := "LibreCaptcha",
libraryDependencies += "com.sksamuel.scrimage" % "scrimage-core" % "4.0.30", libraryDependencies += "com.sksamuel.scrimage" % "scrimage-core" % "4.3.0",
libraryDependencies += "com.sksamuel.scrimage" % "scrimage-filters" % "4.0.30", libraryDependencies += "com.sksamuel.scrimage" % "scrimage-filters" % "4.3.0",
libraryDependencies += "org.json4s" %% "json4s-jackson" % "4.0.4" libraryDependencies += "org.json4s" %% "json4s-jackson" % "4.0.7"
) )
Compile / unmanagedResourceDirectories += { baseDirectory.value / "lib" } Compile / unmanagedResourceDirectories += { baseDirectory.value / "lib" }

View File

@ -3,7 +3,9 @@ version: "3.6"
services: services:
lc-core: lc-core:
container_name: "libre-captcha" container_name: "libre-captcha"
image: librecaptcha/lc-core:1.0.0-stable image: librecaptcha/lc-core:latest
# Comment "image" & uncomment "build" if you intend to build from source
#build: .
volumes: volumes:
- "./docker-data:/lc-core/data" - "./docker-data:/lc-core/data"
ports: ports:

View File

@ -1 +1 @@
sbt.version=1.6.2 sbt.version=1.10.6

View File

@ -1,4 +1,4 @@
addSbtPlugin("ch.epfl.scala" % "sbt-scalafix" % "0.9.34") addSbtPlugin("ch.epfl.scala" % "sbt-scalafix" % "0.13.0")
addSbtPlugin("org.scalameta" % "sbt-scalafmt" % "2.4.6") addSbtPlugin("org.scalameta" % "sbt-scalafmt" % "2.5.2")
addSbtPlugin("com.lightbend.sbt" % "sbt-java-formatter" % "0.7.0") addSbtPlugin("com.lightbend.sbt" % "sbt-java-formatter" % "0.8.0")
addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "1.2.0") addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "2.3.0")

View File

@ -5,12 +5,12 @@ import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
import java.io.File; import java.io.File;
import java.io.FilenameFilter; import java.io.FilenameFilter;
import java.util.Map;
import java.util.List; import java.util.List;
import java.util.Map;
import lc.captchas.interfaces.Challenge; import lc.captchas.interfaces.Challenge;
import lc.captchas.interfaces.ChallengeProvider; import lc.captchas.interfaces.ChallengeProvider;
import lc.misc.PngImageWriter;
import lc.misc.HelperFunctions; import lc.misc.HelperFunctions;
import lc.misc.PngImageWriter;
public class FontFunCaptcha implements ChallengeProvider { public class FontFunCaptcha implements ChallengeProvider {
@ -58,9 +58,10 @@ public class FontFunCaptcha implements ChallengeProvider {
return null; return null;
} }
private byte[] fontFun(String captchaText, String level, String path) { private byte[] fontFun(
final int width, final int height, String captchaText, String level, String path) {
String[] colors = {"#f68787", "#f8a978", "#f1eb9a", "#a4f6a5"}; String[] colors = {"#f68787", "#f8a978", "#f1eb9a", "#a4f6a5"};
BufferedImage img = new BufferedImage(350, 100, BufferedImage.TYPE_INT_RGB); BufferedImage img = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
Graphics2D graphics2D = img.createGraphics(); Graphics2D graphics2D = img.createGraphics();
for (int i = 0; i < captchaText.length(); i++) { for (int i = 0; i < captchaText.length(); i++) {
Font font = loadCustomFont(level, path); Font font = loadCustomFont(level, path);
@ -81,10 +82,14 @@ public class FontFunCaptcha implements ChallengeProvider {
return baos.toByteArray(); return baos.toByteArray();
} }
public Challenge returnChallenge() { public Challenge returnChallenge(String level, String size) {
String secret = HelperFunctions.randomString(7); String secret = HelperFunctions.randomString(7);
final int[] size2D = HelperFunctions.parseSize2D(size);
final int width = size2D[0];
final int height = size2D[1];
String path = "./lib/fonts/"; String path = "./lib/fonts/";
return new Challenge(fontFun(secret, "medium", path), "image/png", secret.toLowerCase()); return new Challenge(
fontFun(width, height, secret, "medium", path), "image/png", secret.toLowerCase());
} }
public boolean checkAnswer(String secret, String answer) { public boolean checkAnswer(String secret, String answer) {

View File

@ -1,51 +1,51 @@
package lc.captchas; package lc.captchas;
import java.awt.Font;
import java.awt.RenderingHints;
import java.awt.Color; import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics2D; import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.image.BufferedImage; import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.io.IOException; import java.io.IOException;
import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.function.Consumer; import java.util.function.Consumer;
import java.util.stream.IntStream; import java.util.stream.IntStream;
import java.util.LinkedList;
import java.util.List;
import javax.imageio.stream.MemoryCacheImageOutputStream; import javax.imageio.stream.MemoryCacheImageOutputStream;
import java.io.ByteArrayOutputStream;
import lc.captchas.interfaces.Challenge; import lc.captchas.interfaces.Challenge;
import lc.captchas.interfaces.ChallengeProvider; import lc.captchas.interfaces.ChallengeProvider;
import lc.misc.HelperFunctions;
import lc.misc.GifSequenceWriter; import lc.misc.GifSequenceWriter;
import lc.misc.HelperFunctions;
public class PoppingCharactersCaptcha implements ChallengeProvider { public class PoppingCharactersCaptcha implements ChallengeProvider {
private final Font font = new Font("Arial", Font.ROMAN_BASELINE, 48);
private final int width = 250;
private final int height = 100;
private Integer[] computeOffsets(final String text) { private int[] computeOffsets(
final Font font, final int width, final int height, final String text) {
final var img = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB); final var img = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
final var graphics2D = img.createGraphics(); final var graphics2D = img.createGraphics();
final var frc = graphics2D.getFontRenderContext(); final var frc = graphics2D.getFontRenderContext();
final var advances = new LinkedList<Integer>(); final var advances = new int[text.length() + 1];
final var spacing = font.getStringBounds(" ", frc).getWidth() / 3; final var spacing = font.getStringBounds(" ", frc).getWidth() / 3;
var currX = 0; var currX = 0;
for (int i = 0; i < text.length(); i++) { for (int i = 0; i < text.length(); i++) {
final var c = text.charAt(i); final var c = text.charAt(i);
advances.add(currX); advances[i] = currX;
currX += font.getStringBounds(String.valueOf(c), frc).getWidth(); currX += font.getStringBounds(String.valueOf(c), frc).getWidth();
currX += spacing; currX += spacing;
}; }
;
advances[text.length()] = currX;
graphics2D.dispose(); graphics2D.dispose();
return advances.toArray(new Integer[]{}); return advances;
} }
private BufferedImage makeImage(final Consumer<Graphics2D> f) { private BufferedImage makeImage(
final Font font, final int width, final int height, final Consumer<Graphics2D> f) {
final var img = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB); final var img = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
final var graphics2D = img.createGraphics(); final var 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);
f.accept(graphics2D); f.accept(graphics2D);
graphics2D.dispose(); graphics2D.dispose();
@ -56,23 +56,42 @@ public class PoppingCharactersCaptcha implements ChallengeProvider {
return HelperFunctions.randomNumber(-2, +2); return HelperFunctions.randomNumber(-2, +2);
} }
private byte[] gifCaptcha(final String text) { private byte[] gifCaptcha(final int width, final int height, final String text) {
try { try {
final var fontHeight = (int) (height * 0.5);
final Font font = new Font("Arial", Font.ROMAN_BASELINE, fontHeight);
final var byteArrayOutputStream = new ByteArrayOutputStream(); final var byteArrayOutputStream = new ByteArrayOutputStream();
final var output = new MemoryCacheImageOutputStream(byteArrayOutputStream); final var output = new MemoryCacheImageOutputStream(byteArrayOutputStream);
final var writer = new GifSequenceWriter(output, 1, 900, true); final var writer = new GifSequenceWriter(output, 1, 900, true);
final var advances = computeOffsets(text); final var advances = computeOffsets(font, width, height, text);
final var expectedWidth = advances[advances.length - 1];
final var scale = width / (float) expectedWidth;
final var prevColor = Color.getHSBColor(0f, 0f, 0.1f); final var prevColor = Color.getHSBColor(0f, 0f, 0.1f);
IntStream.range(0, text.length()).forEach(i -> { IntStream.range(0, text.length())
final var color = Color.getHSBColor(HelperFunctions.randomNumber(0, 100)/100.0f, 0.6f, 1.0f); .forEach(
final var nextImage = makeImage((g) -> { i -> {
final var color =
Color.getHSBColor(HelperFunctions.randomNumber(0, 100) / 100.0f, 0.6f, 1.0f);
final var nextImage =
makeImage(
font,
width,
height,
(g) -> {
g.scale(scale, 1);
if (i > 0) { if (i > 0) {
final var prevI = (i - 1) % text.length(); final var prevI = (i - 1) % text.length();
g.setColor(prevColor); g.setColor(prevColor);
g.drawString(String.valueOf(text.charAt(prevI)), advances[prevI] + jitter(), 45 + jitter()); g.drawString(
String.valueOf(text.charAt(prevI)),
advances[prevI] + jitter(),
fontHeight * 1.1f + jitter());
} }
g.setColor(color); g.setColor(color);
g.drawString(String.valueOf(text.charAt(i)), advances[i] + jitter(), 45 + jitter()); g.drawString(
String.valueOf(text.charAt(i)),
advances[i] + jitter(),
fontHeight * 1.1f + jitter());
}); });
try { try {
writer.writeToSequence(nextImage); writer.writeToSequence(nextImage);
@ -100,9 +119,12 @@ public class PoppingCharactersCaptcha implements ChallengeProvider {
"supportedInputType", List.of("text")); "supportedInputType", List.of("text"));
} }
public Challenge returnChallenge() { public Challenge returnChallenge(String level, String size) {
final var secret = HelperFunctions.randomString(6); final var secret = HelperFunctions.randomString(6);
return new Challenge(gifCaptcha(secret), "image/gif", secret.toLowerCase()); final int[] size2D = HelperFunctions.parseSize2D(size);
final int width = size2D[0];
final int height = size2D[1];
return new Challenge(gifCaptcha(width, height, secret), "image/gif", secret.toLowerCase());
} }
public boolean checkAnswer(String secret, String answer) { public boolean checkAnswer(String secret, String answer) {

View File

@ -1,21 +1,18 @@
package lc.captchas; package lc.captchas;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.Color; import java.awt.Color;
import java.awt.Font; import java.awt.Font;
import java.awt.font.TextLayout; import java.awt.Graphics2D;
import java.awt.image.BufferedImage; import java.awt.image.BufferedImage;
import java.awt.image.ConvolveOp; import java.awt.image.ConvolveOp;
import java.awt.image.Kernel; import java.awt.image.Kernel;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
import java.util.Map;
import java.util.List; import java.util.List;
import java.util.Map;
import lc.misc.HelperFunctions;
import lc.misc.PngImageWriter;
import lc.captchas.interfaces.Challenge; import lc.captchas.interfaces.Challenge;
import lc.captchas.interfaces.ChallengeProvider; import lc.captchas.interfaces.ChallengeProvider;
import lc.misc.HelperFunctions;
import lc.misc.PngImageWriter;
public class ShadowTextCaptcha implements ChallengeProvider { public class ShadowTextCaptcha implements ChallengeProvider {
@ -38,32 +35,43 @@ public class ShadowTextCaptcha implements ChallengeProvider {
return answer.toLowerCase().equals(secret); return answer.toLowerCase().equals(secret);
} }
private byte[] shadowText(String text) { private float[] makeKernel(int size) {
BufferedImage img = new BufferedImage(350, 100, BufferedImage.TYPE_INT_RGB); final int N = size * size;
Font font = new Font("Arial", Font.ROMAN_BASELINE, 48); final float weight = 1.0f / (N);
Graphics2D graphics2D = img.createGraphics(); final float[] kernel = new float[N];
graphics2D.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); java.util.Arrays.fill(kernel, weight);
graphics2D.setRenderingHint( return kernel;
RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON); };
TextLayout textLayout = new TextLayout(text, font, graphics2D.getFontRenderContext()); private byte[] shadowText(final int width, final int height, String text) {
BufferedImage img = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
final int fontHeight = (int) (height * 0.5f);
Font font = new Font("Arial", Font.PLAIN, fontHeight);
Graphics2D graphics2D = img.createGraphics();
HelperFunctions.setRenderingHints(graphics2D); HelperFunctions.setRenderingHints(graphics2D);
graphics2D.setPaint(Color.WHITE); graphics2D.setPaint(Color.WHITE);
graphics2D.fillRect(0, 0, 350, 100); graphics2D.fillRect(0, 0, width, height);
graphics2D.setPaint(Color.BLACK); graphics2D.setPaint(Color.BLACK);
textLayout.draw(graphics2D, 15, 50); graphics2D.setFont(font);
final var stringWidth = graphics2D.getFontMetrics().stringWidth(text);
final var padding = (stringWidth > width) ? 0 : (width - stringWidth) / 2;
final var scaleX = (stringWidth > width) ? width / ((double) stringWidth) : 1d;
graphics2D.scale(scaleX, 1d);
graphics2D.drawString(text, padding, fontHeight * 1.1f);
graphics2D.dispose(); graphics2D.dispose();
float[] kernel = { final int kernelSize = (int) Math.ceil((Math.min(width, height) / 50.0));
1f / 9f, 1f / 9f, 1f / 9f, ConvolveOp op =
1f / 9f, 1f / 9f, 1f / 9f, new ConvolveOp(
1f / 9f, 1f / 9f, 1f / 9f new Kernel(kernelSize, kernelSize, makeKernel(kernelSize)),
}; ConvolveOp.EDGE_NO_OP,
ConvolveOp op = new ConvolveOp(new Kernel(3, 3, kernel), ConvolveOp.EDGE_NO_OP, null); 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);
g2d.setPaint(Color.WHITE); g2d.setPaint(Color.WHITE);
textLayout.draw(g2d, 13, 50); g2d.scale(scaleX, 1d);
g2d.setFont(font);
g2d.drawString(text, padding - kernelSize, fontHeight * 1.1f);
g2d.dispose(); g2d.dispose();
ByteArrayOutputStream baos = new ByteArrayOutputStream(); ByteArrayOutputStream baos = new ByteArrayOutputStream();
try { try {
@ -74,8 +82,11 @@ public class ShadowTextCaptcha implements ChallengeProvider {
return baos.toByteArray(); return baos.toByteArray();
} }
public Challenge returnChallenge() { public Challenge returnChallenge(String level, String size) {
String secret = HelperFunctions.randomString(6); String secret = HelperFunctions.randomString(6);
return new Challenge(shadowText(secret), "image/png", secret.toLowerCase()); final int[] size2D = HelperFunctions.parseSize2D(size);
final int width = size2D[0];
final int height = size2D[1];
return new Challenge(shadowText(width, height, secret), "image/png", secret.toLowerCase());
} }
} }

View File

@ -1,12 +1,12 @@
package lc.captchas.interfaces; package lc.captchas.interfaces;
import java.util.Map;
import java.util.List; import java.util.List;
import java.util.Map;
public interface ChallengeProvider { public interface ChallengeProvider {
public String getId(); public String getId();
public Challenge returnChallenge(); public Challenge returnChallenge(String level, String size);
public boolean checkAnswer(String secret, String answer); public boolean checkAnswer(String secret, String answer);

View File

@ -3,12 +3,12 @@
package lc.misc; package lc.misc;
import javax.imageio.*;
import javax.imageio.metadata.*;
import javax.imageio.stream.*;
import java.awt.image.*; import java.awt.image.*;
import java.io.*; import java.io.*;
import java.util.Iterator; import java.util.Iterator;
import javax.imageio.*;
import javax.imageio.metadata.*;
import javax.imageio.stream.*;
public class GifSequenceWriter { public class GifSequenceWriter {
protected ImageWriter gifWriter; protected ImageWriter gifWriter;

View File

@ -11,7 +11,14 @@ public class HelperFunctions {
random.setSeed(seed); random.setSeed(seed);
} }
public static int[] parseSize2D(final String size) {
final String[] fields = size.split("x");
final int[] result = {Integer.parseInt(fields[0]), Integer.parseInt(fields[1])};
return result;
}
public static void setRenderingHints(Graphics2D g2d) { public static void setRenderingHints(Graphics2D g2d) {
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2d.setRenderingHint( g2d.setRenderingHint(
RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON); RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
g2d.setRenderingHint( g2d.setRenderingHint(
@ -23,7 +30,8 @@ public class HelperFunctions {
public static final String safeNumbers = "23456789"; public static final String safeNumbers = "23456789";
public static final String allNumbers = safeNumbers + "10"; public static final String allNumbers = safeNumbers + "10";
public static final String specialCharacters = "$#%@&?"; public static final String specialCharacters = "$#%@&?";
public static final String safeCharacters = safeAlphabets + safeNumbers + specialCharacters; public static final String safeAlphaNum = safeAlphabets + safeNumbers;
public static final String safeCharacters = safeAlphaNum + specialCharacters;
public static String randomString(final int n) { public static String randomString(final int n) {
return randomString(n, safeCharacters); return randomString(n, safeCharacters);

View File

@ -1,9 +1,9 @@
package lc.misc; package lc.misc;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
import java.io.IOException; import java.io.IOException;
import java.util.Iterator; import java.util.Iterator;
import javax.imageio.IIOImage; import javax.imageio.IIOImage;
import javax.imageio.ImageIO; import javax.imageio.ImageIO;
import javax.imageio.ImageTypeSpecifier; import javax.imageio.ImageTypeSpecifier;
@ -13,7 +13,6 @@ import javax.imageio.metadata.IIOInvalidTreeException;
import javax.imageio.metadata.IIOMetadata; import javax.imageio.metadata.IIOMetadata;
import javax.imageio.metadata.IIOMetadataNode; import javax.imageio.metadata.IIOMetadataNode;
import javax.imageio.stream.ImageOutputStream; import javax.imageio.stream.ImageOutputStream;
import java.awt.image.BufferedImage;
public class PngImageWriter { public class PngImageWriter {
@ -26,7 +25,8 @@ public class PngImageWriter {
iw.hasNext(); ) { iw.hasNext(); ) {
ImageWriter writer = iw.next(); ImageWriter writer = iw.next();
ImageWriteParam writeParam = writer.getDefaultWriteParam(); ImageWriteParam writeParam = writer.getDefaultWriteParam();
ImageTypeSpecifier typeSpecifier = ImageTypeSpecifier.createFromBufferedImageType(BufferedImage.TYPE_INT_RGB); ImageTypeSpecifier typeSpecifier =
ImageTypeSpecifier.createFromBufferedImageType(BufferedImage.TYPE_INT_RGB);
IIOMetadata metadata = writer.getDefaultImageMetadata(typeSpecifier, writeParam); IIOMetadata metadata = writer.getDefaultImageMetadata(typeSpecifier, writeParam);
if (metadata.isReadOnly() || !metadata.isStandardMetadataFormatSupported()) { if (metadata.isReadOnly() || !metadata.isStandardMetadataFormatSupported()) {
continue; continue;

View File

@ -3,21 +3,21 @@
package org.limium.picoserve; package org.limium.picoserve;
import java.net.InetSocketAddress; import com.sun.net.httpserver.HttpExchange;
import com.sun.net.httpserver.HttpHandler;
import com.sun.net.httpserver.HttpServer;
import java.io.IOException; import java.io.IOException;
import java.util.List; import java.net.InetSocketAddress;
import java.util.Map;
import java.util.LinkedList;
import java.util.concurrent.Executor;
import java.util.regex.Pattern;
import java.util.Arrays;
import java.util.Optional;
import java.util.stream.Collectors;
import java.net.URLDecoder; import java.net.URLDecoder;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import com.sun.net.httpserver.HttpServer; import java.util.Arrays;
import com.sun.net.httpserver.HttpHandler; import java.util.LinkedList;
import com.sun.net.httpserver.HttpExchange; import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.Executor;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
public final class Server { public final class Server {
private final HttpServer server; private final HttpServer server;

View File

@ -20,9 +20,10 @@
const levelInput = document.getElementById("levelInput").value const levelInput = document.getElementById("levelInput").value
const mediaInput = document.getElementById("mediaInput").value const mediaInput = document.getElementById("mediaInput").value
const typeInput = document.getElementById("typeInput").value const typeInput = document.getElementById("typeInput").value
fetch("/v1/captcha", { const sizeInput = document.getElementById("sizeInput").value
fetch("/v2/captcha", {
method: 'POST', 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) { }).then(async function(resp) {
const respJson = await resp.json() const respJson = await resp.json()
if (resp.ok) { if (resp.ok) {
@ -30,7 +31,7 @@
const resultDiv = document.getElementById("result") const resultDiv = document.getElementById("result")
const result = ` const result = `
<p>Id: ${id}</p> <p>Id: ${id}</p>
<p><img src="/v1/media?id=${id}" /> </p> <p><img src="/v2/media?id=${id}" /> </p>
<input type="text" id="answerInput" /> <input type="text" id="answerInput" />
<button onClick="submitAnswer('${id}')">Submit</button> <button onClick="submitAnswer('${id}')">Submit</button>
<div id="answerResult" /> <div id="answerResult" />
@ -43,7 +44,7 @@
} }
async function submitAnswer(id) { async function submitAnswer(id) {
const ans = document.getElementById("answerInput").value; const ans = document.getElementById("answerInput").value;
const resp = await fetch("/v1/answer", { const resp = await fetch("/v2/answer", {
method: 'POST', method: 'POST',
body: JSON.stringify({id: id, answer: ans}) body: JSON.stringify({id: id, answer: ans})
}) })
@ -70,6 +71,10 @@
<span>Input Type</span> <span>Input Type</span>
<input type="text" id="typeInput" value="text" /> <input type="text" id="typeInput" value="text" />
</div> </div>
<div class="inputGroup">
<span>Input Size</span>
<input type="text" id="sizeInput" value="350x100" />
</div>
<div class="inputGroup"> <div class="inputGroup">
<button onClick="loadCaptcha()">Get New CAPTCHA</button> <button onClick="loadCaptcha()">Get New CAPTCHA</button>
</div> </div>

View File

@ -25,6 +25,14 @@ object LCFramework {
playgroundEnabled = config.playgroundEnabled, playgroundEnabled = config.playgroundEnabled,
corsHeader = config.corsHeader corsHeader = config.corsHeader
) )
Runtime.getRuntime.addShutdownHook(new Thread {
override def run(): Unit = {
println("Shutting down gracefully...")
backgroundTask.shutdown()
}
})
server.start() server.start()
} }
} }

View File

@ -18,38 +18,73 @@ class BackgroundTask(config: Config, captchaManager: CaptchaManager) {
val challengeGCPstmt = Statements.tlStmts.get.challengeGCPstmt val challengeGCPstmt = Statements.tlStmts.get.challengeGCPstmt
challengeGCPstmt.executeUpdate() challengeGCPstmt.executeUpdate()
val imageNumResult = Statements.tlStmts.get.getCountChallengeTable.executeQuery() val allCombinations = allParameterCombinations()
val imageNum = if (imageNumResult.next()) { val requiredCountPerCombination = Math.max(1, (config.bufferCount * 1.01) / allCombinations.size).toInt
imageNumResult.getInt("total")
} else { for (param <- allCombinations) {
0 if (!shutdownInProgress) {
val countExisting = captchaManager.getCount(param).getOrElse(0)
val countRequired = requiredCountPerCombination - countExisting
if (countRequired > 0) {
val countCreate = Math.min(1.0 + requiredCountPerCombination / 10.0, countRequired).toInt
println(s"Creating $countCreate of $countRequired captchas for $param")
for (i <- 0 until countCreate) {
if (!shutdownInProgress) {
captchaManager.generateChallenge(param)
}
}
}
} }
val throttle = (config.throttle * 1.1).toInt - imageNum
for (i <- 0 until throttle) {
captchaManager.generateChallenge(getRandomParam())
} }
} catch { case exception: Exception => println(exception) } } catch { case exception: Exception => println(exception) }
} }
} }
private def allParameterCombinations(): List[Parameters] = {
(config.captchaConfig).flatMap { captcha =>
(captcha.allowedLevels).flatMap { level =>
(captcha.allowedMedia).flatMap { media =>
(captcha.allowedInputType).flatMap { inputType =>
(captcha.allowedSizes).map { size =>
Parameters(level, media, inputType, size)
}
}
}
}
}
}
private def getRandomParam(): Parameters = { private def getRandomParam(): Parameters = {
val captcha = pickRandom(config.captchaConfig) val captcha = pickRandom(config.captchaConfig)
val level = pickRandom(captcha.allowedLevels) val level = pickRandom(captcha.allowedLevels)
val media = pickRandom(captcha.allowedMedia) val media = pickRandom(captcha.allowedMedia)
val inputType = pickRandom(captcha.allowedInputType) 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 = { private def pickRandom[T](list: List[T]): T = {
list(HelperFunctions.randomNumber(list.size)) list(HelperFunctions.randomNumber(list.size))
} }
private val ex = new ScheduledThreadPoolExecutor(1)
def beginThread(delay: Int): Unit = { def beginThread(delay: Int): Unit = {
val ex = new ScheduledThreadPoolExecutor(1)
ex.scheduleWithFixedDelay(task, 1, delay, TimeUnit.SECONDS) ex.scheduleWithFixedDelay(task, 1, delay, TimeUnit.SECONDS)
} }
@volatile var shutdownInProgress = false
def shutdown(): Unit = {
println(" Shutting down background task...")
shutdownInProgress = true
ex.shutdown()
println(" Finished Shutting background task")
println(" Shutting down DB...")
Statements.tlStmts.get.shutdown.execute()
println(" Finished shutting down db")
}
} }

View File

@ -45,14 +45,14 @@ class DebugCaptcha extends ChallengeProvider {
matches matches
} }
private def simpleText(text: String): Array[Byte] = { private def simpleText(width: Int, height: Int, text: String): Array[Byte] = {
val img = new BufferedImage(350, 100, BufferedImage.TYPE_INT_RGB) val img = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB)
val font = new Font("Arial", Font.ROMAN_BASELINE, 56) val font = new Font("Arial", Font.ROMAN_BASELINE, 56)
val graphics2D = img.createGraphics() val graphics2D = img.createGraphics()
val textLayout = new TextLayout(text, font, graphics2D.getFontRenderContext()) val textLayout = new TextLayout(text, font, graphics2D.getFontRenderContext())
HelperFunctions.setRenderingHints(graphics2D) HelperFunctions.setRenderingHints(graphics2D)
graphics2D.setPaint(Color.WHITE) graphics2D.setPaint(Color.WHITE)
graphics2D.fillRect(0, 0, 350, 100) graphics2D.fillRect(0, 0, width, height)
graphics2D.setPaint(Color.BLACK) graphics2D.setPaint(Color.BLACK)
textLayout.draw(graphics2D, 15, 50) textLayout.draw(graphics2D, 15, 50)
graphics2D.dispose() graphics2D.dispose()
@ -66,8 +66,11 @@ class DebugCaptcha extends ChallengeProvider {
baos.toByteArray() baos.toByteArray()
} }
def returnChallenge(): Challenge = { def returnChallenge(level: String, size: String): Challenge = {
val secret = HelperFunctions.randomString(6, HelperFunctions.safeAlphabets) val secret = HelperFunctions.randomString(6, HelperFunctions.safeAlphabets)
new Challenge(simpleText(secret), "image/png", secret.toLowerCase()) val size2D = HelperFunctions.parseSize2D(size)
val width = size2D(0)
val height = size2D(1)
new Challenge(simpleText(width, height, secret), "image/png", secret.toLowerCase())
} }
} }

View File

@ -10,6 +10,7 @@ import lc.captchas.interfaces.Challenge
import java.util.{List => JavaList, Map => JavaMap} import java.util.{List => JavaList, Map => JavaMap}
import java.io.ByteArrayOutputStream import java.io.ByteArrayOutputStream
import lc.misc.PngImageWriter import lc.misc.PngImageWriter
import lc.misc.HelperFunctions
class FilterChallenge extends ChallengeProvider { class FilterChallenge extends ChallengeProvider {
def getId = "FilterChallenge" def getId = "FilterChallenge"
@ -29,31 +30,38 @@ class FilterChallenge extends ChallengeProvider {
) )
} }
def returnChallenge(): Challenge = { private val filterTypes = List(new FilterType1, new FilterType2)
val filterTypes = List(new FilterType1, new FilterType2)
def returnChallenge(level: String, size: String): Challenge = {
val mediumLevel = level == "medium"
val r = new scala.util.Random val r = new scala.util.Random
val alphabet = "abcdefghijklmnopqrstuvwxyz" val characters = if (mediumLevel) HelperFunctions.safeAlphaNum else HelperFunctions.safeCharacters
val n = 8 val n = if (mediumLevel) 5 else 7
val secret = LazyList.continually(r.nextInt(alphabet.size)).map(alphabet).take(n).mkString val secret = LazyList.continually(r.nextInt(characters.size)).map(characters).take(n).mkString
val canvas = new BufferedImage(225, 50, BufferedImage.TYPE_INT_RGB) val size2D = HelperFunctions.parseSize2D(size)
val width = size2D(0)
val height = size2D(1)
val canvas = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB)
val g = canvas.createGraphics() val g = canvas.createGraphics()
val fontHeight = (height * 0.6).toInt
g.setColor(Color.WHITE) g.setColor(Color.WHITE)
g.fillRect(0, 0, canvas.getWidth, canvas.getHeight) g.fillRect(0, 0, canvas.getWidth, canvas.getHeight)
g.setColor(Color.BLACK) g.setColor(Color.BLACK)
g.setFont(new Font("Serif", Font.PLAIN, 30)) val font = new Font("Serif", Font.BOLD, fontHeight)
g.drawString(secret, 5, 30) g.setFont(font)
val stringWidth = g.getFontMetrics().stringWidth(secret)
val scaleX = if (stringWidth > width) width / (stringWidth.toDouble) else 1d
val margin = if (stringWidth > width) 0 else (width - stringWidth)
val xOffset = (margin * r.nextDouble).toInt
g.scale(scaleX, 1d)
g.drawString(secret, xOffset, fontHeight)
g.dispose() g.dispose()
var image = ImmutableImage.fromAwt(canvas) var image = ImmutableImage.fromAwt(canvas)
val s = scala.util.Random.nextInt(2) val s = r.nextInt(2)
image = filterTypes(s).applyFilter(image) image = filterTypes(s).applyFilter(image, !mediumLevel)
val img = image.awt() val img = image.awt()
val baos = new ByteArrayOutputStream() val baos = new ByteArrayOutputStream()
try {
PngImageWriter.write(baos, img); PngImageWriter.write(baos, img);
} catch {
case e: Exception =>
e.printStackTrace()
}
new Challenge(baos.toByteArray, "image/png", secret) new Challenge(baos.toByteArray, "image/png", secret)
} }
def checkAnswer(secret: String, answer: String): Boolean = { def checkAnswer(secret: String, answer: String): Boolean = {
@ -62,14 +70,15 @@ class FilterChallenge extends ChallengeProvider {
} }
trait FilterType { trait FilterType {
def applyFilter(image: ImmutableImage): ImmutableImage def applyFilter(image: ImmutableImage, hardLevel: Boolean): ImmutableImage
} }
class FilterType1 extends FilterType { class FilterType1 extends FilterType {
override def applyFilter(image: ImmutableImage): ImmutableImage = { override def applyFilter(image: ImmutableImage, hardLevel: Boolean): ImmutableImage = {
val blur = new GaussianBlurFilter(2) val radius = if (hardLevel) 3 else 2
val blur = new GaussianBlurFilter(radius)
val smear = new SmearFilter(com.sksamuel.scrimage.filter.SmearType.Circles, 10, 10, 10, 0, 1) val smear = new SmearFilter(com.sksamuel.scrimage.filter.SmearType.Circles, 10, 10, 10, 0, 1)
val diffuse = new DiffuseFilter(2) val diffuse = new DiffuseFilter(radius.toFloat)
blur.apply(image) blur.apply(image)
diffuse.apply(image) diffuse.apply(image)
smear.apply(image) smear.apply(image)
@ -78,9 +87,10 @@ class FilterType1 extends FilterType {
} }
class FilterType2 extends FilterType { class FilterType2 extends FilterType {
override def applyFilter(image: ImmutableImage): ImmutableImage = { override def applyFilter(image: ImmutableImage, hardLevel: Boolean): ImmutableImage = {
val radius = if (hardLevel) 2f else 1f
val smear = new SmearFilter(com.sksamuel.scrimage.filter.SmearType.Circles, 10, 10, 10, 0, 1) val smear = new SmearFilter(com.sksamuel.scrimage.filter.SmearType.Circles, 10, 10, 10, 0, 1)
val diffuse = new DiffuseFilter(1) val diffuse = new DiffuseFilter(radius)
val ripple = new RippleFilter(com.sksamuel.scrimage.filter.RippleType.Noise, 1, 1, 0.005.toFloat, 0.005.toFloat) val ripple = new RippleFilter(com.sksamuel.scrimage.filter.RippleType.Noise, 1, 1, 0.005.toFloat, 0.005.toFloat)
diffuse.apply(image) diffuse.apply(image)
ripple.apply(image) ripple.apply(image)

View File

@ -40,7 +40,7 @@ class LabelCaptcha extends ChallengeProvider {
) )
} }
def returnChallenge(): Challenge = def returnChallenge(level: String, size: String): Challenge =
synchronized { 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)

View File

@ -11,6 +11,7 @@ import lc.captchas.interfaces.ChallengeProvider
import lc.captchas.interfaces.Challenge import lc.captchas.interfaces.Challenge
import lc.misc.GifSequenceWriter import lc.misc.GifSequenceWriter
import java.util.{List => JavaList, Map => JavaMap} import java.util.{List => JavaList, Map => JavaMap}
import lc.misc.HelperFunctions
class Drop { class Drop {
var x = 0 var x = 0
@ -24,8 +25,6 @@ class Drop {
} }
class RainDropsCP extends ChallengeProvider { class RainDropsCP extends ChallengeProvider {
private val alphabet = "abcdefghijklmnopqrstuvwxyz"
private val n = 6
private val bgColor = new Color(200, 200, 200) private val bgColor = new Color(200, 200, 200)
private val textColor = new Color(208, 208, 218) private val textColor = new Color(208, 208, 218)
private val textHighlightColor = new Color(100, 100, 125) private val textHighlightColor = new Color(100, 100, 125)
@ -56,11 +55,13 @@ class RainDropsCP extends ChallengeProvider {
}) })
} }
def returnChallenge(): Challenge = { def returnChallenge(level: String, size: String): Challenge = {
val r = new scala.util.Random val r = new scala.util.Random
val secret = LazyList.continually(r.nextInt(alphabet.size)).map(alphabet).take(n).mkString val n = if (level == "easy") 4 else 6
val width = 450 val secret = HelperFunctions.randomString(n, HelperFunctions.safeAlphaNum)
val height = 100 val size2D = HelperFunctions.parseSize2D(size)
val width = size2D(0)
val height = size2D(1)
val imgType = BufferedImage.TYPE_INT_RGB val imgType = BufferedImage.TYPE_INT_RGB
val xOffset = 2 + r.nextInt(3) val xOffset = 2 + r.nextInt(3)
val xBias = (height / 10) - 2 val xBias = (height / 10) - 2
@ -80,7 +81,8 @@ class RainDropsCP extends ChallengeProvider {
xOffset xOffset
) )
val baseFont = new Font(Font.MONOSPACED, Font.BOLD, 80) val fontHeight = (height * 0.5f).toInt
val baseFont = new Font(Font.MONOSPACED, Font.BOLD, fontHeight)
val attributes = new java.util.HashMap[TextAttribute, Object]() val attributes = new java.util.HashMap[TextAttribute, Object]()
attributes.put(TextAttribute.TRACKING, Double.box(0.2)) attributes.put(TextAttribute.TRACKING, Double.box(0.2))
attributes.put(TextAttribute.WEIGHT, TextAttribute.WEIGHT_EXTRABOLD) attributes.put(TextAttribute.WEIGHT, TextAttribute.WEIGHT_EXTRABOLD)
@ -117,17 +119,22 @@ class RainDropsCP extends ChallengeProvider {
} }
} }
// center the text
g.setFont(spacedFont) g.setFont(spacedFont)
val textWidth = g.getFontMetrics().charsWidth(secret.toCharArray, 0, secret.toCharArray.length) val textWidth = g.getFontMetrics().stringWidth(secret)
val textX = (width - textWidth) / 2 val scaleX = if (textWidth > width) width / textWidth.toDouble else 1.0d
g.scale(scaleX, 1)
// paint the top outline // center the text
val textX = if (textWidth > width) 0 else ((width - textWidth) / 2)
// this will be overlapped by the following text to show the top outline because of the offset
val yOffset = (fontHeight * 0.01).ceil.toInt
g.setColor(textHighlightColor) g.setColor(textHighlightColor)
g.drawString(secret, textX, 69) g.drawString(secret, textX, (fontHeight * 1.1).toInt - yOffset)
// paint the text // paint the text
g.setColor(textColor) g.setColor(textColor)
g.drawString(secret, textX, 70) g.drawString(secret, textX, (fontHeight * 1.1).toInt)
g.dispose() g.dispose()
writer.writeToSequence(canvas) writer.writeToSequence(canvas)

View File

@ -10,7 +10,7 @@ object ParametersEnum extends Enumeration {
val ALLOWEDLEVELS: Value = Value("allowedLevels") val ALLOWEDLEVELS: Value = Value("allowedLevels")
val ALLOWEDMEDIA: Value = Value("allowedMedia") val ALLOWEDMEDIA: Value = Value("allowedMedia")
val ALLOWEDINPUTTYPE: Value = Value("allowedInputType") val ALLOWEDINPUTTYPE: Value = Value("allowedInputType")
val ALLOWEDSIZES: Value = Value("allowedSizes")
} }
object AttributesEnum extends Enumeration { object AttributesEnum extends Enumeration {
@ -21,12 +21,12 @@ object AttributesEnum extends Enumeration {
val PORT: Value = Value("port") val PORT: Value = Value("port")
val ADDRESS: Value = Value("address") val ADDRESS: Value = Value("address")
val CAPTCHA_EXPIRY_TIME_LIMIT: Value = Value("captchaExpiryTimeLimit") val CAPTCHA_EXPIRY_TIME_LIMIT: Value = Value("captchaExpiryTimeLimit")
val THROTTLE: Value = Value("throttle") val BUFFER_COUNT: Value = Value("bufferCount")
val THREAD_DELAY: Value = Value("threadDelay") val THREAD_DELAY: Value = Value("threadDelay")
val PLAYGROUND_ENABLED: Value = Value("playgroundEnabled") val PLAYGROUND_ENABLED: Value = Value("playgroundEnabled")
val CORS_HEADER: Value = Value("corsHeader") val CORS_HEADER: Value = Value("corsHeader")
val CONFIG: Value = Value("config") val CONFIG: Value = Value("config")
val MAX_ATTEMPTS: Value = Value("maxAttempts") val MAX_ATTEMPTS_RATIO: Value = Value("maxAttemptsRatio")
} }
object ResultEnum extends Enumeration { object ResultEnum extends Enumeration {

View File

@ -34,17 +34,19 @@ class CaptchaManager(config: Config, captchaProviders: CaptchaProviders) {
} }
def generateChallenge(param: Parameters): Option[Int] = { def generateChallenge(param: Parameters): Option[Int] = {
val provider = captchaProviders.getProvider(param) try {
provider match { captchaProviders.getProvider(param).flatMap { provider =>
case Some(value) => { val providerId = provider.getId()
val providerId = value.getId() val challenge = provider.returnChallenge(param.level, param.size)
val challenge = value.returnChallenge()
val blob = new ByteArrayInputStream(challenge.content) val blob = new ByteArrayInputStream(challenge.content)
val token = insertCaptcha(value, challenge, providerId, param, blob) val token = insertCaptcha(provider, challenge, providerId, param, blob)
// println("Added new challenge: " + token.toString) // println("Added new challenge: " + token.toString)
token.map(_.toInt) token.map(_.toInt)
} }
case None => None } catch {
case e: Exception =>
e.printStackTrace()
None
} }
} }
@ -62,7 +64,8 @@ class CaptchaManager(config: Config, captchaProviders: CaptchaProviders) {
insertPstmt.setString(4, challenge.contentType) insertPstmt.setString(4, challenge.contentType)
insertPstmt.setString(5, param.level) insertPstmt.setString(5, param.level)
insertPstmt.setString(6, param.input_type) insertPstmt.setString(6, param.input_type)
insertPstmt.setBlob(7, blob) insertPstmt.setString(7, param.size)
insertPstmt.setBlob(8, blob)
insertPstmt.executeUpdate() insertPstmt.executeUpdate()
val rs: ResultSet = insertPstmt.getGeneratedKeys() val rs: ResultSet = insertPstmt.getGeneratedKeys()
if (rs.next()) { if (rs.next()) {
@ -105,11 +108,31 @@ class CaptchaManager(config: Config, captchaProviders: CaptchaProviders) {
} }
} }
def getCount(param: Parameters): Option[Int] = {
val countPstmt = Statements.tlStmts.get.countForParameterPstmt
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"))
} else {
None
}
}
private def getToken(param: Parameters): Option[Int] = { private def getToken(param: Parameters): Option[Int] = {
val count = getCount(param).getOrElse(0)
if (count == 0) {
None
} else {
val tokenPstmt = Statements.tlStmts.get.tokenPstmt val tokenPstmt = Statements.tlStmts.get.tokenPstmt
tokenPstmt.setString(1, param.level) tokenPstmt.setString(1, param.level)
tokenPstmt.setString(2, param.media) tokenPstmt.setString(2, param.media)
tokenPstmt.setString(3, param.input_type) tokenPstmt.setString(3, param.input_type)
tokenPstmt.setString(4, param.size)
tokenPstmt.setInt(5, count)
val rs = tokenPstmt.executeQuery() val rs = tokenPstmt.executeQuery()
if (rs.next()) { if (rs.next()) {
Some(rs.getInt("token")) Some(rs.getInt("token"))
@ -117,6 +140,7 @@ class CaptchaManager(config: Config, captchaProviders: CaptchaProviders) {
None None
} }
} }
}
private def updateAttempted(token: Int): Unit = { private def updateAttempted(token: Int): Unit = {
val updateAttemptedPstmt = Statements.tlStmts.get.updateAttemptedPstmt val updateAttemptedPstmt = Statements.tlStmts.get.updateAttemptedPstmt

View File

@ -19,7 +19,7 @@ class CaptchaProviders(config: Config) {
def generateChallengeSamples(): Map[String, Challenge] = { def generateChallengeSamples(): Map[String, Challenge] = {
providers.map { case (key, provider) => 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.allowedLevels.contains(param.level)
if configValue.allowedMedia.contains(param.media) if configValue.allowedMedia.contains(param.media)
if configValue.allowedInputType.contains(param.input_type) if configValue.allowedInputType.contains(param.input_type)
if configValue.allowedSizes.contains(param.size)
} yield (configValue.name, configValue.config) } yield (configValue.name, configValue.config)
val providerFilter = for { val providerFilter = for {

View File

@ -45,13 +45,13 @@ class Config(configFilePath: String) {
val port: Int = configFields.portInt.getOrElse(8888) val port: Int = configFields.portInt.getOrElse(8888)
val address: String = configFields.address.getOrElse("0.0.0.0") val address: String = configFields.address.getOrElse("0.0.0.0")
val throttle: Int = configFields.throttleInt.getOrElse(1000) val bufferCount: Int = configFields.bufferCountInt.getOrElse(1000)
val seed: Int = configFields.seedInt.getOrElse(375264328) val seed: Int = configFields.seedInt.getOrElse(375264328)
val captchaExpiryTimeLimit: Int = configFields.captchaExpiryTimeLimitInt.getOrElse(5) val captchaExpiryTimeLimit: Int = configFields.captchaExpiryTimeLimitInt.getOrElse(5)
val threadDelay: Int = configFields.threadDelayInt.getOrElse(2) val threadDelay: Int = configFields.threadDelayInt.getOrElse(2)
val playgroundEnabled: Boolean = configFields.playgroundEnabledBool.getOrElse(true) val playgroundEnabled: Boolean = configFields.playgroundEnabledBool.getOrElse(true)
val corsHeader: String = configFields.corsHeader.getOrElse("") val corsHeader: String = configFields.corsHeader.getOrElse("")
val maxAttempts: Int = configFields.maxAttemptsInt.getOrElse(10) val maxAttempts: Int = Math.max(1, (configFields.maxAttemptsRatioFloat.getOrElse(0.01f) * bufferCount).toInt)
private val captchaConfigJson = (configJson \ "captchas") private val captchaConfigJson = (configJson \ "captchas")
val captchaConfigTransform: JValue = captchaConfigJson transformField { case JField("config", JObject(config)) => val captchaConfigTransform: JValue = captchaConfigJson transformField { case JField("config", JObject(config)) =>
@ -70,17 +70,18 @@ class Config(configFilePath: String) {
(AttributesEnum.PORT.toString -> 8888) ~ (AttributesEnum.PORT.toString -> 8888) ~
(AttributesEnum.ADDRESS.toString -> "0.0.0.0") ~ (AttributesEnum.ADDRESS.toString -> "0.0.0.0") ~
(AttributesEnum.CAPTCHA_EXPIRY_TIME_LIMIT.toString -> 5) ~ (AttributesEnum.CAPTCHA_EXPIRY_TIME_LIMIT.toString -> 5) ~
(AttributesEnum.THROTTLE.toString -> 1000) ~ (AttributesEnum.BUFFER_COUNT.toString -> 1000) ~
(AttributesEnum.THREAD_DELAY.toString -> 2) ~ (AttributesEnum.THREAD_DELAY.toString -> 2) ~
(AttributesEnum.PLAYGROUND_ENABLED.toString -> true) ~ (AttributesEnum.PLAYGROUND_ENABLED.toString -> true) ~
(AttributesEnum.CORS_HEADER.toString -> "") ~ (AttributesEnum.CORS_HEADER.toString -> "") ~
(AttributesEnum.MAX_ATTEMPTS.toString -> 10) ~ (AttributesEnum.MAX_ATTEMPTS_RATIO.toString -> 0.01f) ~
("captchas" -> List( ("captchas" -> List(
( (
(AttributesEnum.NAME.toString -> "FilterChallenge") ~ (AttributesEnum.NAME.toString -> "FilterChallenge") ~
(ParametersEnum.ALLOWEDLEVELS.toString -> List("medium", "hard")) ~ (ParametersEnum.ALLOWEDLEVELS.toString -> List("medium", "hard")) ~
(ParametersEnum.ALLOWEDMEDIA.toString -> List("image/png")) ~ (ParametersEnum.ALLOWEDMEDIA.toString -> List("image/png")) ~
(ParametersEnum.ALLOWEDINPUTTYPE.toString -> List("text")) ~ (ParametersEnum.ALLOWEDINPUTTYPE.toString -> List("text")) ~
(ParametersEnum.ALLOWEDSIZES.toString -> List("350x100")) ~
(AttributesEnum.CONFIG.toString -> JObject()) (AttributesEnum.CONFIG.toString -> JObject())
), ),
( (
@ -88,6 +89,7 @@ class Config(configFilePath: String) {
(ParametersEnum.ALLOWEDLEVELS.toString -> List("hard")) ~ (ParametersEnum.ALLOWEDLEVELS.toString -> List("hard")) ~
(ParametersEnum.ALLOWEDMEDIA.toString -> List("image/gif")) ~ (ParametersEnum.ALLOWEDMEDIA.toString -> List("image/gif")) ~
(ParametersEnum.ALLOWEDINPUTTYPE.toString -> List("text")) ~ (ParametersEnum.ALLOWEDINPUTTYPE.toString -> List("text")) ~
(ParametersEnum.ALLOWEDSIZES.toString -> List("350x100")) ~
(AttributesEnum.CONFIG.toString -> JObject()) (AttributesEnum.CONFIG.toString -> JObject())
), ),
( (
@ -95,6 +97,7 @@ class Config(configFilePath: String) {
(ParametersEnum.ALLOWEDLEVELS.toString -> List("easy")) ~ (ParametersEnum.ALLOWEDLEVELS.toString -> List("easy")) ~
(ParametersEnum.ALLOWEDMEDIA.toString -> List("image/png")) ~ (ParametersEnum.ALLOWEDMEDIA.toString -> List("image/png")) ~
(ParametersEnum.ALLOWEDINPUTTYPE.toString -> List("text")) ~ (ParametersEnum.ALLOWEDINPUTTYPE.toString -> List("text")) ~
(ParametersEnum.ALLOWEDSIZES.toString -> List("350x100")) ~
(AttributesEnum.CONFIG.toString -> JObject()) (AttributesEnum.CONFIG.toString -> JObject())
), ),
( (
@ -102,6 +105,7 @@ class Config(configFilePath: String) {
(ParametersEnum.ALLOWEDLEVELS.toString -> List("easy", "medium")) ~ (ParametersEnum.ALLOWEDLEVELS.toString -> List("easy", "medium")) ~
(ParametersEnum.ALLOWEDMEDIA.toString -> List("image/gif")) ~ (ParametersEnum.ALLOWEDMEDIA.toString -> List("image/gif")) ~
(ParametersEnum.ALLOWEDINPUTTYPE.toString -> List("text")) ~ (ParametersEnum.ALLOWEDINPUTTYPE.toString -> List("text")) ~
(ParametersEnum.ALLOWEDSIZES.toString -> List("350x100")) ~
(AttributesEnum.CONFIG.toString -> JObject()) (AttributesEnum.CONFIG.toString -> JObject())
) )
)) ))

View File

@ -5,7 +5,7 @@ import lc.core.Config.formats
trait ByteConvert { def toBytes(): Array[Byte] } 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 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 Image(image: Array[Byte]) extends ByteConvert { def toBytes(): Array[Byte] = { image } }
case class Answer(answer: String, id: String) case class Answer(answer: String, id: String)
@ -16,28 +16,32 @@ case class CaptchaConfig(
allowedLevels: List[String], allowedLevels: List[String],
allowedMedia: List[String], allowedMedia: List[String],
allowedInputType: List[String], allowedInputType: List[String],
allowedSizes: List[String],
config: String config: String
) )
case class ConfigField( case class ConfigField(
port: Option[Integer], port: Option[Integer],
address: Option[String], address: Option[String],
throttle: Option[Integer], bufferCount: Option[Integer],
seed: Option[Integer], seed: Option[Integer],
captchaExpiryTimeLimit: Option[Integer], captchaExpiryTimeLimit: Option[Integer],
threadDelay: Option[Integer], threadDelay: Option[Integer],
playgroundEnabled: Option[java.lang.Boolean], playgroundEnabled: Option[java.lang.Boolean],
corsHeader: Option[String], corsHeader: Option[String],
maxAttempts: Option[Integer] maxAttemptsRatio: Option[java.lang.Float]
) { ) {
lazy val portInt: Option[Int] = mapInt(port) lazy val portInt: Option[Int] = mapInt(port)
lazy val throttleInt: Option[Int] = mapInt(throttle) lazy val bufferCountInt: Option[Int] = mapInt(bufferCount)
lazy val seedInt: Option[Int] = mapInt(seed) lazy val seedInt: Option[Int] = mapInt(seed)
lazy val captchaExpiryTimeLimitInt: Option[Int] = mapInt(captchaExpiryTimeLimit) lazy val captchaExpiryTimeLimitInt: Option[Int] = mapInt(captchaExpiryTimeLimit)
lazy val threadDelayInt: Option[Int] = mapInt(threadDelay) lazy val threadDelayInt: Option[Int] = mapInt(threadDelay)
lazy val maxAttemptsInt: Option[Int] = mapInt(maxAttempts) lazy val maxAttemptsRatioFloat: Option[Float] = mapFloat(maxAttemptsRatio)
lazy val playgroundEnabledBool: Option[Boolean] = playgroundEnabled.map(_ || true) lazy val playgroundEnabledBool: Option[Boolean] = playgroundEnabled.map(_ || false)
private def mapInt(x: Option[Integer]): Option[Int] = { private def mapInt(x: Option[Integer]): Option[Int] = {
x.map(_ + 0) x.map(_ + 0)
} }
private def mapFloat(x: Option[java.lang.Float]): Option[Float] = {
x.map(_ + 0.0f)
}
} }

View File

@ -3,7 +3,8 @@ package lc.database
import java.sql.{Connection, DriverManager, Statement} import java.sql.{Connection, DriverManager, Statement}
class DBConn() { class DBConn() {
val con: Connection = DriverManager.getConnection("jdbc:h2:./data/H2/captcha2", "sa", "") val con: Connection =
DriverManager.getConnection("jdbc:h2:./data/H2/captcha3;MAX_COMPACT_TIME=8000;DB_CLOSE_ON_EXIT=FALSE", "sa", "")
def getStatement(): Statement = { def getStatement(): Statement = {
con.createStatement() con.createStatement()

View File

@ -17,6 +17,7 @@ class Statements(dbConn: DBConn, maxAttempts: Int) {
"contentType varchar, " + "contentType varchar, " +
"contentLevel varchar, " + "contentLevel varchar, " +
"contentInput varchar, " + "contentInput varchar, " +
"size varchar, " +
"image blob, " + "image blob, " +
"attempted int default 0, " + "attempted int default 0, " +
"PRIMARY KEY(token));" + "PRIMARY KEY(token));" +
@ -37,8 +38,8 @@ class Statements(dbConn: DBConn, maxAttempts: Int) {
val insertPstmt: PreparedStatement = dbConn.con.prepareStatement( val insertPstmt: PreparedStatement = dbConn.con.prepareStatement(
"INSERT INTO " + "INSERT INTO " +
"challenge(id, secret, provider, contentType, contentLevel, contentInput, image) " + "challenge(id, secret, provider, contentType, contentLevel, contentInput, size, image) " +
"VALUES (?, ?, ?, ?, ?, ?, ?)", "VALUES (?, ?, ?, ?, ?, ?, ?, ?)",
Statement.RETURN_GENERATED_KEYS Statement.RETURN_GENERATED_KEYS
) )
@ -70,6 +71,18 @@ class Statements(dbConn: DBConn, maxAttempts: Int) {
"WHERE token = ?;" "WHERE token = ?;"
) )
val countForParameterPstmt: PreparedStatement = dbConn.con.prepareStatement(
s"""
SELECT count(*) as count
FROM challenge
WHERE attempted < $maxAttempts AND
contentLevel = ? AND
contentType = ? AND
contentInput = ? AND
size = ?
"""
)
val tokenPstmt: PreparedStatement = dbConn.con.prepareStatement( val tokenPstmt: PreparedStatement = dbConn.con.prepareStatement(
s""" s"""
SELECT token, attempted SELECT token, attempted
@ -77,8 +90,11 @@ class Statements(dbConn: DBConn, maxAttempts: Int) {
WHERE attempted < $maxAttempts AND WHERE attempted < $maxAttempts AND
contentLevel = ? AND contentLevel = ? AND
contentType = ? AND contentType = ? AND
contentInput = ? contentInput = ? AND
ORDER BY attempted ASC LIMIT 1""" size = ?
LIMIT 1
OFFSET FLOOR(RAND()*?)
"""
) )
val deleteAnswerPstmt: PreparedStatement = dbConn.con.prepareStatement( val deleteAnswerPstmt: PreparedStatement = dbConn.con.prepareStatement(
@ -107,6 +123,14 @@ class Statements(dbConn: DBConn, maxAttempts: Int) {
"SELECT * FROM mapId" "SELECT * FROM mapId"
) )
val shutdown: PreparedStatement = dbConn.con.prepareStatement(
"SHUTDOWN"
)
val shutdownCompact: PreparedStatement = dbConn.con.prepareStatement(
"SHUTDOWN COMPACT"
)
} }
object Statements { object Statements {

View File

@ -13,7 +13,13 @@ import java.net.InetSocketAddress
import java.util import java.util
import scala.jdk.CollectionConverters._ import scala.jdk.CollectionConverters._
class Server(address: String, port: Int, captchaManager: CaptchaManager, playgroundEnabled: Boolean, corsHeader: String) { class Server(
address: String,
port: Int,
captchaManager: CaptchaManager,
playgroundEnabled: Boolean,
corsHeader: String
) {
var headerMap: util.Map[String, util.List[String]] = _ var headerMap: util.Map[String, util.List[String]] = _
if (corsHeader.nonEmpty) { if (corsHeader.nonEmpty) {
headerMap = Map("Access-Control-Allow-Origin" -> List(corsHeader).asJava).asJava headerMap = Map("Access-Control-Allow-Origin" -> List(corsHeader).asJava).asJava
@ -23,7 +29,7 @@ class Server(address: String, port: Int, captchaManager: CaptchaManager, playgro
.address(new InetSocketAddress(address, port)) .address(new InetSocketAddress(address, port))
.backlog(32) .backlog(32)
.POST( .POST(
"/v1/captcha", "/v2/captcha",
(request) => { (request) => {
val json = parse(request.getBodyString()) val json = parse(request.getBodyString())
val param = json.extract[Parameters] val param = json.extract[Parameters]
@ -32,7 +38,7 @@ class Server(address: String, port: Int, captchaManager: CaptchaManager, playgro
} }
) )
.GET( .GET(
"/v1/media", "/v2/media",
(request) => { (request) => {
val params = request.getQueryParams() val params = request.getQueryParams()
val result = if (params.containsKey("id")) { val result = if (params.containsKey("id")) {
@ -46,7 +52,7 @@ class Server(address: String, port: Int, captchaManager: CaptchaManager, playgro
} }
) )
.POST( .POST(
"/v1/answer", "/v2/answer",
(request) => { (request) => {
val json = parse(request.getBodyString()) val json = parse(request.getBodyString())
val answer = json.extract[Answer] val answer = json.extract[Answer]
@ -63,6 +69,20 @@ class Server(address: String, port: Int, captchaManager: CaptchaManager, playgro
new StringResponse(200, str) new StringResponse(200, str)
} }
) )
serverBuilder.GET(
"/",
(_) => {
val str = """
<html>
<h2>Welcome to LibreCaptcha server</h2>
<h3><a href="/demo/index.html">Link to Demo</a></h3>
<h3>API is served at <b>/v2/</b></h3>
</html>
"""
new StringResponse(200, str)
}
)
println("Playground enabled on /demo/index.html")
} }
val server: picoserve.Server = serverBuilder.build() val server: picoserve.Server = serverBuilder.build()

View File

@ -3,16 +3,17 @@
"port" : 8888, "port" : 8888,
"address" : "0.0.0.0", "address" : "0.0.0.0",
"captchaExpiryTimeLimit" : 5, "captchaExpiryTimeLimit" : 5,
"throttle" : 10, "bufferCount" : 10,
"threadDelay" : 2, "threadDelay" : 2,
"playgroundEnabled" : false, "playgroundEnabled" : false,
"corsHeader" : "*", "corsHeader" : "*",
"maxAttempts" : 20, "maxAttemptsRatio" : 0.01,
"captchas" : [ { "captchas" : [ {
"name" : "DebugCaptcha", "name" : "DebugCaptcha",
"allowedLevels" : [ "debug" ], "allowedLevels" : [ "debug" ],
"allowedMedia" : [ "image/png" ], "allowedMedia" : [ "image/png" ],
"allowedInputType" : [ "text" ], "allowedInputType" : [ "text" ],
"allowedSizes" : [ "350x100" ],
"config" : { } "config" : { }
}] }]
} }

View File

@ -22,9 +22,9 @@ class QuickStartUser(SequentialTaskSet):
@task @task
def captcha(self): def captcha(self):
captcha_params = {"level":"debug","media":"image/png","input_type":"text"} captcha_params = {"level":"debug","media":"image/png","input_type":"text", "size":"350x100"}
with self.client.post(path="/v1/captcha", json=captcha_params, name="/captcha", catch_response = True) as resp: with self.client.post(path="/v2/captcha", json=captcha_params, name="/captcha", catch_response = True) as resp:
if resp.status_code != 200: if resp.status_code != 200:
resp.failure("Status was not 200: " + resp.text) resp.failure("Status was not 200: " + resp.text)
captchaJson = resp.json() captchaJson = resp.json()
@ -32,7 +32,7 @@ class QuickStartUser(SequentialTaskSet):
if not uuid: if not uuid:
resp.failure("uuid not returned on /captcha endpoint: " + resp.text) resp.failure("uuid not returned on /captcha endpoint: " + resp.text)
with self.client.get(path="/v1/media?id=%s" % uuid, name="/media", stream=True, catch_response = True) as resp: with self.client.get(path="/v2/media?id=%s" % uuid, name="/media", stream=True, catch_response = True) as resp:
if resp.status_code != 200: if resp.status_code != 200:
resp.failure("Status was not 200: " + resp.text) resp.failure("Status was not 200: " + resp.text)
@ -41,7 +41,7 @@ class QuickStartUser(SequentialTaskSet):
ocrAnswer = self.solve(uuid, media) ocrAnswer = self.solve(uuid, media)
answerBody = {"answer": ocrAnswer,"id": uuid} answerBody = {"answer": ocrAnswer,"id": uuid}
with self.client.post(path='/v1/answer', json=answerBody, name="/answer", catch_response=True) as resp: with self.client.post(path='/v2/answer', json=answerBody, name="/answer", catch_response=True) as resp:
if resp.status_code != 200: if resp.status_code != 200:
resp.failure("Status was not 200: " + resp.text) resp.failure("Status was not 200: " + resp.text)
else: else:

View File

@ -24,9 +24,9 @@ class QuickStartUser(SequentialTaskSet):
@task @task
def captcha(self): def captcha(self):
# TODO: Iterate over parameters for a more comprehensive test # TODO: Iterate over parameters for a more comprehensive test
captcha_params = {"level":"easy","media":"image/png","input_type":"text"} captcha_params = {"level":"easy","media":"image/png","input_type":"text", "size":"350x100"}
resp = self.client.post(path="/v1/captcha", json=captcha_params, name="/captcha") resp = self.client.post(path="/v2/captcha", json=captcha_params, name="/captcha")
if resp.status_code != 200: if resp.status_code != 200:
print("\nError on /captcha endpoint: ") print("\nError on /captcha endpoint: ")
print(resp) print(resp)
@ -36,14 +36,14 @@ class QuickStartUser(SequentialTaskSet):
uuid = json.loads(resp.text).get("id") uuid = json.loads(resp.text).get("id")
answerBody = {"answer": "qwer123","id": uuid} answerBody = {"answer": "qwer123","id": uuid}
resp = self.client.get(path="/v1/media?id=%s" % uuid, name="/media") resp = self.client.get(path="/v2/media?id=%s" % uuid, name="/media")
if resp.status_code != 200: if resp.status_code != 200:
print("\nError on /media endpoint: ") print("\nError on /media endpoint: ")
print(resp) print(resp)
print(resp.text) print(resp.text)
print("----------------END.MEDIA-------------------\n\n") print("----------------END.MEDIA-------------------\n\n")
resp = self.client.post(path='/v1/answer', json=answerBody, name="/answer") resp = self.client.post(path='/v2/answer', json=answerBody, name="/answer")
if resp.status_code != 200: if resp.status_code != 200:
print("\nError on /answer endpoint: ") print("\nError on /answer endpoint: ")
print(resp) print(resp)

View File

@ -4,7 +4,7 @@ python3 -m venv testEnv
source ./testEnv/bin/activate source ./testEnv/bin/activate
pip install locust pip install locust
mkdir -p data/ mkdir -p data/
java -jar target/scala-3.1.1/LibreCaptcha.jar & java -jar target/scala-3.6.2/LibreCaptcha.jar &
JAVA_PID=$! JAVA_PID=$!
sleep 4 sleep 4
@ -22,7 +22,7 @@ echo Run functional test
cp data/config.json data/config.json.bak cp data/config.json data/config.json.bak
cp tests/debug-config.json data/config.json cp tests/debug-config.json data/config.json
java -jar target/scala-3.1.1/LibreCaptcha.jar & java -jar target/scala-3.6.2/LibreCaptcha.jar &
JAVA_PID=$! JAVA_PID=$!
sleep 4 sleep 4