├── .gitignore ├── utils ├── ascii-sum.py ├── bf-print.py └── http-request-fields.txt ├── package.json ├── README.md ├── server.js ├── LICENSE.txt └── server.bf /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | *.log 3 | -------------------------------------------------------------------------------- /utils/ascii-sum.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | from sys import argv 3 | 4 | result = 0 5 | for c in argv[1]: 6 | result += ord(c) 7 | 8 | print(result) 9 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "brainfuck-web-app", 3 | "private": true, 4 | "scripts": { 5 | "start": "node server", 6 | "test": "standard --fix" 7 | }, 8 | "dependencies": { 9 | "brainfuck2000": "^0.1.1" 10 | }, 11 | "devDependencies": { 12 | "standard": "^10.0.3" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /utils/bf-print.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | from sys import argv 3 | 4 | value = 10 5 | 6 | for c in argv[1]: 7 | desired_value = ord(c) 8 | diff = abs(desired_value - value) 9 | if desired_value > value: 10 | increments = '+' * diff 11 | else: 12 | increments = '-' * diff 13 | 14 | print(c + ' ' + increments + '.') 15 | 16 | value = desired_value 17 | -------------------------------------------------------------------------------- /utils/http-request-fields.txt: -------------------------------------------------------------------------------- 1 | Accept 2 | Accept-Charset 3 | Accept-Encoding 4 | Accept-Language 5 | Accept-Datetime 6 | Authorization 7 | Cache-Control 8 | Connection 9 | Cookie 10 | Content-Length 11 | Content-MD5 12 | Content-Type 13 | Date 14 | Expect 15 | Forwarded 16 | From 17 | Host 18 | If-Match 19 | If-Modified-Since 20 | If-None-Match 21 | If-Range 22 | If-Unmodified-Since 23 | Max-Forwards 24 | Origin 25 | Pragma 26 | Proxy-Authorization 27 | Referer 28 | TE 29 | User-Agent 30 | Upgrade 31 | Via 32 | Warning 33 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | a Brainfuck web app 2 | =================== 3 | 4 | This is a web app that returns your useragent to you in plain text. The tricky bit: it's written in Brainfuck. 5 | 6 | ![screenshot](https://cloud.githubusercontent.com/assets/777712/14399473/ee4bda32-fda1-11e5-85b2-a906ec8f2982.png) 7 | 8 | Get this app set up 9 | ------------------- 10 | 11 | ``` 12 | # clone this and cd inside 13 | npm install 14 | npm start 15 | ``` 16 | 17 | Visit it at `http://localhost:3000`. Try hitting other URLs to notice your 404 errors! 18 | 19 | How does this work? 20 | ------------------- 21 | 22 | I wrote a small Node server (`server.js`) that's kind of like CGI for Brainfuck. It takes the bytes from a TCP stream and sends them to Brainfuck (`server.bf`), responding with the Brainfuck program's result. 23 | -------------------------------------------------------------------------------- /server.js: -------------------------------------------------------------------------------- 1 | const net = require('net') 2 | const brainfuck = require('brainfuck2000') 3 | const fs = require('fs') 4 | const path = require('path') 5 | 6 | const PORT = process.env.PORT || 3000 7 | const BF_PATH = path.resolve(__dirname, 'server.bf') 8 | 9 | const server = net.createServer((socket) => { 10 | socket.on('data', (req) => { 11 | req = req.toString() 12 | 13 | fs.readFile(BF_PATH, { encoding: 'utf8' }, (err, bfSource) => { 14 | if (err) { throw err } 15 | 16 | const program = brainfuck(bfSource) 17 | program.run(req) 18 | const res = program.resultString() 19 | 20 | socket.end(res) 21 | }) 22 | }) 23 | }) 24 | 25 | server.on('error', (err) => { 26 | throw err 27 | }) 28 | 29 | server.listen(PORT, () => { 30 | console.log('opened server on %s', server.address().port) 31 | }) 32 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | This is free and unencumbered software released into the public domain. 2 | 3 | Anyone is free to copy, modify, publish, use, compile, sell, or 4 | distribute this software, either in source code form or as a compiled 5 | binary, for any purpose, commercial or non-commercial, and by any 6 | means. 7 | 8 | In jurisdictions that recognize copyright laws, the author or authors 9 | of this software dedicate any and all copyright interest in the 10 | software to the public domain. We make this dedication for the benefit 11 | of the public at large and to the detriment of our heirs and 12 | successors. We intend this dedication to be an overt act of 13 | relinquishment in perpetuity of all present and future rights to this 14 | software under copyright law. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | For more information, please refer to 25 | -------------------------------------------------------------------------------- /server.bf: -------------------------------------------------------------------------------- 1 | Preamble: some punctuation is valid Brainfuck so there is very little punctuation in these comments 2 | 3 | This is a server that returns your useragent back to you if you sent a GET request to / and returns a 404 otherwise 4 | 5 | Step 1: send "HTTP/1(dot)1 " 6 | ============================ 7 | 8 | You could be clever with this but I prefer to be boring 9 | 10 | H ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++. 11 | T ++++++++++++. 12 | T . 13 | P ----. 14 | / [-]+++++++++++++++++++++++++++++++++++++++++++++++. 15 | 1 ++. 16 | dot ---. 17 | 1 +++. 18 | space -----------------. 19 | 20 | Step 2: is this a GET request? 21 | ============================== 22 | 23 | Turns out that if you sum the ASCII values of each character in GET or PUT or DELETE (etc) you get different numbers (GET = 224; POST = 326; etc) 24 | GET is the only thing that sums to 224 and with a trailing space it sums to 256 25 | This is fabulous for us 26 | 27 | Here's what we'll do: 28 | * start a counter at 256 (in register 0) 29 | * for each character: 30 | * subtract the ASCII value from this counter 31 | * if it's a space then we stop looping 32 | * if the counter is 0 then we know it's a GET request 33 | * if we know it's NOT a GET request then we send a 404 message 34 | 35 | Let's make register 0 become 256: 36 | >+++++++++++++++++[-<+++++++++++++>]<+++ 37 | 38 | Head to r1 and make sure it's nonzero so that we run the following loop at least once 39 | >+ 40 | 41 | [ 42 | In the context of this loop: 43 | * r0 = the counter (mentioned above) 44 | * r1 = the ASCII character for space (32) 45 | * r2 = the character we read 46 | 47 | First step is to zero out r1 48 | Iterations of this loop could make r1 positive OR negative so we add a bunch of numbers to it to make sure it's positive before we zero it out 49 | ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 50 | ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 51 | ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 52 | ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 53 | ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 54 | [-] 55 | 56 | Next we make r1 equal to 32 (the ASCII code for space) 57 | ++++++++++++++++++++++++++++++++ 58 | 59 | Read the character into r2 60 | >, 61 | 62 | Subtract the ASCII value from the counter (r0) AND subtract its value from r1 63 | [-<-<->>] 64 | 65 | Head back to r1 66 | This loop will stop if the character was a space (AKA r1 == 0) 67 | < 68 | ] 69 | 70 | Head back to r0 71 | < 72 | 73 | At this point: 74 | * if r0 == 0 then this is a GET request 75 | * else this is any other HTTP method (POST or PUT or DELETE or whatever) and r0 is negative 76 | 77 | The state of the registers: 78 | * r0 is mentioned above 79 | * everything else is 0 80 | * pointer @ r0 81 | 82 | Step 3: is this a request to /? 83 | =============================== 84 | 85 | At this point we've read "(METHOD) " 86 | The next part is the path which we hope to be "/" and nothing else 87 | 88 | The desired result is this: 89 | * if r1 == 0 then this is a request to / 90 | * else this is NOT a request to / and we should 404 91 | 92 | Let's head to r1 93 | > 94 | 95 | Every path starts with a / so we can read that in and ignore it 96 | But the next character is important so we'll take a look at that 97 | ,, 98 | 99 | Subtract 32 from this which will give us the desired result from above 100 | -------------------------------- 101 | 102 | Let's return to r0 103 | < 104 | 105 | At this point: 106 | * if r0 == 0 then this is a GET request 107 | * if r1 == 0 then this is a request to / 108 | * else this is a request to any other path and r1 is positive 109 | 110 | The state of the registers: 111 | * r0 is mentioned above 112 | * r1 is mentioned above 113 | * pointer @ r0 114 | 115 | Step 4: send an error if we should 116 | ================================== 117 | 118 | This is the dream code we'd love to write: 119 | 120 | if path != '/': 121 | send_404 122 | elif method != 'GET': 123 | send_405 124 | else: 125 | send_200 126 | send_user_agent 127 | 128 | This kinda translates to: 129 | 130 | if r1 != 0: 131 | send_404 132 | r0 = 0 133 | r2 = negative 1 134 | if r0 != 0: 135 | send_405 136 | r2 = negative 1 137 | set r2 to r2 plus 1 138 | if r2 != 0: 139 | send_200 140 | send_user_agent 141 | 142 | Here's how we'll do this: 143 | 144 | If r1 != 0: 145 | >[ 146 | Move to r2 147 | > 148 | r2 is already 0 so we can just start to print stuff 149 | 150 | 4 ++++++++++++++++++++++++++++++++++++++++++++++++++++. 151 | 0 ----. 152 | 4 ++++. 153 | --------------------. 154 | N ++++++++++++++++++++++++++++++++++++++++++++++. 155 | o +++++++++++++++++++++++++++++++++. 156 | t +++++. 157 | ------------------------------------------------------------------------------------. 158 | F ++++++++++++++++++++++++++++++++++++++. 159 | o +++++++++++++++++++++++++++++++++++++++++. 160 | u ++++++. 161 | n -------. 162 | d ----------. 163 | \r\n [-]+++++++++++++.---. 164 | 165 | Reset r2 166 | [-] 167 | Increment r2 168 | + 169 | 170 | Zero out r1 171 | <[-] 172 | Zero out r0 to prevent 405 from being sent 173 | <[+] 174 | ] 175 | < 176 | 177 | If r0 != 0: 178 | [ 179 | As we cleared r1 above we can be reasonably sure that r2 is still zero 180 | So we use that for printing 181 | >> 182 | 183 | "405 Method Not Allowed" 184 | 4 ++++++++++++++++++++++++++++++++++++++++++++++++++++. 185 | 0 ----. 186 | 5 +++++. 187 | ---------------------. 188 | M +++++++++++++++++++++++++++++++++++++++++++++. 189 | e ++++++++++++++++++++++++. 190 | t +++++++++++++++. 191 | h ------------. 192 | o +++++++. 193 | d -----------. 194 | --------------------------------------------------------------------. 195 | N ++++++++++++++++++++++++++++++++++++++++++++++. 196 | o +++++++++++++++++++++++++++++++++. 197 | t +++++. 198 | ------------------------------------------------------------------------------------. 199 | A +++++++++++++++++++++++++++++++++. 200 | l +++++++++++++++++++++++++++++++++++++++++++. 201 | l . 202 | o +++. 203 | w ++++++++. 204 | e ------------------. 205 | d -. 206 | \r ---------------------------------------------------------------------------------------. 207 | \n ---. 208 | RFC 2616 mandates an Allow header on a 405 response 209 | A +++++++++++++++++++++++++++++++++++++++++++++++++++++++. 210 | l +++++++++++++++++++++++++++++++++++++++++++. 211 | l . 212 | o +++. 213 | w ++++++++. 214 | : -------------------------------------------------------------. 215 | --------------------------. 216 | G +++++++++++++++++++++++++++++++++++++++. 217 | E --. 218 | T +++++++++++++++. 219 | \r\n [-]+++++++++++++.---. 220 | 221 | Zero out r2 222 | [-] 223 | Set r2 to 1 224 | + 225 | 226 | Zero out r0 227 | <<[+] 228 | ] 229 | 230 | At this point: 231 | * if r2 != 0 then we set r1 to negative 1 232 | * if r2 == 0 then we leave r1 be 233 | >>[ 234 | Set r2 to 0 235 | [-] 236 | 237 | Set r1 to negative 1—we'll come back to this 238 | <-> 239 | ] 240 | 241 | Increment r1 as a marker for "is this a GET request to /" 242 | It'll be 0 if it's NOT a GET request 243 | It'll be 1 if it IS a GET request 244 | <+ 245 | 246 | Head back to r0 247 | < 248 | 249 | At this point: 250 | * if r1 == 1 then this is a GET request to / 251 | * else this is NOT a GET request to / 252 | 253 | The state of the registers: 254 | * r1 is mentioned above 255 | * everything else is 0 256 | * pointer @ r0 257 | 258 | Step 5: grab and print the useragent 259 | ==================================== 260 | 261 | This is the big kahuna 262 | 263 | As mentioned above: if r1 == 1 then this is a GET request to / 264 | So let's go through this loop if we should 265 | 266 | >[ 267 | Step 5/1: finish the first line of the HTTP request 268 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 269 | 270 | First let's read until we hit the \r character (into r1) 271 | [,-------------] 272 | 273 | Skip the \n that follows it and then zero out r1 274 | ,[-] 275 | 276 | Back to r0 277 | < 278 | 279 | The state of the registers: 280 | * everything is 0 281 | * pointer @ r0 282 | 283 | Step 5/2: send "200 OK" and the content type 284 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 285 | 286 | Send "200 OK" before we start parsing and printing stuff 287 | 2 ++++++++++++++++++++++++++++++++++++++++++++++++++. 288 | 0 --. 289 | 0 . 290 | ----------------. 291 | O +++++++++++++++++++++++++++++++++++++++++++++++. 292 | K ----. 293 | 294 | "\r\nContent(dash)Type: text/plain\r\n\r\n" 295 | \r\n [-]+++++++++++++.---. 296 | C +++++++++++++++++++++++++++++++++++++++++++++++++++++++++. 297 | o ++++++++++++++++++++++++++++++++++++++++++++. 298 | n -. 299 | t ++++++. 300 | e ---------------. 301 | n +++++++++. 302 | t ++++++. 303 | - ----------------------------------------------------------------------. 304 | T +++++++++++++++++++++++++++++++++++++++. 305 | y +++++++++++++++++++++++++++++++++++++. 306 | p ---------. 307 | e -----------. 308 | : -------------------------------------------. 309 | --------------------------. 310 | t ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++. 311 | e ---------------. 312 | x +++++++++++++++++++. 313 | t ----. 314 | / ---------------------------------------------------------------------. 315 | p +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++. 316 | l ----. 317 | a -----------. 318 | i ++++++++. 319 | n +++++. 320 | \r\n [-]+++++++++++++.---. 321 | \r\n +++.---. 322 | 323 | The state of the registers: 324 | * r0 == 10 325 | * pointer @ r0 326 | 327 | Step 5/3: find the useragent header 328 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 329 | 330 | HTTP request headers are like HTTP verbs in that you can sum their ASCII codes and get unique values 331 | You can look at the "List of HTTP header fields" page on Wikipedia to verify this 332 | "User(dash)Agent: " sums to 1045 so let's set r0 to 1045 333 | (We use r1 to help here but zero it out when we're done) 334 | >++++++++++++++++++++++++++++++++[-<++++++++++++++++++++++++++++++++>]<+++++++++++> 335 | 336 | Let's read lines until we see "User(dash)Agent:" 337 | We will keep looping while r1 == 1 338 | +[ 339 | At the start of this loop: 340 | * r0 = 1045 341 | * r1 = 1 342 | 343 | Let's copy 1045 from r0 into r2 and r3 344 | * r2 will start at 1045 and we'll decrement it 345 | * r3 will stay at 1045 so we can reset r0 at the end 346 | <[->>+>+<<<] 347 | 348 | Read each character up until the space 349 | all the while subtracting it from r2 350 | +[ 351 | Read the character into r0 352 | , 353 | Subtract r2 from it and move it to r4 354 | [->>->>+<<<<] 355 | Move r4 back to r0 356 | >>>>[-<<<<+>>>>] 357 | Subtract 32 from r0 so we can figure out if this is a space 358 | <<<<-------------------------------- 359 | ] 360 | 361 | Move r3 back into r0 362 | >>>[-<<<+>>>] 363 | 364 | Now we've read the header key (and the ": ")! 365 | 366 | If our counter (in r2) is 0: 367 | * we've found our useragent header 368 | * r1 should be set to 0 so we leave this loop 369 | If our counter (in r2) is NOT 0: 370 | * we haven't found our useragent header 371 | * we should read until the end of the line 372 | * r1 should be set to 1 so we stay in this loop 373 | 374 | Always decrement r1 (setting it to 0) 375 | We'll increment it back to 1 if r2 wasn't 0 376 | <<- 377 | 378 | If we haven't found the header yet we need to 379 | >[ 380 | Increment r1 381 | <+> 382 | Read until the end of the line 383 | [,----------] 384 | ] 385 | 386 | Head back to r1 387 | < 388 | ] 389 | 390 | Return to r0 391 | < 392 | 393 | The state of the registers: 394 | * r0 == 1045 395 | * pointer @ r0 396 | 397 | Step 5/4: print the useragent 398 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 399 | 400 | This part is pretty easy 401 | We'll just print every character until we hit \n 402 | 403 | [,.----------] 404 | ] 405 | 406 | That was horrible 407 | --------------------------------------------------------------------------------