1 /// A CAPTCHA generator for D services. 2 /// Written in the D programming language. 3 4 module dcaptcha.dcaptcha; 5 6 import std.algorithm; 7 import std.conv; 8 import std.exception; 9 import std.random; 10 import std.range; 11 import std..string; 12 13 import ae.utils.array; 14 15 import dcaptcha.markov; 16 17 struct CaptchaSpec 18 { 19 bool allowEasy = true; 20 bool allowHard = false; 21 bool allowStatic = false; 22 } 23 24 struct Challenge 25 { 26 string question, code; 27 string[] answers; 28 string hint; /// HTML! 29 } 30 31 /** 32 Goals: 33 - Answers should not be obvious: 34 - Keywords shouldn't give away the answer, e.g. `unittest { ... }` 35 is obviously an unit test block. 36 - `return 2+2` is obvious to non-programmers. 37 - Answers should vary considerably: 38 - A question which has the answer "0" much of the time is easy 39 to defeat simply by giving the answer "0" all of the time. 40 - Questions should not be Google-able: 41 - Search engines ignore most punctuation and math operators. 42 - Keywords and string literals should vary or be generic enough. 43 **/ 44 45 Challenge getCaptcha(CaptchaSpec spec = CaptchaSpec.init) 46 { 47 string[] identifiers = 48 26 49 .iota 50 .map!(l => [cast(char)('a'+l)].assumeUnique()) 51 .filter!(s => s != "l") 52 .array(); 53 identifiers ~= "foo, bar, baz".split(", "); 54 //identifiers ~= "qux, quux, corge, grault, garply, waldo, fred, plugh, xyzzy, thud".split(", "); 55 56 enum hardFactor = 100; 57 int lowerFactor = spec.allowEasy ? 1 : hardFactor; 58 int upperFactor = spec.allowHard ? hardFactor : 1; 59 60 assert(spec.allowEasy || spec.allowHard, "No difficulty selected"); 61 62 int specUniform(int lowerBound, int upperBound) { return uniform(lowerBound*lowerFactor, upperBound*upperFactor); } 63 64 string[] mathOperators = "+ - / * %".split(); 65 66 Challenge challenge; 67 with (challenge) 68 [ 69 // Identify syntax 70 { 71 if (!spec.allowStatic || !spec.allowHard) 72 return false; 73 question = "What is the name of the D language syntax feature illustrated in the following fragment of D code?"; 74 hint = `You can find the answer in the<br><a href="http://dlang.org/spec.html">Language Reference section of dlang.org</a>.`; 75 // `You can find the answers in the <a href="http://dlang.org/lex.html">Lexical</a> and ` 76 // `<a href="http://dlang.org/grammar.html">Grammar</a> language reference sections on dlang.org.`; 77 return 78 [ 79 // lambda 80 { 81 code = 82 q{ 83 (A, B) => A @ B 84 }.formatExample() 85 .replace("A", identifiers.pluck) 86 .replace("B", identifiers.pluck) 87 .replace("@", mathOperators.pluck) 88 ; 89 answers = cartesianJoin(["lambda", "lambda function", "anonymous function"], ["", " literal"]); 90 return true; 91 }, 92 // static destructor 93 { 94 string bye = ["Bye", "Goodbye", "Shutting down", "Exiting"].sample ~ ["", ".", "...", "!"].sample; 95 code = 96 q{ 97 static ~this() 98 { 99 writeln("BYE"); 100 } 101 }.formatExample() 102 .replace("BYE", bye) 103 ; 104 answers = ["static destructor", "module destructor", "thread destructor"]; 105 return true; 106 }, 107 // nested comments 108 { 109 code = 110 q{ 111 /+ A = B @ C; /+ A = X; +/ +/ 112 }.formatExample() 113 .replace("A", identifiers.pluck) 114 .replace("B", identifiers.pluck) 115 .replace("C", identifiers.pluck) 116 .replace("X", uniform(10, 100).text) 117 .replace("@", mathOperators.pluck) 118 ; 119 answers = cartesianJoin(["nested ", "nesting "], ["", "block "], ["comment", "comments"]); 120 return true; 121 }, 122 // anonymous nested classes 123 { 124 code = 125 q{ 126 auto A = new class O {}; 127 }.formatExample() 128 .replace("A", identifiers.pluck) 129 .replace("O", identifiers.pluck.toUpper) 130 ; 131 answers = cartesianJoin(["anonymous "], ["", "nested "], ["class", "classes"]); 132 return true; 133 }, 134 // delimited (heredoc) strings 135 { 136 auto delimiter = ["EOF", "DELIM", "STR", "QUOT", "MARK"].sample; 137 code = 138 q{ 139 string A = TEXT; 140 }.formatExample() 141 .replace("F", identifiers.pluck) 142 .replace("TEXT", `q"` ~ delimiter ~ "\n" ~ MarkovChain!2.query().join(" ").wrap(38).strip() ~ "\n" ~ delimiter ~ `"`) 143 ; 144 answers = cartesianJoin(["", "multiline ", "multi-line "], ["delimited", "heredoc"], ["", " string", " strings"]); 145 return true; 146 }, 147 // hex strings 148 { 149 string hex; 150 do 151 hex = 152 uniform(3, 5) 153 .iota 154 .map!(i => "xX".sample) 155 .map!(f => 156 [1, 2, 4].sample 157 .iota 158 .map!(j => 159 format("%02" ~ f, uniform(0, 0x100)) 160 ) 161 .join("") 162 ) 163 .join(" "); 164 while (hex.length > 20); 165 code = 166 q{ 167 string A = x"CC"; 168 }.formatExample() 169 .replace("A", identifiers.pluck) 170 .replace("CC", hex) 171 ; 172 answers = cartesianJoin(["hex", "hex ", "hexadecimal "], ["string", "strings"], ["", " literal", " literals"]); 173 return true; 174 }, 175 // associative arrays 176 { 177 string[] types = ["int", "string"]; 178 code = 179 q{ 180 T[U] A; 181 }.formatExample() 182 .replace("A", identifiers.pluck) 183 .replace("T", types.sample) 184 .replace("U", types.sample) 185 ; 186 answers = ["AA", "associative array", "hashmap"]; 187 return true; 188 }, 189 // array slicing 190 { 191 code = 192 q{ 193 A = B[X..Y]; 194 }.formatExample() 195 .replace("A", identifiers.pluck) 196 .replace("B", identifiers.pluck) 197 .replace("X", uniform(0, 5).text) 198 .replace("Y", uniform(5, 10).text) 199 ; 200 answers = cartesianJoin(["", "array "], ["slice", "slicing"]); 201 return true; 202 }, 203 ].randomCover().map!(f => f()).any(); 204 }, 205 // Calculate function result 206 // (use syntax that only programmers should be familiar with) 207 { 208 question = "What will be the return value of the following function?"; 209 hint = `You can run D code online on <a href="http://dpaste.dzfl.pl/">DPaste</a>.`; 210 return 211 [ 212 // Modulo operator (%) 213 { 214 int x, y; 215 do 216 { 217 x = specUniform(10, 50); 218 y = specUniform(x/4, x/2); 219 } 220 while (x % y == 0); 221 222 code = 223 q{ 224 int F() 225 { 226 int A = X; 227 A %= Y; 228 return A; 229 } 230 }.formatExample() 231 .replace("F", identifiers.pluck) 232 .replace("A", identifiers.pluck) 233 .replace("X", x.text) 234 .replace("Y", y.text) 235 ; 236 answers = [(x % y).text]; 237 return true; 238 }, 239 // Integer division, increment 240 { 241 int y = specUniform(2, 5); 242 int x = uniform(5, 50*upperFactor / y) * y + specUniform(1, y); 243 int sign = uniform(0, 2) ? -1 : 1; 244 code = 245 q{ 246 int F() 247 { 248 int A = X, B = Y; 249 B@@; 250 A /= B; 251 return A; 252 } 253 }.formatExample() 254 .replace("F", identifiers.pluck) 255 .replace("A", identifiers.pluck) 256 .replace("B", identifiers.pluck) 257 .replace("X", x.text) 258 .replace("Y", (y - sign).text) 259 .replace("@", sign > 0 ? "+" : "-") 260 ; 261 answers = [(x / y).text]; 262 return true; 263 }, 264 // Ternary operator + division/modulo 265 { 266 int x = specUniform(10, 50); 267 int y = specUniform(2, 4); 268 int a = specUniform(10, 50); 269 int b = uniform(2*lowerFactor, a/3); 270 int d = specUniform(5, 10); 271 int c = uniform(2, 50*upperFactor / d) * d + uniform(1, d); 272 code = 273 q{ 274 int F() 275 { 276 return X % Y 277 ? A / B 278 : C % D; 279 } 280 }.formatExample() 281 .replace("F", identifiers.pluck) 282 .replace("X", x.text) 283 .replace("Y", y.text) 284 .replace("A", a.text) 285 .replace("B", b.text) 286 .replace("C", c.text) 287 .replace("D", d.text) 288 ; 289 answers = [(x % y ? a / b : c % d).text]; 290 return true; 291 }, 292 // Formatting, hexadecimal numbers 293 { 294 int n = specUniform(20, 100); 295 n &= ~7; 296 int w = uniform(2, 8); 297 string id = identifiers.pluck; 298 code = 299 q{ 300 string F() 301 { 302 return format("A=%0WX", N); 303 } 304 }.formatExample() 305 .replace("F", identifiers.pluck) 306 .replace("A", id) 307 .replace("N", n.text) 308 .replace("W", w.text) 309 ; 310 answers = [format("%s=%0*X", id, w, n)]; 311 answers ~= answers.map!(s => `"`~s~`"`).array(); 312 return true; 313 }, 314 // iota+reduce - max 315 { 316 int x = specUniform(10, 100); 317 code = 318 q{ 319 int F() 320 { 321 return iota(X).reduce!max; 322 } 323 }.formatExample() 324 .replace("F", identifiers.pluck) 325 .replace("X", x.text) 326 ; 327 answers = [(x - 1).text]; 328 return true; 329 }, 330 // iota+reduce - sum 331 { 332 if (!spec.allowHard) return false; 333 int x = specUniform(3, 10); 334 code = 335 q{ 336 int F() 337 { 338 return iota(X).reduce!"a+b"; 339 } 340 }.formatExample() 341 .replace("F", identifiers.pluck) 342 .replace("X", x.text) 343 ; 344 answers = [(iota(x).reduce!"a+b").text]; 345 return true; 346 }, 347 ].randomCover().map!(f => f()).any(); 348 }, 349 ].randomCover().map!(f => f()).any().enforce("Can't find suitable CAPTCHA"); 350 return challenge; 351 } 352 353 private string[] cartesianJoin(PARTS...)(PARTS parts) 354 { 355 return cartesianProduct(parts).map!(t => join([t.expand])).array(); 356 } 357 358 private string formatExample(string s) 359 { 360 return s 361 .outdent() 362 .strip() 363 .replace("\t", " ") 364 ; 365 }