├── .env ├── .gitignore ├── LICENSE ├── README.md ├── bashttpd.sh ├── libbashttpd ├── bwf.sh ├── content │ ├── application │ │ ├── json.sh │ │ └── x-www-form-urlencoded.sh │ └── multipart │ │ └── form-data.sh ├── handler.sh ├── request.sh ├── router.sh └── utility.sh ├── localhost ├── .etc │ ├── .env │ ├── db.sql │ └── tpl │ │ ├── age.html │ │ ├── error.html │ │ ├── image.html │ │ └── ls.html ├── api │ ├── age │ │ └── POST.sh │ ├── comments │ │ ├── GET.sh │ │ └── POST.sh │ ├── images │ │ └── GET.sh │ ├── upload │ │ └── POST.sh │ └── visits │ │ └── GET.sh ├── assets │ ├── app.css │ ├── app.js │ └── img.png ├── away │ └── GET.sh ├── gallery │ ├── image │ │ └── GET.sh │ └── index.html ├── index.html ├── ls │ └── GET.sh └── storage │ └── images │ └── UXRf0oub.jpg └── testdata ├── 15.bin ├── 20.bin ├── ABCD.bin ├── genbin.go ├── ico.bin ├── llq.json ├── ls.bin ├── req_ABCD.bin ├── req_ico.bin ├── req_png.bin ├── uploadme.ico ├── uploadme.jpg ├── uploadme.png ├── uploadme.txt └── uploadme.xml /.env: -------------------------------------------------------------------------------- 1 | PORT=8080 2 | 3 | # Supported values are 1 to 5 4 | LOG_VERBOSITY=2 5 | 6 | DEBUG_DUMP_HEADERS=reqheaders 7 | DEBUG_DUMP_BODY=reqbody 8 | 9 | # DB creds 10 | MYSQL_HOST=127.0.0.1 11 | MYSQL_USER=root 12 | MYSQL_PASSWORD= 13 | MYSQL_DB=bashttpd 14 | 15 | # Set to 1 to allow requests to static files within dot-dirs, like $PROJECT/.etc 16 | SERVE_DOTFILES=0 17 | 18 | # Set to 1 to allow requests to handler scripts as static files. 19 | SERVE_HANDLER_SCRIPTS=0 20 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | reqbody 2 | reqdata 3 | reqheaders 4 | exp -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | Preamble 9 | 10 | The GNU General Public License is a free, copyleft license for 11 | software and other kinds of works. 12 | 13 | The licenses for most software and other practical works are designed 14 | to take away your freedom to share and change the works. By contrast, 15 | the GNU General Public License is intended to guarantee your freedom to 16 | share and change all versions of a program--to make sure it remains free 17 | software for all its users. We, the Free Software Foundation, use the 18 | GNU General Public License for most of our software; it applies also to 19 | any other work released this way by its authors. You can apply it to 20 | your programs, too. 21 | 22 | When we speak of free software, we are referring to freedom, not 23 | price. Our General Public Licenses are designed to make sure that you 24 | have the freedom to distribute copies of free software (and charge for 25 | them if you wish), that you receive source code or can get it if you 26 | want it, that you can change the software or use pieces of it in new 27 | free programs, and that you know you can do these things. 28 | 29 | To protect your rights, we need to prevent others from denying you 30 | these rights or asking you to surrender the rights. Therefore, you have 31 | certain responsibilities if you distribute copies of the software, or if 32 | you modify it: responsibilities to respect the freedom of others. 33 | 34 | For example, if you distribute copies of such a program, whether 35 | gratis or for a fee, you must pass on to the recipients the same 36 | freedoms that you received. You must make sure that they, too, receive 37 | or can get the source code. And you must show them these terms so they 38 | know their rights. 39 | 40 | Developers that use the GNU GPL protect your rights with two steps: 41 | (1) assert copyright on the software, and (2) offer you this License 42 | giving you legal permission to copy, distribute and/or modify it. 43 | 44 | For the developers' and authors' protection, the GPL clearly explains 45 | that there is no warranty for this free software. For both users' and 46 | authors' sake, the GPL requires that modified versions be marked as 47 | changed, so that their problems will not be attributed erroneously to 48 | authors of previous versions. 49 | 50 | Some devices are designed to deny users access to install or run 51 | modified versions of the software inside them, although the manufacturer 52 | can do so. This is fundamentally incompatible with the aim of 53 | protecting users' freedom to change the software. The systematic 54 | pattern of such abuse occurs in the area of products for individuals to 55 | use, which is precisely where it is most unacceptable. Therefore, we 56 | have designed this version of the GPL to prohibit the practice for those 57 | products. If such problems arise substantially in other domains, we 58 | stand ready to extend this provision to those domains in future versions 59 | of the GPL, as needed to protect the freedom of users. 60 | 61 | Finally, every program is threatened constantly by software patents. 62 | States should not allow patents to restrict development and use of 63 | software on general-purpose computers, but in those that do, we wish to 64 | avoid the special danger that patents applied to a free program could 65 | make it effectively proprietary. To prevent this, the GPL assures that 66 | patents cannot be used to render the program non-free. 67 | 68 | The precise terms and conditions for copying, distribution and 69 | modification follow. 70 | 71 | TERMS AND CONDITIONS 72 | 73 | 0. Definitions. 74 | 75 | "This License" refers to version 3 of the GNU General Public License. 76 | 77 | "Copyright" also means copyright-like laws that apply to other kinds of 78 | works, such as semiconductor masks. 79 | 80 | "The Program" refers to any copyrightable work licensed under this 81 | License. Each licensee is addressed as "you". "Licensees" and 82 | "recipients" may be individuals or organizations. 83 | 84 | To "modify" a work means to copy from or adapt all or part of the work 85 | in a fashion requiring copyright permission, other than the making of an 86 | exact copy. The resulting work is called a "modified version" of the 87 | earlier work or a work "based on" the earlier work. 88 | 89 | A "covered work" means either the unmodified Program or a work based 90 | on the Program. 91 | 92 | To "propagate" a work means to do anything with it that, without 93 | permission, would make you directly or secondarily liable for 94 | infringement under applicable copyright law, except executing it on a 95 | computer or modifying a private copy. Propagation includes copying, 96 | distribution (with or without modification), making available to the 97 | public, and in some countries other activities as well. 98 | 99 | To "convey" a work means any kind of propagation that enables other 100 | parties to make or receive copies. Mere interaction with a user through 101 | a computer network, with no transfer of a copy, is not conveying. 102 | 103 | An interactive user interface displays "Appropriate Legal Notices" 104 | to the extent that it includes a convenient and prominently visible 105 | feature that (1) displays an appropriate copyright notice, and (2) 106 | tells the user that there is no warranty for the work (except to the 107 | extent that warranties are provided), that licensees may convey the 108 | work under this License, and how to view a copy of this License. If 109 | the interface presents a list of user commands or options, such as a 110 | menu, a prominent item in the list meets this criterion. 111 | 112 | 1. Source Code. 113 | 114 | The "source code" for a work means the preferred form of the work 115 | for making modifications to it. "Object code" means any non-source 116 | form of a work. 117 | 118 | A "Standard Interface" means an interface that either is an official 119 | standard defined by a recognized standards body, or, in the case of 120 | interfaces specified for a particular programming language, one that 121 | is widely used among developers working in that language. 122 | 123 | The "System Libraries" of an executable work include anything, other 124 | than the work as a whole, that (a) is included in the normal form of 125 | packaging a Major Component, but which is not part of that Major 126 | Component, and (b) serves only to enable use of the work with that 127 | Major Component, or to implement a Standard Interface for which an 128 | implementation is available to the public in source code form. A 129 | "Major Component", in this context, means a major essential component 130 | (kernel, window system, and so on) of the specific operating system 131 | (if any) on which the executable work runs, or a compiler used to 132 | produce the work, or an object code interpreter used to run it. 133 | 134 | The "Corresponding Source" for a work in object code form means all 135 | the source code needed to generate, install, and (for an executable 136 | work) run the object code and to modify the work, including scripts to 137 | control those activities. However, it does not include the work's 138 | System Libraries, or general-purpose tools or generally available free 139 | programs which are used unmodified in performing those activities but 140 | which are not part of the work. For example, Corresponding Source 141 | includes interface definition files associated with source files for 142 | the work, and the source code for shared libraries and dynamically 143 | linked subprograms that the work is specifically designed to require, 144 | such as by intimate data communication or control flow between those 145 | subprograms and other parts of the work. 146 | 147 | The Corresponding Source need not include anything that users 148 | can regenerate automatically from other parts of the Corresponding 149 | Source. 150 | 151 | The Corresponding Source for a work in source code form is that 152 | same work. 153 | 154 | 2. Basic Permissions. 155 | 156 | All rights granted under this License are granted for the term of 157 | copyright on the Program, and are irrevocable provided the stated 158 | conditions are met. This License explicitly affirms your unlimited 159 | permission to run the unmodified Program. The output from running a 160 | covered work is covered by this License only if the output, given its 161 | content, constitutes a covered work. This License acknowledges your 162 | rights of fair use or other equivalent, as provided by copyright law. 163 | 164 | You may make, run and propagate covered works that you do not 165 | convey, without conditions so long as your license otherwise remains 166 | in force. You may convey covered works to others for the sole purpose 167 | of having them make modifications exclusively for you, or provide you 168 | with facilities for running those works, provided that you comply with 169 | the terms of this License in conveying all material for which you do 170 | not control copyright. Those thus making or running the covered works 171 | for you must do so exclusively on your behalf, under your direction 172 | and control, on terms that prohibit them from making any copies of 173 | your copyrighted material outside their relationship with you. 174 | 175 | Conveying under any other circumstances is permitted solely under 176 | the conditions stated below. Sublicensing is not allowed; section 10 177 | makes it unnecessary. 178 | 179 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law. 180 | 181 | No covered work shall be deemed part of an effective technological 182 | measure under any applicable law fulfilling obligations under article 183 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or 184 | similar laws prohibiting or restricting circumvention of such 185 | measures. 186 | 187 | When you convey a covered work, you waive any legal power to forbid 188 | circumvention of technological measures to the extent such circumvention 189 | is effected by exercising rights under this License with respect to 190 | the covered work, and you disclaim any intention to limit operation or 191 | modification of the work as a means of enforcing, against the work's 192 | users, your or third parties' legal rights to forbid circumvention of 193 | technological measures. 194 | 195 | 4. Conveying Verbatim Copies. 196 | 197 | You may convey verbatim copies of the Program's source code as you 198 | receive it, in any medium, provided that you conspicuously and 199 | appropriately publish on each copy an appropriate copyright notice; 200 | keep intact all notices stating that this License and any 201 | non-permissive terms added in accord with section 7 apply to the code; 202 | keep intact all notices of the absence of any warranty; and give all 203 | recipients a copy of this License along with the Program. 204 | 205 | You may charge any price or no price for each copy that you convey, 206 | and you may offer support or warranty protection for a fee. 207 | 208 | 5. Conveying Modified Source Versions. 209 | 210 | You may convey a work based on the Program, or the modifications to 211 | produce it from the Program, in the form of source code under the 212 | terms of section 4, provided that you also meet all of these conditions: 213 | 214 | a) The work must carry prominent notices stating that you modified 215 | it, and giving a relevant date. 216 | 217 | b) The work must carry prominent notices stating that it is 218 | released under this License and any conditions added under section 219 | 7. This requirement modifies the requirement in section 4 to 220 | "keep intact all notices". 221 | 222 | c) You must license the entire work, as a whole, under this 223 | License to anyone who comes into possession of a copy. This 224 | License will therefore apply, along with any applicable section 7 225 | additional terms, to the whole of the work, and all its parts, 226 | regardless of how they are packaged. This License gives no 227 | permission to license the work in any other way, but it does not 228 | invalidate such permission if you have separately received it. 229 | 230 | d) If the work has interactive user interfaces, each must display 231 | Appropriate Legal Notices; however, if the Program has interactive 232 | interfaces that do not display Appropriate Legal Notices, your 233 | work need not make them do so. 234 | 235 | A compilation of a covered work with other separate and independent 236 | works, which are not by their nature extensions of the covered work, 237 | and which are not combined with it such as to form a larger program, 238 | in or on a volume of a storage or distribution medium, is called an 239 | "aggregate" if the compilation and its resulting copyright are not 240 | used to limit the access or legal rights of the compilation's users 241 | beyond what the individual works permit. Inclusion of a covered work 242 | in an aggregate does not cause this License to apply to the other 243 | parts of the aggregate. 244 | 245 | 6. Conveying Non-Source Forms. 246 | 247 | You may convey a covered work in object code form under the terms 248 | of sections 4 and 5, provided that you also convey the 249 | machine-readable Corresponding Source under the terms of this License, 250 | in one of these ways: 251 | 252 | a) Convey the object code in, or embodied in, a physical product 253 | (including a physical distribution medium), accompanied by the 254 | Corresponding Source fixed on a durable physical medium 255 | customarily used for software interchange. 256 | 257 | b) Convey the object code in, or embodied in, a physical product 258 | (including a physical distribution medium), accompanied by a 259 | written offer, valid for at least three years and valid for as 260 | long as you offer spare parts or customer support for that product 261 | model, to give anyone who possesses the object code either (1) a 262 | copy of the Corresponding Source for all the software in the 263 | product that is covered by this License, on a durable physical 264 | medium customarily used for software interchange, for a price no 265 | more than your reasonable cost of physically performing this 266 | conveying of source, or (2) access to copy the 267 | Corresponding Source from a network server at no charge. 268 | 269 | c) Convey individual copies of the object code with a copy of the 270 | written offer to provide the Corresponding Source. This 271 | alternative is allowed only occasionally and noncommercially, and 272 | only if you received the object code with such an offer, in accord 273 | with subsection 6b. 274 | 275 | d) Convey the object code by offering access from a designated 276 | place (gratis or for a charge), and offer equivalent access to the 277 | Corresponding Source in the same way through the same place at no 278 | further charge. You need not require recipients to copy the 279 | Corresponding Source along with the object code. If the place to 280 | copy the object code is a network server, the Corresponding Source 281 | may be on a different server (operated by you or a third party) 282 | that supports equivalent copying facilities, provided you maintain 283 | clear directions next to the object code saying where to find the 284 | Corresponding Source. Regardless of what server hosts the 285 | Corresponding Source, you remain obligated to ensure that it is 286 | available for as long as needed to satisfy these requirements. 287 | 288 | e) Convey the object code using peer-to-peer transmission, provided 289 | you inform other peers where the object code and Corresponding 290 | Source of the work are being offered to the general public at no 291 | charge under subsection 6d. 292 | 293 | A separable portion of the object code, whose source code is excluded 294 | from the Corresponding Source as a System Library, need not be 295 | included in conveying the object code work. 296 | 297 | A "User Product" is either (1) a "consumer product", which means any 298 | tangible personal property which is normally used for personal, family, 299 | or household purposes, or (2) anything designed or sold for incorporation 300 | into a dwelling. In determining whether a product is a consumer product, 301 | doubtful cases shall be resolved in favor of coverage. For a particular 302 | product received by a particular user, "normally used" refers to a 303 | typical or common use of that class of product, regardless of the status 304 | of the particular user or of the way in which the particular user 305 | actually uses, or expects or is expected to use, the product. A product 306 | is a consumer product regardless of whether the product has substantial 307 | commercial, industrial or non-consumer uses, unless such uses represent 308 | the only significant mode of use of the product. 309 | 310 | "Installation Information" for a User Product means any methods, 311 | procedures, authorization keys, or other information required to install 312 | and execute modified versions of a covered work in that User Product from 313 | a modified version of its Corresponding Source. The information must 314 | suffice to ensure that the continued functioning of the modified object 315 | code is in no case prevented or interfered with solely because 316 | modification has been made. 317 | 318 | If you convey an object code work under this section in, or with, or 319 | specifically for use in, a User Product, and the conveying occurs as 320 | part of a transaction in which the right of possession and use of the 321 | User Product is transferred to the recipient in perpetuity or for a 322 | fixed term (regardless of how the transaction is characterized), the 323 | Corresponding Source conveyed under this section must be accompanied 324 | by the Installation Information. But this requirement does not apply 325 | if neither you nor any third party retains the ability to install 326 | modified object code on the User Product (for example, the work has 327 | been installed in ROM). 328 | 329 | The requirement to provide Installation Information does not include a 330 | requirement to continue to provide support service, warranty, or updates 331 | for a work that has been modified or installed by the recipient, or for 332 | the User Product in which it has been modified or installed. Access to a 333 | network may be denied when the modification itself materially and 334 | adversely affects the operation of the network or violates the rules and 335 | protocols for communication across the network. 336 | 337 | Corresponding Source conveyed, and Installation Information provided, 338 | in accord with this section must be in a format that is publicly 339 | documented (and with an implementation available to the public in 340 | source code form), and must require no special password or key for 341 | unpacking, reading or copying. 342 | 343 | 7. Additional Terms. 344 | 345 | "Additional permissions" are terms that supplement the terms of this 346 | License by making exceptions from one or more of its conditions. 347 | Additional permissions that are applicable to the entire Program shall 348 | be treated as though they were included in this License, to the extent 349 | that they are valid under applicable law. If additional permissions 350 | apply only to part of the Program, that part may be used separately 351 | under those permissions, but the entire Program remains governed by 352 | this License without regard to the additional permissions. 353 | 354 | When you convey a copy of a covered work, you may at your option 355 | remove any additional permissions from that copy, or from any part of 356 | it. (Additional permissions may be written to require their own 357 | removal in certain cases when you modify the work.) You may place 358 | additional permissions on material, added by you to a covered work, 359 | for which you have or can give appropriate copyright permission. 360 | 361 | Notwithstanding any other provision of this License, for material you 362 | add to a covered work, you may (if authorized by the copyright holders of 363 | that material) supplement the terms of this License with terms: 364 | 365 | a) Disclaiming warranty or limiting liability differently from the 366 | terms of sections 15 and 16 of this License; or 367 | 368 | b) Requiring preservation of specified reasonable legal notices or 369 | author attributions in that material or in the Appropriate Legal 370 | Notices displayed by works containing it; or 371 | 372 | c) Prohibiting misrepresentation of the origin of that material, or 373 | requiring that modified versions of such material be marked in 374 | reasonable ways as different from the original version; or 375 | 376 | d) Limiting the use for publicity purposes of names of licensors or 377 | authors of the material; or 378 | 379 | e) Declining to grant rights under trademark law for use of some 380 | trade names, trademarks, or service marks; or 381 | 382 | f) Requiring indemnification of licensors and authors of that 383 | material by anyone who conveys the material (or modified versions of 384 | it) with contractual assumptions of liability to the recipient, for 385 | any liability that these contractual assumptions directly impose on 386 | those licensors and authors. 387 | 388 | All other non-permissive additional terms are considered "further 389 | restrictions" within the meaning of section 10. If the Program as you 390 | received it, or any part of it, contains a notice stating that it is 391 | governed by this License along with a term that is a further 392 | restriction, you may remove that term. If a license document contains 393 | a further restriction but permits relicensing or conveying under this 394 | License, you may add to a covered work material governed by the terms 395 | of that license document, provided that the further restriction does 396 | not survive such relicensing or conveying. 397 | 398 | If you add terms to a covered work in accord with this section, you 399 | must place, in the relevant source files, a statement of the 400 | additional terms that apply to those files, or a notice indicating 401 | where to find the applicable terms. 402 | 403 | Additional terms, permissive or non-permissive, may be stated in the 404 | form of a separately written license, or stated as exceptions; 405 | the above requirements apply either way. 406 | 407 | 8. Termination. 408 | 409 | You may not propagate or modify a covered work except as expressly 410 | provided under this License. Any attempt otherwise to propagate or 411 | modify it is void, and will automatically terminate your rights under 412 | this License (including any patent licenses granted under the third 413 | paragraph of section 11). 414 | 415 | However, if you cease all violation of this License, then your 416 | license from a particular copyright holder is reinstated (a) 417 | provisionally, unless and until the copyright holder explicitly and 418 | finally terminates your license, and (b) permanently, if the copyright 419 | holder fails to notify you of the violation by some reasonable means 420 | prior to 60 days after the cessation. 421 | 422 | Moreover, your license from a particular copyright holder is 423 | reinstated permanently if the copyright holder notifies you of the 424 | violation by some reasonable means, this is the first time you have 425 | received notice of violation of this License (for any work) from that 426 | copyright holder, and you cure the violation prior to 30 days after 427 | your receipt of the notice. 428 | 429 | Termination of your rights under this section does not terminate the 430 | licenses of parties who have received copies or rights from you under 431 | this License. If your rights have been terminated and not permanently 432 | reinstated, you do not qualify to receive new licenses for the same 433 | material under section 10. 434 | 435 | 9. Acceptance Not Required for Having Copies. 436 | 437 | You are not required to accept this License in order to receive or 438 | run a copy of the Program. Ancillary propagation of a covered work 439 | occurring solely as a consequence of using peer-to-peer transmission 440 | to receive a copy likewise does not require acceptance. However, 441 | nothing other than this License grants you permission to propagate or 442 | modify any covered work. These actions infringe copyright if you do 443 | not accept this License. Therefore, by modifying or propagating a 444 | covered work, you indicate your acceptance of this License to do so. 445 | 446 | 10. Automatic Licensing of Downstream Recipients. 447 | 448 | Each time you convey a covered work, the recipient automatically 449 | receives a license from the original licensors, to run, modify and 450 | propagate that work, subject to this License. You are not responsible 451 | for enforcing compliance by third parties with this License. 452 | 453 | An "entity transaction" is a transaction transferring control of an 454 | organization, or substantially all assets of one, or subdividing an 455 | organization, or merging organizations. If propagation of a covered 456 | work results from an entity transaction, each party to that 457 | transaction who receives a copy of the work also receives whatever 458 | licenses to the work the party's predecessor in interest had or could 459 | give under the previous paragraph, plus a right to possession of the 460 | Corresponding Source of the work from the predecessor in interest, if 461 | the predecessor has it or can get it with reasonable efforts. 462 | 463 | You may not impose any further restrictions on the exercise of the 464 | rights granted or affirmed under this License. For example, you may 465 | not impose a license fee, royalty, or other charge for exercise of 466 | rights granted under this License, and you may not initiate litigation 467 | (including a cross-claim or counterclaim in a lawsuit) alleging that 468 | any patent claim is infringed by making, using, selling, offering for 469 | sale, or importing the Program or any portion of it. 470 | 471 | 11. Patents. 472 | 473 | A "contributor" is a copyright holder who authorizes use under this 474 | License of the Program or a work on which the Program is based. The 475 | work thus licensed is called the contributor's "contributor version". 476 | 477 | A contributor's "essential patent claims" are all patent claims 478 | owned or controlled by the contributor, whether already acquired or 479 | hereafter acquired, that would be infringed by some manner, permitted 480 | by this License, of making, using, or selling its contributor version, 481 | but do not include claims that would be infringed only as a 482 | consequence of further modification of the contributor version. For 483 | purposes of this definition, "control" includes the right to grant 484 | patent sublicenses in a manner consistent with the requirements of 485 | this License. 486 | 487 | Each contributor grants you a non-exclusive, worldwide, royalty-free 488 | patent license under the contributor's essential patent claims, to 489 | make, use, sell, offer for sale, import and otherwise run, modify and 490 | propagate the contents of its contributor version. 491 | 492 | In the following three paragraphs, a "patent license" is any express 493 | agreement or commitment, however denominated, not to enforce a patent 494 | (such as an express permission to practice a patent or covenant not to 495 | sue for patent infringement). To "grant" such a patent license to a 496 | party means to make such an agreement or commitment not to enforce a 497 | patent against the party. 498 | 499 | If you convey a covered work, knowingly relying on a patent license, 500 | and the Corresponding Source of the work is not available for anyone 501 | to copy, free of charge and under the terms of this License, through a 502 | publicly available network server or other readily accessible means, 503 | then you must either (1) cause the Corresponding Source to be so 504 | available, or (2) arrange to deprive yourself of the benefit of the 505 | patent license for this particular work, or (3) arrange, in a manner 506 | consistent with the requirements of this License, to extend the patent 507 | license to downstream recipients. "Knowingly relying" means you have 508 | actual knowledge that, but for the patent license, your conveying the 509 | covered work in a country, or your recipient's use of the covered work 510 | in a country, would infringe one or more identifiable patents in that 511 | country that you have reason to believe are valid. 512 | 513 | If, pursuant to or in connection with a single transaction or 514 | arrangement, you convey, or propagate by procuring conveyance of, a 515 | covered work, and grant a patent license to some of the parties 516 | receiving the covered work authorizing them to use, propagate, modify 517 | or convey a specific copy of the covered work, then the patent license 518 | you grant is automatically extended to all recipients of the covered 519 | work and works based on it. 520 | 521 | A patent license is "discriminatory" if it does not include within 522 | the scope of its coverage, prohibits the exercise of, or is 523 | conditioned on the non-exercise of one or more of the rights that are 524 | specifically granted under this License. You may not convey a covered 525 | work if you are a party to an arrangement with a third party that is 526 | in the business of distributing software, under which you make payment 527 | to the third party based on the extent of your activity of conveying 528 | the work, and under which the third party grants, to any of the 529 | parties who would receive the covered work from you, a discriminatory 530 | patent license (a) in connection with copies of the covered work 531 | conveyed by you (or copies made from those copies), or (b) primarily 532 | for and in connection with specific products or compilations that 533 | contain the covered work, unless you entered into that arrangement, 534 | or that patent license was granted, prior to 28 March 2007. 535 | 536 | Nothing in this License shall be construed as excluding or limiting 537 | any implied license or other defenses to infringement that may 538 | otherwise be available to you under applicable patent law. 539 | 540 | 12. No Surrender of Others' Freedom. 541 | 542 | If conditions are imposed on you (whether by court order, agreement or 543 | otherwise) that contradict the conditions of this License, they do not 544 | excuse you from the conditions of this License. If you cannot convey a 545 | covered work so as to satisfy simultaneously your obligations under this 546 | License and any other pertinent obligations, then as a consequence you may 547 | not convey it at all. For example, if you agree to terms that obligate you 548 | to collect a royalty for further conveying from those to whom you convey 549 | the Program, the only way you could satisfy both those terms and this 550 | License would be to refrain entirely from conveying the Program. 551 | 552 | 13. Use with the GNU Affero General Public License. 553 | 554 | Notwithstanding any other provision of this License, you have 555 | permission to link or combine any covered work with a work licensed 556 | under version 3 of the GNU Affero General Public License into a single 557 | combined work, and to convey the resulting work. The terms of this 558 | License will continue to apply to the part which is the covered work, 559 | but the special requirements of the GNU Affero General Public License, 560 | section 13, concerning interaction through a network will apply to the 561 | combination as such. 562 | 563 | 14. Revised Versions of this License. 564 | 565 | The Free Software Foundation may publish revised and/or new versions of 566 | the GNU General Public License from time to time. Such new versions will 567 | be similar in spirit to the present version, but may differ in detail to 568 | address new problems or concerns. 569 | 570 | Each version is given a distinguishing version number. If the 571 | Program specifies that a certain numbered version of the GNU General 572 | Public License "or any later version" applies to it, you have the 573 | option of following the terms and conditions either of that numbered 574 | version or of any later version published by the Free Software 575 | Foundation. If the Program does not specify a version number of the 576 | GNU General Public License, you may choose any version ever published 577 | by the Free Software Foundation. 578 | 579 | If the Program specifies that a proxy can decide which future 580 | versions of the GNU General Public License can be used, that proxy's 581 | public statement of acceptance of a version permanently authorizes you 582 | to choose that version for the Program. 583 | 584 | Later license versions may give you additional or different 585 | permissions. However, no additional obligations are imposed on any 586 | author or copyright holder as a result of your choosing to follow a 587 | later version. 588 | 589 | 15. Disclaimer of Warranty. 590 | 591 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY 592 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT 593 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY 594 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, 595 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 596 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM 597 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF 598 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 599 | 600 | 16. Limitation of Liability. 601 | 602 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 603 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS 604 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY 605 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE 606 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF 607 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD 608 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), 609 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF 610 | SUCH DAMAGES. 611 | 612 | 17. Interpretation of Sections 15 and 16. 613 | 614 | If the disclaimer of warranty and limitation of liability provided 615 | above cannot be given local legal effect according to their terms, 616 | reviewing courts shall apply local law that most closely approximates 617 | an absolute waiver of all civil liability in connection with the 618 | Program, unless a warranty or assumption of liability accompanies a 619 | copy of the Program in return for a fee. 620 | 621 | END OF TERMS AND CONDITIONS 622 | 623 | How to Apply These Terms to Your New Programs 624 | 625 | If you develop a new program, and you want it to be of the greatest 626 | possible use to the public, the best way to achieve this is to make it 627 | free software which everyone can redistribute and change under these terms. 628 | 629 | To do so, attach the following notices to the program. It is safest 630 | to attach them to the start of each source file to most effectively 631 | state the exclusion of warranty; and each file should have at least 632 | the "copyright" line and a pointer to where the full notice is found. 633 | 634 | 635 | Copyright (C) 636 | 637 | This program is free software: you can redistribute it and/or modify 638 | it under the terms of the GNU General Public License as published by 639 | the Free Software Foundation, either version 3 of the License, or 640 | (at your option) any later version. 641 | 642 | This program is distributed in the hope that it will be useful, 643 | but WITHOUT ANY WARRANTY; without even the implied warranty of 644 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 645 | GNU General Public License for more details. 646 | 647 | You should have received a copy of the GNU General Public License 648 | along with this program. If not, see . 649 | 650 | Also add information on how to contact you by electronic and paper mail. 651 | 652 | If the program does terminal interaction, make it output a short 653 | notice like this when it starts in an interactive mode: 654 | 655 | Copyright (C) 656 | This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 657 | This is free software, and you are welcome to redistribute it 658 | under certain conditions; type `show c' for details. 659 | 660 | The hypothetical commands `show w' and `show c' should show the appropriate 661 | parts of the General Public License. Of course, your program's commands 662 | might be different; for a GUI interface, you would use an "about box". 663 | 664 | You should also get your employer (if you work as a programmer) or school, 665 | if any, to sign a "copyright disclaimer" for the program, if necessary. 666 | For more information on this, and how to apply and follow the GNU GPL, see 667 | . 668 | 669 | The GNU General Public License does not permit incorporating your program 670 | into proprietary programs. If your program is a subroutine library, you 671 | may consider it more useful to permit linking proprietary applications with 672 | the library. If this is what you want to do, use the GNU Lesser General 673 | Public License instead of this License. But first, please read 674 | . -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Bashttpd 2 | An HTTP server and a web framework, both written in pure Bash script. It really do be like that sometimes. 3 | 4 | Bashttpd aims to implement the HTTP protocol and provide a modern web development platform, while sticking to Bash Script and standard POSIX tools as much as possible. 5 | 6 | At the moment it has basic support for HTTP and all kinds of requests, fully supports binary files & file upload, form data, JSON requests & responses, has MySQL utilities, does routing, renders simple HTML templates and more. 7 | 8 | ## Requirements 9 | To carry the socket work, `socat` or `netcat` are needed. Bashttpd will use whichever is installed in the system, but if you want, you can specify which one to use: `bashttpd /path/to/project [socat|netcat]` 10 | 11 | Unlike `netcat`, `socat` can fork on request and reuse the bound address for multiple incoming connections, which makes the server parallel and much more responsive, so `socat` is recommended for use and is default. 12 | 13 | `mysql` to keep data. 14 | 15 | [jq](https://stedolan.github.io/jq/) is you want JSON requests. Responses don't need it. 16 | 17 | ## Usage 18 | `./bashttpd localhost` 19 | 20 | Here `localhost` is a path to a folder that contains a project. 21 | 22 | Then visit [localhost:8080](http://localhost:8080) in browser. 23 | 24 | You may want to fix MySQL connection credentials in the `.env` file to see the DB in action. 25 | 26 | ## Design 27 | ### Request routing 28 | When Bashttpd receives a request, three things can hapen. 29 | 30 | First, it tries to match the path from request to the folder structure of the supplied project, and looks for a script file named after the HTTP request method used. 31 | 32 | For example, a `GET` request to the `/foo/bar` path is served by the `localhost/foo/bar/GET.sh` script. 33 | 34 | If the request path exactly matches a file path in the project directory, it will respond with it's contents. At the moment it supports `js`, `css` & `html`, as well as `jpeg` & `png` images with proper content types. 35 | 36 | If none of the criterias above have matched, it'll try to interpret the requested path as a directory path and will try to find and serve `index.html` file from there. 37 | 38 | ### Functions, values and yielding 39 | Since in Bash you only return interger values from functions, the rest of data is echoed to stdin and captured `$()` by a caller. In terms of Bashttpd & BWF this is called "yielding" for clarity. 40 | 41 | Some functions can yield their results in two ways: regular yield to stdin which should be captured by `$()`, and yield by reference, where function takes a name of a variable where it writes it's result. The `req.Data` function & the `mysql.*` family are examples of such `referential functions`. They also can automatically report errors, see below. 42 | 43 | ### Error handling & reporing 44 | Since `$()` actually `captures` *any* output, it is not possible to automatically report errors from such invocations, so only `referential functions` can do that, and this is the primary reason they exist. 45 | 46 | ## Framework 47 | There is one! Bash Web Framework, or BWF, implements some standard operations expected from a modern web framework, making development of simple web apps in Bash script a breeze. 48 | 49 | ### Request Data 50 | Structured request data (i.e. forms or JSONs) is available via the `req.Data "fieldName"` function. It is mostly Content-Type agnostic, but for JSON requests it allows looking deeply through JSON structure with the [jq filter syntax](https://stedolan.github.io/jq/manual/#Basicfilters). 51 | 52 | #### Headers 53 | Request headers are available to handler scripts under their names capitalized and dashes replaced by underscores. So a `Content-Type` header is accessible as a `$CONTENT_TYPE` variable. 54 | 55 | #### Supported Request Content Types 56 | At the moment BWF understands `application/x-www-form-urlencoded` and `multipart/form-data` for forms, `application/json` for generic data. 57 | 58 | | Function | Description | Example | 59 | | --- | --- | --- | 60 | |**req.Cookie**| Yields a value of a cookie from the request. |`SID=$(req.Cookie "session_id")`| 61 | |**req.Data**|Yields a single field value from the request body. Content-Type-agnostic.
*$1* Request parameter name.
*$2* Optional result reference name.|`userName=$(req.Data "userName")`| 62 | |**req.File**|Yields a temporary file name where contents of the uploaded file is stored.
*$1* The name of the file as in form data.|`filePath=$(req.File "theFile")`| 63 | |**req.FileName**|Yields original name of the uploaded file. Takes the name of the file as in form data.|`sourceFileName=$(req.FileName "theFile")`| 64 | |**req.FileCT**|Yields the Content-Type of the uploaded file. Takes the name of the file as in form data.|`fileCT=$(req.FileCT "theFile")`| 65 | |**req.Query**|Yields a value of a query string parameter.|`page=$(req.Query "page")`| 66 | 67 | ### Responding 68 | Basically you can just `echo` anything, and it'll get to a client, but you'll have to follow the HTTP protocol yourself. 69 | 70 | If you're not a fan, there are functions for that. 71 | 72 | | Function | Description | Example | 73 | | --- | --- | --- | 74 | |**resp.Status**|Initiates a response by sending an `HTTP/1.1` header with the status you provide.|`resp.Status 200`| 75 | |**resp.Header**|Writes an HTTP header.|`resp.Header "Content-Type" "text/html"`| 76 | |**resp.Cookie**|Sends a cookie to a client.|`resp.Cookie "visit_counter" $counter`| 77 | |**resp.Body**|Writes the response body.|`resp.Body "

YOLO

"`| 78 | |**resp.File**|Responds with a file contents. Note that you have to specify Content-Type yourself.|`resp.File "/etc/passwd"`| 79 | |**resp.TemplateFile**|Reads a file from `$PROJECT/.etc/tpl/` directory, expands variables into it, responds with the result.|`resp.TemplateFile "welcome.html"`| 80 | |**resp.JSON**|A shorthand function to respond with JSONs. Encodes the passed data, sends Content-Type. Regular arrays are encoded as JSON arrays, associative ones become objects.
*$1* A *reference* to a regular or associative array.|`declare -a FILE_LIST`
`# Fill the $FILE_LIST...`
`resp.JSON FILE_LIST`| 81 | |**resp.CLI**|Formats the colored output (`\e[34;91m...\e[0m`) as HTML.|`HTML=$(resp.CLI $(ls -la --color=always ~))`| 82 | |**resp.Redirect**|A regular HTTP redirect response. Writes a `Location` header with a `30*` status code.|`resp.Redirect "http://example.com" 303`| 83 | 84 | ### MySQL 85 | | Function | Description | Example | 86 | | --- | --- | --- | 87 | |**mysql.All**|Returns all available rows from a table.
*$1* Table name to select from.
*$2* Optional result *reference* name.|`mysql.All image_comments ROWS`| 88 | |**mysql.Select**|Performs a simple SELECT MySQL query.
*$1* Table name to select from.
*$2* A WHERE clause.
*$3* Optional result *reference* name.|`mysql.Select image_comments "imageID='$imageID'" ROWS`| 89 | |**mysql.Insert**|Performs an INSERT MySQL query. Result is an ID of the inserted row.
*$1* Table name to insert to.
*$2* An associative array with column data.
*$3* Optional result *reference* name.|`declare -A COMMENT=(`
`[imageID]=$(req.Data imageID)`
`[message]=$(req.Data message)`
`)`
`mysql.Insert image_comments COMMENT ID`| 90 | |**mysql.foreach**|Alias. Iterates over MySQL query result rows. Expects the `ROWS` variable.|See below.| 91 | |**mysql.row**|Alias. Must be called within the `mysql.foreach` loop, creates a local `row` variable which is an associative array containing the row's column data.|`mysql.Select image_comments "imageID='$imageID'" ROWS`
`mysql.foreach; do`
`mysql.row`
`echo "Message is ${row[message]}"`
`done`| 92 | |**mysql.Install**|Performs a first-time installation of a MySQL database for a running project. It will read the `MYSQL_DB` value from environment, create a MySQL database named after it, the execute an SQL file from `$PROJECT/.etc/DB.sql`, if such is found. It is performed automatically on server startup.|| 93 | 94 | ### Utility 95 | | Function | Description | Example | 96 | | --- | --- | --- | 97 | |**log**
**logg**
**loggg**
**logggg**
**loggggg**|A logging function. Outputs to the host's `stderr`.
The more **g**'s in the name, the higher **LOG_VERBOSITY** config value is required for the message to be displayed.|`log "User name is $name"`
`loggg "Not your everyday message"`| 98 | |**var**|A syntactic sugar function which defines and initializes a dynamically named variable.|`var "DATA_$dataName" $dataValue`| 99 | |**yield**|A syntactic sugar to output dynamic variables. A relative to the conventional `return` keyword.|`yield "$theResult"`| 100 | |**HTTP.urldecode**|A standard URL decoding function.|`decodedInput=$(HTTP.urldecode $encodedInput)`| 101 | |**HTTP.urlencode**|A standard URL encoding function.|`encodedInput=$(HTTP.urlencode $decodedInput)`| 102 | |**sys.Time**|Yields current unixtime.|`T=$(sys.Time)`| 103 | |**sys.IFS**|A function to help preserve correct values for the IFS variable. Supports stacking.|`sys.IFS $'\r'`
`sys.IFS ';'`
`sys.IFS # IFS is $'\r' now`
`sys.IFS # IFS is default now`| 104 | 105 | 106 | ## TODO 107 | * [x] Serve static resources 108 | * [x] Serve binary resources (fonts, images, etc) 109 | * [x] www-form-urlencoded requests 110 | * [x] multipart/form-data (no binary files yet) 111 | * [x] Handle uploaded files 112 | * [x] Figure out binary request bodies 113 | * [x] File uploading progress (JS) 114 | * [x] Access data from application/json requests 115 | * [ ] Access data from application/xml requests 116 | * [x] application/json responses 117 | * [x] Redirect responses 118 | * [x] Page templating 119 | * [ ] Branches in templates 120 | * [ ] Loops in templates 121 | * [ ] Path templates (/user/{ID}) 122 | * [x] Query String parsing 123 | * [ ] Array values in QS 124 | * [x] Cookies 125 | * [x] MySQL 126 | * [x] MySQL migrations 127 | * [x] Content url-en/decoding 128 | * [x] Socat port for concurrency? 129 | * [x] Render colored CLI output as HTML (`ls --color=always`) 130 | * [x] Automatic error handling and reporting 131 | * [x] Colorful logs 132 | * [x] Forbid requests to dot-files (configurable) 133 | * [x] Forbid requests to source code (configurable) 134 | * [x] Project-specific .env files 135 | 136 | ## TODO V1+ 137 | * [ ] Basic/Digest HTTP Authentication 138 | * [ ] Proper caching-related headers (`cache-control`, `expires`, `last-modified`, `etag`) 139 | * [ ] Respond to HEAD/OPTIONS automatically? -------------------------------------------------------------------------------- /bashttpd.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | source libbashttpd/utility.sh 4 | source libbashttpd/bwf.sh 5 | 6 | log "Bashttpd 0.9.1" 7 | 8 | [[ -f .env ]] && loggg "Loading .env" && source .env 9 | 10 | project.Load $1 11 | 12 | mysql.Install 13 | 14 | function maybe_exit { 15 | if [ $1 -gt 0 ]; then 16 | log "Exited with $1" 17 | log "It was nice having you here. Come back one day." 18 | exit 0 19 | fi 20 | } 21 | 22 | function run.netcat { 23 | log "Using ${lcR}${lcLYellow}netcat${lcX} transport ${lcLRed}(not recommended, consider switching to ${lcbgLRed}${lcWhite}socat${lcX}${lcLRed})." 24 | while true; do 25 | netcat -l -k -p $PORT -c "./libbashttpd/handler.sh $1" 26 | maybe_exit $? 27 | done 28 | } 29 | 30 | function run.socat { 31 | log "Using ${lcR}${lcLGreen}socat${lcX} transport." 32 | socat TCP-LISTEN:$PORT,fork,reuseaddr EXEC:"./libbashttpd/handler.sh $1" 33 | maybe_exit $? 34 | } 35 | 36 | case $2 in 37 | 'socat') 38 | sys.Installed socat && run.socat $1 || log "${lcbgLRed}${lcWhite}socat is not installed. Aborting." 39 | ;; 40 | 41 | 'netcat') 42 | sys.Installed netcat && run.netcat $1 || log "${lcbgLRed}${lcWhite}netcat is not installed. Aborting." 43 | ;; 44 | 45 | *) 46 | if sys.Installed socat; then 47 | run.socat $1; 48 | elif sys.Installed netcat; then 49 | run.netcat $1; 50 | else 51 | log "${lcbgLRed}${lcWhite}Either socat or netcat is needed to handle connections, but none is installed. Aborting." 52 | exit 255 53 | fi 54 | ;; 55 | esac 56 | -------------------------------------------------------------------------------- /libbashttpd/bwf.sh: -------------------------------------------------------------------------------- 1 | # Bashttpd Web Framework 2 | 3 | shopt -s expand_aliases 4 | 5 | # Sends an HTTP response status code. 6 | function resp.Status { 7 | [[ -z $__HTTP_STATUS_SENT ]] && echo "HTTP/1.1 $1" && __HTTP_STATUS_SENT=$1 && loggg " ${lcLGray}resp.Status${lcX} HTTP/1.1 ${lcWhite}$1" 8 | } 9 | 10 | # Sends an HTTP response header. 11 | function resp.Header { 12 | echo "$1: $2" 13 | loggg " ${lcLGray}resp.Header${lcX} ${lcYellow}$1${lcX}: ${lcWhite}$2" 14 | } 15 | 16 | # Sends an HTTP response body. 17 | function resp.Body { 18 | echo "" 19 | echo -E "$1" 20 | loggg " ${lcLGray}resp.Body${lcX} ${lcWhite}[...]" 21 | } 22 | 23 | # Responds with file contents. 24 | # Doesn't care about neither content length nor it's type. Expects you to do it. 25 | function resp.File { 26 | echo "" 27 | cat "$1" 28 | loggg " ${lcLGray}resp.File${lcX} ${lcWhite}$1" 29 | } 30 | 31 | # Reads a file, expands variables in it, then responds. 32 | function resp.TemplateFile { 33 | echo "" 34 | 35 | tplFilePath="$PROJECT/.etc/tpl/$1" 36 | tmp=$(mktemp --suffix=.sh) 37 | 38 | echo 'cat < $tmp 39 | cat "$tplFilePath" >> $tmp 40 | echo "" >> $tmp 41 | echo 'END_OF_TEXT' >> $tmp 42 | 43 | source $tmp 44 | loggg " ${lcLGray}resp.TemplateFile${lcX} ${lcWhite}\"$1\"" 45 | } 46 | 47 | # Sets a response cookie. 48 | function resp.Cookie { 49 | resp.Header "Set-Cookie" "$1=$2; Path=/" 50 | } 51 | 52 | # Outputs a value of a single cookie from the $COOKIE header. 53 | function req.Cookie { 54 | IFS_backup="$IFS" 55 | IFS=';' 56 | 57 | read -r -a COOKIES <<< "$COOKIE" 58 | for C in "${COOKIES[@]}"; do 59 | if [[ $C =~ ^$1= ]]; then 60 | echo "$(echo "$C" | sed -r 's/.*'"$1"'=(.*).*/\1/')" 61 | break 62 | fi 63 | done 64 | 65 | IFS="$IFS_backup" 66 | # $(echo -e $COOKIE | sed -n 's/'"$1"'=\(.*\)/\2/p') 67 | } 68 | 69 | # Outputs a single value from the request body of structured 70 | # requests (JSON, form data),regardless of it's Content-Type. 71 | # See actual parsers in libbashttpd/content 72 | # Arguments: 73 | # $1: parameter name. 74 | # $2: optional output reference name. 75 | function req.Data { 76 | if [ -z $CONTENT_TYPE ]; then 77 | echo "" 78 | return 0 79 | fi 80 | 81 | local r 82 | # req.DataImpl must be declared by a content parser in it's own file. 83 | r=$(req.DataImpl $1) 84 | api.Error "req.DataImpl" $? "$r" 85 | yield "$r" $2 86 | } 87 | 88 | # Outputs a temporary file name where contents of the uploaded file is stored. 89 | function req.File { 90 | vn="FILE_$1" 91 | yield ${!vn} 92 | } 93 | 94 | # Outputs original name of the uploaded file. 95 | function req.FileName { 96 | vn="FILENAME_$1" 97 | yield ${!vn} 98 | } 99 | 100 | # Outputs Content-Type of the uploaded file. 101 | function req.FileContentType { 102 | vn="FILECT_$1" 103 | yield ${!vn} 104 | } 105 | 106 | # Outputs a values of a query string parameter. 107 | # Arguments: 108 | # $1: name of the request query string parameter. 109 | # $2: optional reference name to put the value into. 110 | function req.Query { 111 | vn="QS_$1" 112 | yield "${!vn}" "$2" 113 | } 114 | 115 | # A shorthand function to responding with JSONs. Encodes passed data, sends Content-Type. 116 | # In case of enabled upload progress reporting, it responds with a JSONP and stores the response 117 | # under the "X-Bwf-Upload-ID" value. 118 | # Arguments 119 | # $1: a name of a variable containing response data. Not the var itself. 120 | # $2: an optional encoding mode for JSON.EncodeObject & EncodeArray functions. 121 | function resp.JSON { 122 | type=$(reflection.Type $1) 123 | 124 | case $type in 125 | "MAP") 126 | JSON=$(JSON.EncodeObject $1 $2) 127 | ;; 128 | 129 | "ARRAY") 130 | JSON=$(JSON.EncodeArray $1 $2) 131 | ;; 132 | 133 | "STRING") 134 | JSON=$(JSON.EncodeString $1) 135 | ;; 136 | 137 | *) 138 | JSON=$(JSON.EncodePass $1) 139 | ;; 140 | esac 141 | 142 | if [[ ! -z $X_BWF_UPLOAD_ID ]]; then 143 | loggggg "Upload ID is \"$X_BWF_UPLOAD_ID\", responding as JSONP." 144 | resp.Body "bwf.set(\"$X_BWF_UPLOAD_ID\", $JSON);" 145 | resp.Body "console.log(\"set the $X_BWF_UPLOAD_ID\");" 146 | else 147 | resp.Header "Content-Type" "application/json" 148 | resp.Body "$JSON" 149 | fi 150 | } 151 | 152 | # Encodes an associative array as a JSON object. 153 | # Arguments: 154 | # $1: name of the array to encode. Note, it's name, not the array itself. 155 | # $2: an optional "untyped" flag to enable proper JSON types for fields. 156 | # When omitted, the values go to JSON as is, without any additional encoding. 157 | function JSON.EncodeObject { 158 | declare -a JSONFIELDS 159 | decl=$(declare -p $1) 160 | eval "declare -A INPUT=${decl#*=}" 161 | 162 | for IK in "${!INPUT[@]}"; do 163 | SRCVAL=${INPUT[$IK]} 164 | JSONVAL=$SRCVAL 165 | 166 | if [[ $2 == 'untyped' ]]; then 167 | type=$(reflection.Type SRCVAL) 168 | # log "JSON.EncodeObject Type of SRCVAL is $type" 169 | 170 | case $type in 171 | "STRING") 172 | JSONVAL="$(JSON.EncodeString SRCVAL)" 173 | ;; 174 | 175 | *) 176 | JSONVAL="$(JSON.EncodePass SRCVAL)" 177 | ;; 178 | esac 179 | fi 180 | 181 | JSONFIELDS+=("\"$IK\":$JSONVAL") 182 | done 183 | 184 | IFS='' 185 | JSON=$(array.join ", " "${JSONFIELDS[@]}") 186 | JSON="{$JSON}" 187 | yield "$JSON" 188 | } 189 | 190 | # Encodes an associative array as a JSON array. 191 | # Arguments: 192 | # $1: name of the array to encode. Note, it's name, not the array itself. 193 | # $2: an optional "untyped" flag to enable proper JSON types for values. 194 | # When omitted, the values go to JSON as is, without any additional encoding. 195 | function JSON.EncodeArray { 196 | declare -a JSONFIELDS 197 | decl=$(declare -p $1) 198 | # This reverses the key order. 199 | eval "declare -A INPUT=${decl#*=}" 200 | 201 | for IK in "${!INPUT[@]}"; do 202 | SRCVAL=${INPUT[$IK]} 203 | JSONVAL=$SRCVAL 204 | type=$(reflection.Type SRCVAL) 205 | 206 | if [[ $2 == 'untyped' ]]; then 207 | case $type in 208 | "STRING") 209 | JSONVAL=$(JSON.EncodeString SRCVAL) 210 | ;; 211 | 212 | *) 213 | JSONVAL=$(JSON.EncodePass SRCVAL) 214 | ;; 215 | esac 216 | fi 217 | 218 | JSONFIELDS+=("$JSONVAL") 219 | done 220 | 221 | IFS='' 222 | JSON=$(array.join ", " ${JSONFIELDS[@]}) 223 | JSON="[$JSON]" 224 | 225 | yield "$JSON" 226 | } 227 | 228 | # Encodes a value as a JSON string. 229 | # Takes name of the variable as a argument. 230 | # It's name, not the variable itself. 231 | function JSON.EncodeString { 232 | s=$1 233 | val=$(eval echo "\$${s}") 234 | val=${val//$'\n'/\\n} 235 | val=${val//'"'/\\\"} 236 | yield "\"$val\"" 237 | } 238 | 239 | # Encodes a value as a JSON value, i.e. doesn't change it in any way. 240 | # Takes name of the variable as a argument. 241 | # It's name, not the variable itself. 242 | function JSON.EncodePass { 243 | val=$(eval echo \$${1}) 244 | [[ -z $val ]] && val='""' 245 | yield "$val" 246 | } 247 | 248 | TIMER_LAST=$(date +%s) 249 | 250 | # Outputs current timestamp in seconds. 251 | function sys.Time { 252 | date +%s 253 | } 254 | 255 | # Outputs the time elapsed since previous call to this function. 256 | function sys.TimeElapsed { 257 | T=$(date +%s) 258 | DT=$((T-TIMER_LAST)) 259 | TIMER_LAST=$T 260 | yield "$DT" 261 | } 262 | 263 | # Checks if a progrma is installed in the system. 264 | # For use in if conditions. 265 | function sys.Installed { 266 | local prog=$(command -v $1) 267 | if [[ -z $prog ]]; then 268 | return 255 269 | else 270 | return 0 271 | fi 272 | } 273 | 274 | # Executes a MySQL binary and provides auth credentials. 275 | function mysql.Run { 276 | if ! sys.Installed mysql; then 277 | error "MySQL is not installed." 278 | return 255 279 | fi 280 | 281 | [[ ! -z $MYSQL_PASSWORD ]] && PSWD="-p$MYSQL_PASSWORD" 282 | loggggg "mysql.Run: mysql --host $MYSQL_HOST -u $MYSQL_USER $PSWD $@" 283 | mysql --host $MYSQL_HOST -u $MYSQL_USER $PSWD $@ 2>&1 284 | } 285 | 286 | # Executes a MySQL query. 287 | # Arguments: 288 | # $1: a query to execute. 289 | # $2: optional reference name to store the result 290 | function mysql.Query { 291 | local r 292 | r=$(mysql.Run $MYSQL_DB -e "$1") 293 | local __xc=$? 294 | yield "$r" $2 295 | return $__xc 296 | } 297 | 298 | # Executes a SELECT MySQL query with a WHERE sattement. 299 | # Arguments: 300 | # $1: a table name to select rows from 301 | # $2: a 'WHERE' clause without the "WHERE" keyword 302 | # $3: optional reference name to store the result 303 | function mysql.Select { 304 | local r 305 | r=$(mysql.Query "SELECT * FROM $1 WHERE $2") 306 | api.Error "mysql.Select" $? "$r" 307 | 308 | yield "$r" $3 309 | } 310 | 311 | # Executes a "SELECT *" MySQL query, returns all available rows. 312 | # Arguments: 313 | # $1: a table name to select rows from 314 | # $2: optional reference name to store the result 315 | function mysql.All { 316 | local r 317 | r=$(mysql.Query "SELECT * FROM $1") 318 | api.Error "mysql.Select" $? "$r" 319 | 320 | yield "$r" $2 321 | } 322 | 323 | # Iterates over a set of rows returned from mysql. 324 | # Context: 325 | # $ROWS - raw text output from mysql 326 | alias mysql.foreach=" 327 | IFS=\$' \\t\\r\\n' 328 | declare -a sqlHeader 329 | declare -a sqlLines 330 | declare -a sqlColumns 331 | readarray -t AROWS <<< \$ROWS 332 | for i in \${!AROWS[@]}; do 333 | [[ \$i == 0 ]] && sqlHeader=(\${AROWS[0]}) || sqlLines+=(\"\${AROWS[\$i]}\") 334 | done; for lI in \${!sqlLines[@]};" 335 | 336 | 337 | # Declares a local associative array and puts mysql row data in it. 338 | # Context: 339 | # must be executed within a mysql.foreach loop 340 | # declares a $row associative array with row data inside 341 | alias mysql.row=" 342 | IFS_backup=\$IFS 343 | IFS='\t' 344 | readarray -d $'\\t' -t sqlColumns <<< \${sqlLines[\$lI]} 345 | declare -A row 346 | for colI in \${!sqlColumns[@]}; do 347 | row[\${sqlHeader[\$colI]}]="\${sqlColumns[\$colI]}" 348 | done 349 | IFS=\$IFS_backup" 350 | 351 | # Inserts a row into a MySQL table. 352 | # Arguments: 353 | # $1: table name. 354 | # $2: name of the associative array which contains column data. 355 | # $3: optional reference name to store the ID of the inserted record. 356 | function mysql.Insert { 357 | declare -a keys 358 | declare -a vals 359 | array.getbyref2 360 | array.foreach; do 361 | keys+=("$key") 362 | #TODO: escape quotes and stuff 363 | vals+=("\"${E[$key]}\"") 364 | done 365 | 366 | skeys=$(array.join ', ' ${keys[@]}) 367 | skeys="($skeys)" 368 | svals=$(array.join ', ' ${vals[@]}) 369 | svals="($svals)" 370 | 371 | local ROWS 372 | ROWS=$(mysql.Query "INSERT INTO $1 $skeys VALUES $svals; SELECT LAST_INSERT_ID() as ID;") 373 | api.Error "mysql.Insert" $? "$ROWS" 374 | 375 | mysql.foreach do 376 | mysql.row 377 | yield "${row[ID]}" $3 378 | return 0 379 | done 380 | } 381 | 382 | # Installs a project MySQL database. 383 | function mysql.Install { 384 | IFS='' 385 | hasDB=$(mysql.Run -e "SHOW DATABASES;" | grep -sw $MYSQL_DB) 386 | 387 | if [[ -z $hasDB ]]; then 388 | log "Installing the database." 389 | 390 | log "Creating the '$MYSQL_DB' database..." 391 | mysql.Run -e "CREATE DATABASE IF NOT EXISTS $MYSQL_DB" 392 | 393 | local projDBSQL="$PROJECT/.etc/db.sql" 394 | if [[ -f $projDBSQL ]]; then 395 | log "Executing the $projDBSQL..." 396 | mysql.Run $MYSQL_DB < $projDBSQL 397 | fi 398 | 399 | log "Done." 400 | else 401 | loggg "The DB is in place." 402 | fi 403 | } 404 | 405 | # Checks the passed exit code and reports 500 to the client in case it's not 0. 406 | # Arguments: 407 | # $1: an operation name, free form. 408 | # $2: operation's exit code. 409 | # $3: an error message. 410 | function api.Error { 411 | IFS='' 412 | if [[ $2 != 0 ]]; then 413 | log " ${lcbgLRed}${lcWhite}Internal Server Error.${lcX}" 414 | log " ${lcLRed}$1 exit code is $2." 415 | log " ${lcLRed}$3" 416 | log " ${lcLRed}Reporting to client." 417 | 418 | resp.Status 500 419 | declare -A ERRRESP=( 420 | [command]="$1" 421 | [code]="$2" 422 | [message]="$3" 423 | ) 424 | resp.JSON ERRRESP untyped 425 | log " I can't even." 426 | exit 0 427 | fi 428 | } 429 | 430 | # Formats Bash colored output as HTML. 431 | # Arguments: 432 | # $@: an array of colored CLI output lines. 433 | function resp.CLI { 434 | IFS=$'\n' 435 | LINES=($@) 436 | for line in ${LINES[@]}; do 437 | line=${line//$'\e'\[m/""} 438 | line=${line//$'\e'\[K} # No idea what's that 439 | line=${line//$'\r'} 440 | line=${line// /" "} 441 | 442 | # Matches the \e[X;XX;XXm sequences 443 | RX=$'\e\[([0-9]{1,2}\;){0,1}([0-9]{1,2}\;){0,1}[0-9]+m' 444 | while [[ $line =~ $RX ]]; do 445 | 446 | local class="" 447 | fmtOptsLine=${BASH_REMATCH[0]:2} 448 | fmtOptsLine=${fmtOptsLine::-1} 449 | readarray -t -d ';' fmtOpts <<< $fmtOptsLine 450 | for fmt in ${fmtOpts[@]}; do 451 | class="$class fmt-$fmt" 452 | done 453 | 454 | HTMLtag="" 455 | 456 | # \e[0m 457 | [[ $fmtOptsLine == "0" ]] && HTMLtag="" 458 | 459 | line=${line//${BASH_REMATCH[0]}/$HTMLtag} 460 | done 461 | 462 | HTML="$HTML

$line

\n" 463 | done 464 | 465 | echo -e "$HTML" 466 | } 467 | 468 | # Initializes various project-wide things. 469 | function project.Load { 470 | PROJECT=$(realpath $1) 471 | DOMAIN=${PROJECT##*/} 472 | [[ -f $1/.etc/.env ]] && loggg "Loading $(realpath $1/.etc/.env)" && source $1/.etc/.env 473 | 474 | local URL=$(project.URL) 475 | 476 | loggg "Project directory is ${lcWhite}$PROJECT" 477 | loggg "Project URL is ${lcU}${lcCyan}$URL${lcX}" 478 | } 479 | 480 | # Yields a fully qualified project URL, with domain name and port number. 481 | # All arguments are joined with / and used as path. 482 | function project.URL { 483 | [[ $PORT != 80 ]] && uPORT=":$PORT" 484 | 485 | local path=$(array.join '/' $@) 486 | echo "http://$DOMAIN$uPORT/$path" 487 | } 488 | 489 | # A regular HTTP redirect response. 490 | # Arguments: 491 | # $1: A URL to relocate useragent to. 492 | # $2: An optional 30* HTTP status code. 493 | function resp.Redirect { 494 | resp.Status ${2:-302} 495 | resp.Header "Location" "$1" 496 | resp.Body "" 497 | } 498 | -------------------------------------------------------------------------------- /libbashttpd/content/application/json.sh: -------------------------------------------------------------------------------- 1 | _IFS=$IFS 2 | IFS=$'\r' 3 | CL=0 4 | 5 | # Reading body, 1 char at a time 6 | # Regular read can't get the last line because of missing newline on this Content-Type 7 | if [ -z ${CONTENT_LENGTH+x} ]; then 8 | : 9 | else 10 | while [ $CL -lt $CONTENT_LENGTH ]; do 11 | read -n1 -r CHAR 12 | BODY="$BODY$CHAR" 13 | let CL=CL+1 14 | done; 15 | 16 | # Debug dump 17 | [[ ! -z $DEBUG_DUMP_BODY ]] && echo -n "$BODY" > $DEBUG_DUMP_BODY 18 | fi 19 | 20 | # An implementation of req.Data. 21 | function req.DataImpl { 22 | if ! sys.Installed jq; then 23 | error "jq is not installed." 24 | return 255 25 | fi 26 | 27 | Q=$1 28 | [[ ${Q:0:1} != '.' ]] && Q=".$Q" 29 | local r 30 | r=$(echo -nE "$BODY" | jq -r $Q 2>&1) 31 | local __xc=$? 32 | yield "$r" 33 | return $__xc 34 | } 35 | 36 | IFS=$_IFS -------------------------------------------------------------------------------- /libbashttpd/content/application/x-www-form-urlencoded.sh: -------------------------------------------------------------------------------- 1 | CL=0 2 | 3 | # Reading body, 1 char at a time 4 | # Regular read can't get the last line because of missing newline on this Content-Type 5 | if [ -z ${CONTENT_LENGTH+x} ]; then 6 | : 7 | else 8 | while [ $CL -lt $CONTENT_LENGTH ]; do 9 | read -n1 CHAR 10 | BODY="$BODY$CHAR" 11 | let CL=CL+1 12 | done; 13 | 14 | # Debug dump 15 | [[ ! -z $DEBUG_DUMP_BODY ]] && echo -nE $BODY > $DEBUG_DUMP_BODY 16 | fi 17 | 18 | IFS_backup="$IFS" 19 | IFS='&' 20 | 21 | read -r -a FIELDS <<< "$BODY" 22 | for FIELD in "${FIELDS[@]}"; do 23 | fieldName=$(echo -En "$FIELD" | cut -d "=" -f 1) 24 | fieldValue=$(echo -En "$FIELD" | cut -d "=" -f 2) 25 | 26 | fieldValue=$(HTTP.urldecode $fieldValue) 27 | var "DATA_$fieldName" "$fieldValue" 28 | done 29 | 30 | IFS="$IFS_backup" 31 | 32 | # An implementation of req.Data. 33 | function req.DataImpl { 34 | vn="DATA_$1" 35 | yield ${!vn} 36 | } 37 | -------------------------------------------------------------------------------- /libbashttpd/content/multipart/form-data.sh: -------------------------------------------------------------------------------- 1 | _IFS=$IFS 2 | IFS=$'' 3 | LANG=C 4 | LC_ALL=C 5 | 6 | CL=0 7 | 8 | CHUNK_SIZE=2000 9 | CLT=$CHUNK_SIZE 10 | 11 | function renderProgress { 12 | echo -en "\r Read ${lcCyan}$CL/$CONTENT_LENGTH${lcX} bytes " >&2 13 | local n=$(($CLT/$CHUNK_SIZE)) 14 | local mark="=" 15 | local numMarks=40 16 | local markCLT=$(($CONTENT_LENGTH/$numMarks)) 17 | 18 | echo -en "${lcLCyan}[${lcLMagenta}" >&2 19 | 20 | for ((i=0; i<$CL; i+=$markCLT)); do 21 | echo -En "$mark" >&2 22 | done 23 | 24 | for ((i=$CL; i<$CONTENT_LENGTH; i+=$markCLT)); do 25 | echo -En " " >&2 26 | done 27 | 28 | echo -en "${lcLCyan}] " >&2 29 | } 30 | 31 | # Reads from input until the supplied predicate function returns 0, 32 | # and dumps the contents to a specified file. 33 | # Usage: 34 | # dumpUntil CRLFFound $tmp - reads until found a \r\n sequence and dumps the data to the $tmp file 35 | function dumpUntil { 36 | loggggg " Dumping fast to ${lcWhite}$2${lcX} until ${lcBlue}$1" 37 | LINE="" 38 | # loggggg "" 39 | 40 | if [[ ! -z $X_BWF_UPLOAD_ID ]]; then 41 | # Going to report the progress 42 | loggggg " Upload ID is \"$X_BWF_UPLOAD_ID\", going to report the upload progress to the client." 43 | echo "HTTP/1.1 200" 44 | echo "Content-Type: application/javascript" 45 | echo "" 46 | fi 47 | 48 | while [ $CL -lt $CONTENT_LENGTH ]; do 49 | read -r -d '' -n1 CHAR 50 | let CL=CL+1 51 | 52 | # Slashes interfere with \x00 53 | [[ $CHAR == "\\" ]] && CHAR="\x5c" 54 | [[ -z $CHAR ]] && CHAR="\x00" 55 | 56 | LINE="$LINE$CHAR" 57 | 58 | # Making sure the chunking won't chunk the last content boundary line, 59 | # and CB parsers are able to detect it, so leaving a padding. 60 | CLREM=$(($CONTENT_LENGTH-$CL)) 61 | CB_PADDING=$((${#CONTENT_BOUNDARY}+10)) 62 | if [[ $CL -ge $CLT ]] && [[ $CLREM -gt $CB_PADDING ]]; then 63 | echo -en $LINE >> $2 64 | LINE="" 65 | 66 | CLT=$((CLT+CHUNK_SIZE)) 67 | renderProgress; 68 | 69 | if [[ ! -z $X_BWF_UPLOAD_ID ]]; then 70 | # Reporting the progress as JS to the client. 71 | echo "bwf.renderUploadProgress($CL, $CONTENT_LENGTH);" 72 | fi 73 | fi 74 | 75 | # Testing & breaking 76 | if $1; then 77 | if [[ ${#LINE} > 0 ]]; then 78 | echo -en $LINE >> $2 79 | LINE="" 80 | fi 81 | 82 | renderProgress; 83 | 84 | return 0 85 | fi 86 | done; 87 | 88 | return 255 89 | } 90 | 91 | # Reads from input until the supplied predicate function returns 0 92 | # Usage: 93 | # readUntil CRLFFound - reads until found a \r\n sequence 94 | function readUntil { 95 | LINE="" 96 | HEXLINE="" 97 | while [ $CL -lt $CONTENT_LENGTH ]; do 98 | read -r -d '' -n1 CHAR 99 | let CL=CL+1 100 | LINE="$LINE$CHAR" 101 | 102 | let CLR=$CL%500 103 | if [[ $CLR == 0 ]]; then 104 | local safechar=$(echo -n "$CHAR" | tr '\n' '\\') 105 | loggggg " READ $CL/$CONTENT_LENGTH $hexchar ($safechar)" 106 | fi 107 | 108 | # Testing & breaking 109 | if $1; then 110 | return 0 111 | fi 112 | done; 113 | 114 | return 255 115 | } 116 | 117 | # A predicate function for readUntil. 118 | # Stops when a content boundary is encountered. 119 | function BoundaryFound { 120 | if [[ $LINE =~ "$CONTENT_BOUNDARY"$ ]]; then 121 | return 0 122 | fi 123 | 124 | return 255 125 | } 126 | 127 | # A predicate function for readUntil. 128 | # Stops when a CRLF is encountered. 129 | function CRLFFound { 130 | if [[ ${LINE:${#LINE}-2:2} == $'\r\n' ]]; then 131 | LINE=${LINE::-2} 132 | return 0 133 | fi 134 | 135 | return 255 136 | } 137 | 138 | # A predicate function for readUntil. 139 | # Stops when a CRLF followed by a content boundary is encountered. 140 | function CRLFBoundaryFound { 141 | # The '--' are required by RFC https://www.w3.org/Protocols/rfc1341/7_2_Multipart.html 142 | CB="--$CONTENT_BOUNDARY" 143 | let LEN=${#CB}+2 144 | SEP=$'\r\n'"$CB" 145 | if [[ ${LINE:${#LINE}-$LEN:$LEN} == $SEP ]]; then 146 | LINE=${LINE::-$LEN} 147 | return 0 148 | fi 149 | 150 | return 255 151 | } 152 | 153 | # Looks for a Content-Disposition line and extract param eter and file names from it. 154 | function parseContentDisposition { 155 | if [[ $LINE =~ Content-Disposition: ]]; then 156 | logggg " Found a Content-Disposition" 157 | # Found a content disposition, extracting a parameter name from it 158 | CURRENT_PARAMETER=$(echo -e $LINE | sed -rn 's/.* name\=\"([^"]*)\";{0,1}.*/\1/p') 159 | logggg " Found a parameter ${lcGreen}\"$CURRENT_PARAMETER\"" 160 | 161 | if [[ $LINE =~ ' 'filename= ]]; then 162 | # Found a 'filename=' substring, extracting a file name from it 163 | CURRENT_FILENAME=$(echo -e $LINE | sed -rn 's/.* filename\=\"([^"]*)\";{0,1}.*/\1/p') 164 | logggg " Found a filename ${lcBlue}\"$CURRENT_FILENAME\"" 165 | fi 166 | 167 | NEXT_PARSER=parseCRLF_or_ContentType 168 | 169 | return 0 170 | fi 171 | 172 | return 255 173 | } 174 | 175 | function parseCRLF { 176 | if [[ -z $LINE ]]; then 177 | logggg " Found a CRLF, proceeding to the content body" 178 | # Not setting NEXT_PARSER because parseContent will read the input itself. 179 | parseContent 180 | return 0 181 | fi 182 | 183 | return 255 # evaluates as false in parseCRLF_or_ContentType 184 | } 185 | 186 | # Reads a part of the request body until encounters a content boundary value. 187 | function parseContent { 188 | logggg " Reading the request body" 189 | T=$(sys.TimeElapsed) 190 | 191 | if [[ -z $CURRENT_FILENAME ]]; then 192 | readUntil CRLFBoundaryFound 193 | T=$(sys.TimeElapsed) 194 | loggggg " Took $T seconds to read the request body." 195 | 196 | # Regular values are stored as variables. 197 | var "DATA_$CURRENT_PARAMETER" "$LINE" 198 | loggg " Set ${lcR}${lcGreen}$CURRENT_PARAMETER${lcX} to ${lcWhite}\"$LINE\"${lcX}" 199 | else 200 | # Uploaded files are stored in /tmp... 201 | tmp=$(mktemp) 202 | dumpUntil CRLFBoundaryFound $tmp 203 | T=$(sys.TimeElapsed) 204 | loggg " " 205 | loggggg " Took $T seconds to dump the request body." 206 | 207 | # ...and their filenames are stored as variables. 208 | var "FILE_$CURRENT_PARAMETER" $tmp 209 | var "FILENAME_$CURRENT_PARAMETER" $CURRENT_FILENAME 210 | var "FILECT_$CURRENT_PARAMETER" $CURRENT_CONTENT_TYPE 211 | loggg " Saved ${lcR}${lcGreen}\"$CURRENT_PARAMETER\"${lcX} as ${lcWhite}$tmp${lcX}" 212 | fi 213 | 214 | NEXT_PARSER=parseContentDisposition_or_Fin 215 | 216 | CURRENT_FILENAME="" 217 | CURRENT_PARAMETER="" 218 | CURRENT_CONTENT_TYPE="" 219 | } 220 | 221 | # Multipart data sometimes has it's own Content-Type 222 | function parseContentType { 223 | if [[ $LINE =~ Content-Type: ]]; then 224 | CURRENT_CONTENT_TYPE=$(echo -nE "$LINE" | sed -r 's/\s+//g' | sed -\n 's/.*:\s*\(.*\)/\1/p') 225 | logggg " Found a Content-Type of '$CURRENT_CONTENT_TYPE', proceeding to a CRLF" 226 | NEXT_PARSER=parseCRLF 227 | return 0 228 | fi 229 | 230 | return 255 231 | } 232 | 233 | function parseNothing { 234 | return 0 235 | } 236 | 237 | function parseCRLF_or_ContentType { 238 | if ! parseCRLF; then 239 | parseContentType 240 | fi 241 | } 242 | 243 | function parseContentDisposition_or_Fin { 244 | if ! parseContentDisposition; then 245 | parseFin 246 | fi 247 | } 248 | 249 | function parseFin { 250 | if [[ $LINE = "--" ]]; then 251 | logggg " Found the request end."x 252 | renderProgress; 253 | log "" 254 | NEXT_PARSER=parseNothing 255 | return 0 256 | fi 257 | } 258 | 259 | NEXT_PARSER=parseContentDisposition 260 | 261 | if ! [ -z ${CONTENT_LENGTH+x} ]; then 262 | readUntil BoundaryFound 263 | let LI=0 264 | 265 | T1=$(sys.Time) 266 | while readUntil CRLFFound; do 267 | let LI=$LI+1 268 | logggg "" 269 | loggggg "${lcLGray}Line #$LI is ($LINE) (${#LINE} chars)" 270 | logggg "The parser is ${lcBlue}$NEXT_PARSER" 271 | 272 | [[ ! -z $NEXT_PARSER ]] && $NEXT_PARSER 273 | 274 | done 275 | 276 | T2=$(sys.Time) 277 | loggggg "Done in $(($T2-$T1)) seconds." 278 | 279 | # Debug dump 280 | [[ ! -z $DEBUG_DUMP_BODY ]] && echo -n "$BODY" > $DEBUG_DUMP_BODY 281 | fi 282 | 283 | # An implementation of req.Data. 284 | function req.DataImpl { 285 | vn="DATA_$1" 286 | yield ${!vn} 287 | } 288 | 289 | IFS=$_IFS -------------------------------------------------------------------------------- /libbashttpd/handler.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | source libbashttpd/utility.sh 4 | source libbashttpd/request.sh 5 | source libbashttpd/router.sh 6 | source libbashttpd/bwf.sh 7 | 8 | [[ -f .env ]] && loggg "Loading .env" && source .env 9 | 10 | project.Load $1 11 | 12 | HTTP.readHeaders 13 | HTTP.normalizeHeaders 14 | HTTP.readBody 15 | 16 | router 17 | 18 | loggg "" 19 | loggg "Fin." -------------------------------------------------------------------------------- /libbashttpd/request.sh: -------------------------------------------------------------------------------- 1 | IFS='' 2 | 3 | rxHeader='^([a-zA-Z-]+)\s*:\s*(.*)' 4 | rxMethod='^(GET|POST|PUT|DELETE|OPTIONS) +(.*) +HTTP' 5 | 6 | # Reads HTTP request headers. 7 | function HTTP.readHeaders { 8 | loggg "${lcLGray}Reading request headers" 9 | 10 | # Debug dump (clear) 11 | [[ ! -z $DEBUG_DUMP_HEADERS ]] && echo -nE "" > $DEBUG_DUMP_HEADERS 12 | while read INPUT; do 13 | # Debug dump 14 | [[ ! -z $DEBUG_DUMP_HEADERS ]] && echo -nE $INPUT >> $DEBUG_DUMP_HEADERS 15 | 16 | if [[ $INPUT =~ $rxHeader ]]; then 17 | headerName=${BASH_REMATCH[1]} 18 | headerValue=${BASH_REMATCH[2]} 19 | 20 | # Trimming off whitespace 21 | headerValue="$(echo -e "${headerValue}" | sed -r 's/\s+//g')" 22 | 23 | loggg " ${lcYellow}$headerName${lcX}: ${lcLGray}$headerValue${lcX}" 24 | 25 | # Replacing - with _ in header names and uppercasing them 26 | headerName="$(echo -e "${headerName}" | sed -r 's/-/_/g' | sed -e 's/\(.*\)/\U\1/g')" 27 | 28 | # This creates variables named after header names with header values 29 | var $headerName "$headerValue" 30 | 31 | # Figuring out the request method used 32 | elif [[ $INPUT =~ $rxMethod ]]; then 33 | reqMethod=${BASH_REMATCH[1]} 34 | reqURL=${BASH_REMATCH[2]} 35 | reqPath=${reqURL%%\?*} 36 | reqQuery=${reqURL#*\?} 37 | 38 | [[ $reqQuery == $reqPath ]] && reqQuery="" 39 | 40 | log "Request is ${lcLYellow}$reqMethod${lcX} @ ${lcU}${lcLCyan}$reqPath${lcX}" 41 | 42 | if [[ ! -z $reqQuery ]]; then 43 | logg "Query string is ${lcCyan}$reqQuery" 44 | 45 | # Parsing the query string. 46 | readarray -t -d '&' QSA <<< "$reqQuery" 47 | for QSP in ${QSA[@]}; do 48 | # Somehow this fixes that weird trailing \n 49 | QSP=$(echo "${QSP}") 50 | readarray -t -d '=' QSKV <<< "$QSP" 51 | QSK=$(echo "${QSKV[0]}") 52 | QSV=$(echo "${QSKV[1]}") 53 | QSK=$(HTTP.urldecode $QSK) 54 | QSV=$(HTTP.urldecode $QSV) 55 | var "QS_$QSK" "$QSV" 56 | 57 | loggg " ${lcCyan}$QSK${lcX} = ${lcLGray}$QSV${lxC}" 58 | done 59 | fi 60 | 61 | # Done with headers 62 | else 63 | loggg "" 64 | break 65 | fi 66 | done 67 | } 68 | 69 | # Pulls extra info from some headers' values, like content boundaries, strips unused stuff. 70 | function HTTP.normalizeHeaders { 71 | # Figuring out the content boundary in case we have a multipart/form-data Content-Type 72 | if [[ $CONTENT_TYPE =~ ^multipart\/form\-data ]]; then 73 | CONTENT_BOUNDARY="$(echo $CONTENT_TYPE | sed -n 's/.*data\;boundary=\(.*\)/\1/p')" 74 | fi 75 | 76 | # Cleaning Content-Type if it has stuff after ; 77 | if [[ $CONTENT_TYPE =~ \; ]]; then 78 | CONTENT_TYPE="$(echo $CONTENT_TYPE | sed -n 's/\(.*\);.*/\1/p')" 79 | fi 80 | } 81 | 82 | # Reads an HTTP request body contents. Different Content-Types must be read & parsed differently, 83 | # so it relies on specific implementations of body parsers. 84 | function HTTP.readBody { 85 | if ! [[ -z $CONTENT_TYPE ]] && [[ $CONTENT_LENGTH -gt 0 ]]; then 86 | loggg "${lcLGray}Reading request body" 87 | 88 | # Choosing a parser for the rest of request data based on Content-Type 89 | parserFile="libbashttpd/content/$CONTENT_TYPE.sh" 90 | 91 | if [[ -f $parserFile ]]; then 92 | source $parserFile 93 | else 94 | log "${lcbgLRed}${lcWhite}The Content-Type \"$CONTENT_TYPE\" is not supported yet. Please implement and submit a pull request @ github.com/x1n13y84issmd42/bashttpd${lcX}" 95 | fi 96 | 97 | loggg "" 98 | fi 99 | } -------------------------------------------------------------------------------- /libbashttpd/router.sh: -------------------------------------------------------------------------------- 1 | # Routing of requests happens here. 2 | # It works in 3 ways: 3 | # If request path exactly matches a file within the $PROJECT directory - serve the file as it is; 4 | # If request path exactly matches a directory within the $PROJECT directory - serve "index.html" from there; 5 | # Otherwise it concatenates the request path and method, adds a trailing ".sh", 6 | # then tries to execute the result as a controller script. 7 | function router() { 8 | ctrler="$PROJECT$reqPath/$reqMethod.sh" 9 | staticFile="$PROJECT$reqPath" 10 | 11 | if [ -f "$ctrler" ]; then 12 | log "${lcLGray}Executing the controller ${lcBlue}$PROJECT${lcLCyan}$reqPath/${lcX}${lcLYellow}$reqMethod${lcBlue}.sh" 13 | # This must be here in order for POST variables with spaces 14 | # to expand in templates correctly 15 | IFS=$'' 16 | source $ctrler 17 | 18 | elif [ -f "$staticFile" ] && safeToServeStatically "$staticFile"; then 19 | log "${lcLGray}Serving the static file ${lcBlue}$PROJECT${lcLCyan}$reqPath" 20 | serveStatic $staticFile 21 | 22 | elif [ -d "$staticFile" ] && [ -f "$staticFile/index.html" ]; then 23 | log "${lcLGray}Serving the static file ${lcBlue}$PROJECT${lcLCyan}$reqPath${lcBlue}/index.html" 24 | serveStatic "$staticFile/index.html" 25 | 26 | else 27 | log "404 Not Found" 28 | resp.Status 404 29 | resp.Header "Content-Type" "text/html" 30 | resp.Body "$reqPath Was Not Found" 31 | 32 | fi 33 | } 34 | 35 | # Checks the request path and a path BWF has chosen to serve 36 | # statically for various criterias that may make serving impossible. 37 | # Examples are dotfiles & handler scripts. 38 | # Arguments: 39 | # $1: the path BWF decided to serve. 40 | function safeToServeStatically { 41 | # Checking the path for dotfiles 42 | if [[ $1 =~ \/\. && $SERVE_DOTFILES == 0 ]]; then 43 | logg "${lcLRed}Requests to dotfiles are forbidden for security reasons." 44 | return 255 45 | fi 46 | 47 | # Checking if the path ends up with a handler script 48 | if [[ $1 =~ (GET|POST|PUT|DELETE|OPTIONS).sh$ && $SERVE_HANDLER_SCRIPTS == 0 ]]; then 49 | logg "${lcLRed}Requests to handler scripts are forbidden for security reasons." 50 | return 255 51 | fi 52 | 53 | return 0 54 | } 55 | 56 | # Serves static files from file system. 57 | # Tries to guess Content-Type from their extensions. 58 | function serveStatic() { 59 | filePath=$1 60 | fileName=$(basename "$filePath") 61 | fileExt="${fileName##*.}" 62 | fileMIMEType=$(file -b --mime-type "$filePath") 63 | fileSize=$(stat --printf="%s" "$filePath") 64 | 65 | resp.Status "200" 66 | 67 | resp.Header "Content-Length" $fileSize 68 | 69 | case $fileExt in 70 | # Somehow `file --mime-type` recognizes css files as text/x-asm 71 | "css") 72 | resp.Header "Content-Type" "text/css" 73 | ;; 74 | 75 | *) 76 | resp.Header "Content-Type" "$fileMIMEType" 77 | ;; 78 | esac 79 | 80 | resp.File $filePath 81 | } -------------------------------------------------------------------------------- /libbashttpd/utility.sh: -------------------------------------------------------------------------------- 1 | # Default value, override it in the .env file 2 | LOG_VERBOSITY=1 3 | 4 | # Colors & styles 5 | lc0="\e[0m" 6 | 7 | lc1="\e[1m" 8 | lc2="\e[2m" 9 | lc4="\e[4m" 10 | lc5="\e[5m" 11 | lc7="\e[7m" 12 | lc8="\e[8m" 13 | 14 | lcB=$lc1 15 | lcD=$lc2 16 | lcU=$lc4 17 | lcL=$lc5 18 | lcR=$lc7 19 | lcH=$lc8 20 | 21 | lcBlack="\e[30m" 22 | lcRed="\e[31m" 23 | lcGreen="\e[32m" 24 | lcYellow="\e[33m" 25 | lcBlue="\e[34m" 26 | lcMagenta="\e[35m" 27 | lcCyan="\e[36m" 28 | lcLGray="\e[37m" 29 | 30 | lcDGray="\e[90m" 31 | lcLRed="\e[91m" 32 | lcLGreen="\e[92m" 33 | lcLYellow="\e[93m" 34 | lcLBlue="\e[94m" 35 | lcLMagenta="\e[95m" 36 | lcLCyan="\e[96m" 37 | lcWhite="\e[97m" 38 | 39 | lcbgBlack="\e[40m" 40 | lcbgRed="\e[41m" 41 | lcbgGreen="\e[42m" 42 | lcbgYellow="\e[43m" 43 | lcbgBlue="\e[44m" 44 | lcbgMagenta="\e[45m" 45 | lcbgCyan="\e[46m" 46 | lcbgLGray="\e[47m" 47 | 48 | lcbgDGray="\e[100m" 49 | lcbgLRed="\e[101m" 50 | lcbgLGreen="\e[102m" 51 | lcbgLYellow="\e[103m" 52 | lcbgLBlue="\e[104m" 53 | lcbgLMagenta="\e[105m" 54 | lcbgLCyan="\e[106m" 55 | lcbgWhite="\e[107m" 56 | 57 | lcX="$lc0$lcDGray" 58 | 59 | lcEm="$lcWhite" 60 | 61 | # A pinch of syntatic sugar for declaring and initializing variables 62 | function var { 63 | printf -v $1 "%s" "$2" 64 | } 65 | 66 | # Outputs to the host's stderr 67 | function log { 68 | _IFS=$IFS 69 | IFS='' 70 | # [[ $LOG_VERBOSITY -ge 1 ]] && printf "%s " $@ >&2 && echo "" >&2 71 | [[ $LOG_VERBOSITY -ge 1 ]] && echo -e "${lcX}" $@ "\e[0m" >&2 72 | IFS=$_IFS 73 | return 0 74 | } 75 | 76 | # Verbose logging 77 | function logg { 78 | [[ $LOG_VERBOSITY -ge 2 ]] && log $@ 79 | return 0 80 | } 81 | 82 | # Even more verbose logging 83 | function loggg { 84 | [[ $LOG_VERBOSITY -ge 3 ]] && log $@ 85 | return 0 86 | } 87 | 88 | # Slightly annoying logging 89 | function logggg { 90 | [[ $LOG_VERBOSITY -ge 4 ]] && log $@ 91 | return 0 92 | } 93 | 94 | # Absolutely annoying chatter 95 | function loggggg { 96 | [[ $LOG_VERBOSITY -ge 5 ]] && log $@ 97 | return 0 98 | } 99 | 100 | function error { 101 | echo -En "$1" 102 | } 103 | 104 | # Like `return` in other languages, capture it with $() 105 | function yield { 106 | if [[ -z $2 ]]; then 107 | echo -En "$1" 108 | else 109 | var $2 "$1" 110 | eval "${2}=\"$1\"" 111 | fi 112 | } 113 | 114 | # Taken from https://gist.github.com/cdown/1163649#file-gistfile1-sh 115 | function HTTP.urldecode { 116 | local plussless="${1//+/ }" 117 | printf '%b' "${plussless//%/\\x}" 118 | } 119 | 120 | # Taken from https://gist.github.com/cdown/1163649#gistcomment-1256298 121 | function HTTP.urlencode() { 122 | local length="${#1}" 123 | for (( i = 0; i < length; i++ )); do 124 | local c="${1:i:1}" 125 | case $c in 126 | [a-zA-Z0-9.~_-]) printf "$c" ;; 127 | *) printf "$c" | xxd -p -c1 | while read x;do printf "%%%s" "$x";done 128 | esac 129 | done 130 | } 131 | 132 | # Joins it's arguments into a string. 133 | # Delimiter goes as a first argument. 134 | function array.join { 135 | local d=$1; 136 | shift 137 | 138 | res="" 139 | 140 | for a in ${@}; do 141 | res="$res$d$a" 142 | done 143 | 144 | echo ${res:${#d}} 145 | } 146 | 147 | # Declares a copy of an associative array by its provided name. 148 | # Context: 149 | # $1 must be a name of an associative array variable. 150 | # Creates a local variable $E which is a copy of $the array referenced by $1. 151 | alias array.getbyref='e="$( declare -p ${1} )"; eval "declare -A E=${e#*=}"' 152 | 153 | # Declares a copy of an associative array by its provided name, gets the name from $2. 154 | alias array.getbyref2='e="$( declare -p ${2} )"; eval "declare -A E=${e#*=}"' 155 | 156 | # Iterates over the array created by array.getbyref. 157 | # Context: 158 | # array.getbyref must be called prior to this. 159 | # the do...done block must be supplied by the caller. 160 | alias array.foreach='for key in "${!E[@]}"' 161 | 162 | # Tries to figure out the type of given variable. 163 | # Takes a name of a variable, not the variable itself. 164 | # Usage: 165 | # userName="John" 166 | # listOfThings=(1 2 33 444) 167 | # declare -A mapOfThings=([first]=1 [other]=2 [nextAfterOther]=33 [plenty]=444) 168 | # boolFlagValue=true 169 | # userAge=234 170 | # reflection.Type userName # outputs "STRING" 171 | # reflection.Type listOfThings # outputs "ARRAY" 172 | # reflection.Type mapOfThings # outputs "MAP" 173 | # reflection.Type boolFlagValue # outputs "BOOLEAN" 174 | # reflection.Type userAge # outputs "NUMBER" 175 | function reflection.Type { 176 | decl=$(declare -p $1) 177 | mode=${decl:8:2} 178 | 179 | val=$(eval echo \$${1}) 180 | 181 | case $mode in 182 | "-a") 183 | echo "ARRAY" 184 | ;; 185 | 186 | "-A") 187 | echo "MAP" 188 | ;; 189 | 190 | *) 191 | if [[ $val == "true" || $val == "false" ]]; then 192 | echo "BOOLEAN" 193 | elif [[ $val =~ ^[[:digit:]]+$ ]]; then 194 | echo "NUMBER" 195 | else 196 | echo "STRING" 197 | fi 198 | ;; 199 | esac 200 | } 201 | 202 | declare -a IFS_backup_stack 203 | 204 | # Changes the IFS variable while backing it up and automatically restoring. 205 | # To set a new IFS: sys.IFS $'\r' 206 | # To reset IFS to it's original value: sys.IFS 207 | function sys.IFS { 208 | if [[ -z ${1+x} ]]; then 209 | # Resetting 210 | IFS_backup=${IFS_backup_stack[${#IFS_backup_stack[@]}-1]} 211 | if ! [[ -z $IFS_backup ]]; then 212 | IFS=$IFS_backup 213 | unset IFS_backup_stack[${#IFS_backup_stack[@]}-1] 214 | fi 215 | else 216 | # Setting a new value 217 | IFS_backup_stack+=("$IFS") 218 | IFS=$1 219 | fi 220 | } 221 | 222 | # Initilizes function arguments in reversed order, so $#-th argument becomes $_0, $#-1 becomes $_1 and so on. 223 | # Context: 224 | # Used within a function. 225 | # Creates local variables $_0, $_1 ... 226 | alias fn.arguments='local _0; local _1; local _2; eval "_0=\$$(($#-0)); _1=\$$(($#-1)); _2=\$$(($#-2))"' 227 | -------------------------------------------------------------------------------- /localhost/.etc/.env: -------------------------------------------------------------------------------- 1 | # A path to the storage folder where image files for localhost:8080/gallery are hosted. 2 | GALLERY_STORAGE=storage/images -------------------------------------------------------------------------------- /localhost/.etc/db.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE image_comments ( 2 | id INT(11) NOT NULL AUTO_INCREMENT, 3 | imageID VARCHAR(50) NOT NULL, 4 | message VARCHAR(5000) NOT NULL, 5 | date DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, 6 | PRIMARY KEY (id) 7 | ) 8 | COLLATE='utf8_general_ci' 9 | ENGINE=InnoDB 10 | AUTO_INCREMENT=38 11 | ; 12 | 13 | 14 | INSERT INTO `image_comments` (`id`, `imageID`, `message`, `date`) VALUES (38, 'UXRf0oub.jpg', 'REST APIs are totally possible with BWF and it\'s support for MySQL and JSON data.', '2019-08-25 11:19:44'); 15 | INSERT INTO `image_comments` (`id`, `imageID`, `message`, `date`) VALUES (39, 'UXRf0oub.jpg', 'You can leave comments in the field below and they will be saved to the DB.', '2019-08-25 10:24:57'); 16 | -------------------------------------------------------------------------------- /localhost/.etc/tpl/age.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | ~# Bashttpd 4 | 5 | 6 | 7 | 8 | 9 | 10 |
11 |
12 |

~# This is how we template.

13 |

~# Now we know that $name is $age years old.

14 |

~# The cookie is still here, showing $visits visits.

15 |

 

16 |

~# Return to the homepage

17 |

~# _

18 |
19 | 20 | 27 |
28 | 29 | 30 | 35 | -------------------------------------------------------------------------------- /localhost/.etc/tpl/error.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | ~# Bashttpd 4 | 5 | 6 | 7 | 8 | 9 | 10 |
11 |
12 |

~# ${__HTTP_STATUS_SENT:-500}.

13 |

~# That's a lot of errors.

14 |

 

15 |

~# Return to the homepage

16 |

~# _

17 |
18 |
19 | 20 | 21 | 26 | -------------------------------------------------------------------------------- /localhost/.etc/tpl/image.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | ~# Bashttpd 4 | 5 | 6 | 7 | 8 | 9 | 10 | 32 | 33 | 34 | 47 | -------------------------------------------------------------------------------- /localhost/.etc/tpl/ls.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | ~# Bashttpd 4 | 5 | 6 | 7 | 8 | 9 | 10 |
11 |
12 |

~# $COMMAND_

13 |

 

14 |
15 | $CLIOUTPUT 16 |
17 |

 

18 |

~# Return to the homepage

19 |

 

20 |

~# _

21 |
22 |
23 | 24 | 25 | 30 | -------------------------------------------------------------------------------- /localhost/api/age/POST.sh: -------------------------------------------------------------------------------- 1 | age=$(req.Data "age") 2 | name=$(req.Data "name") 3 | let visits=$(req.Cookie "visit_counter")+1 4 | 5 | resp.Status 200 6 | resp.Header "Content-Type" "text/html" 7 | resp.Cookie "visit_counter" $visits 8 | 9 | resp.TemplateFile "age.html" 10 | -------------------------------------------------------------------------------- /localhost/api/comments/GET.sh: -------------------------------------------------------------------------------- 1 | req.Query image imageID 2 | 3 | # Add an image=XXXX query string parameter to see comments for a specific image. 4 | if [[ ! -z $imageID ]]; then 5 | mysql.Select image_comments "imageID=\"$imageID\" ORDER BY date DESC" ROWS 6 | else 7 | mysql.All image_comments ROWS 8 | fi 9 | 10 | RESP=() 11 | 12 | mysql.foreach do 13 | mysql.row 14 | RESP+=("$(JSON.EncodeObject row untyped)") 15 | done 16 | 17 | resp.Status 200 18 | resp.JSON RESP -------------------------------------------------------------------------------- /localhost/api/comments/POST.sh: -------------------------------------------------------------------------------- 1 | req.Data imageID imageID 2 | req.Data message message 3 | 4 | declare -A COMMENT=( 5 | [imageID]=$imageID 6 | [message]=$message 7 | ) 8 | 9 | mysql.Insert image_comments COMMENT ID 10 | declare -A RESP=([commentID]=$ID) 11 | 12 | resp.Status 201 13 | resp.JSON RESP 14 | 15 | log "The comment ${lcEm}\"${message}\"${lcX} saved as ${ID}." 16 | -------------------------------------------------------------------------------- /localhost/api/images/GET.sh: -------------------------------------------------------------------------------- 1 | # Executing ls 2 | lsOut=$(ls -lA --time-style=long-iso $PROJECT/$GALLERY_STORAGE) 3 | readarray -t LINES <<< $lsOut 4 | 5 | # Going through it's output 6 | for LINE in "${LINES[@]}"; do 7 | # Splitting the line by whitespace. 8 | IFS=$' ' 9 | FILE=($LINE) 10 | 11 | # If there are size & name columns in place 12 | if ! [[ -z ${FILE[7]} || -z ${FILE[4]} ]]; then 13 | declare -A fdata=( 14 | [name]="${FILE[7]}" 15 | [URL]=$(project.URL $GALLERY_STORAGE ${FILE[7]}) 16 | [size]="${FILE[4]}" 17 | [modifiedAt]="${FILE[5]} ${FILE[6]}" 18 | ) 19 | # Encoding an object and appending it as a string to RESP_FILES 20 | IFS='' 21 | RESP_FILES+=("$(JSON.EncodeObject fdata untyped)") 22 | log "File ${fdata[name]} added" 23 | fi 24 | done 25 | 26 | resp.Status 200 27 | resp.JSON RESP_FILES -------------------------------------------------------------------------------- /localhost/api/upload/POST.sh: -------------------------------------------------------------------------------- 1 | local EXT="bin" 2 | local fCT=$(req.FileContentType aPicture) 3 | 4 | case $fCT in 5 | "image/jpeg") 6 | EXT="jpg" 7 | ;; 8 | 9 | "image/png") 10 | EXT="png" 11 | ;; 12 | 13 | "image/gif") 14 | EXT="gif" 15 | ;; 16 | 17 | "image/x-icon") 18 | EXT="ico" 19 | ;; 20 | 21 | *) 22 | log "Impossible file Content-Type: $fCT" 23 | ;; 24 | esac 25 | 26 | local fTmp=$(req.File aPicture) 27 | local fDest=$(mktemp $PROJECT/$GALLERY_STORAGE/XXXXXXXX.$EXT) 28 | mv $fTmp $fDest 29 | 30 | declare -A RESP=( 31 | [name]=${fDest##*/} 32 | [tpmFilename]=$fTmp 33 | [srcFilename]=$(req.FileName aPicture) 34 | [URL]=$(project.URL ${fDest//$PROJECT\/}) 35 | [isItReallyHappeningInBash]=true 36 | ) 37 | 38 | resp.Status 200 39 | resp.JSON RESP untyped -------------------------------------------------------------------------------- /localhost/api/visits/GET.sh: -------------------------------------------------------------------------------- 1 | counter=$(($(req.Cookie "visit_counter")+1)) 2 | declare -A RESP=([visits]=$counter) 3 | 4 | resp.Status 200 5 | resp.Cookie "visit_counter" $counter 6 | resp.JSON RESP 7 | -------------------------------------------------------------------------------- /localhost/assets/app.css: -------------------------------------------------------------------------------- 1 | * { 2 | font-family: 'Share Tech Mono', monospace; 3 | margin: 0; 4 | padding: 0; 5 | } 6 | 7 | .container { 8 | position: relative; 9 | /* width: 1300px; */ 10 | width: 1725px; 11 | left: 50%; 12 | margin-left: -850px; 13 | zoom: 0.75; 14 | padding-top: 50pt; 15 | } 16 | 17 | .hBashttpd { 18 | color: #222; 19 | font-size: 62pt !important; 20 | line-height: 61pt; 21 | position: relative; 22 | margin-left: -4pt; 23 | } 24 | 25 | .hBWF { 26 | color: #222; 27 | font-size: 179pt !important; 28 | line-height: 133pt; 29 | margin-left: -17px; 30 | position: relative; 31 | } 32 | 33 | .hForms { 34 | color: #222; 35 | font-size: 104pt !important; 36 | position: relative; 37 | margin-left: -13px; 38 | line-height: 108px; 39 | } 40 | 41 | .hGallery { 42 | color: #222; 43 | font-size: 71pt !important; 44 | line-height: 70pt; 45 | position: relative; 46 | margin-left: -4pt; 47 | } 48 | 49 | .hImageID { 50 | color: #222; 51 | font-size: 41pt !important; 52 | line-height: 41pt; 53 | position: relative; 54 | margin-left: -4pt; 55 | } 56 | 57 | .hAND { 58 | font-family: Vidaloka; 59 | font-size: 72pt; 60 | height: 0; 61 | text-align: center; 62 | position: relative; 63 | top: -60px; 64 | color: #222; 65 | z-index: 99; 66 | text-shadow: -1px 0 0 white, 67 | 1px 0 0 white, 68 | 0 -1px 0 white, 69 | 0 1px 0 white, 70 | -2px 0 0 white, 71 | 2px 0 0 white, 72 | 0 -2px 0 white, 73 | 0 2px 0 white, 74 | -3px 0 0 white, 75 | 3px 0 0 white, 76 | 0 -3px 0 white, 77 | 0 3px 0 white, 78 | -4px 0 0 white, 79 | 4px 0 0 white, 80 | 0 -4px 0 white, 81 | 0 4px 0 white 82 | ; 83 | } 84 | 85 | .hJSON { 86 | color: #222; 87 | font-size: 132pt; 88 | line-height: 105pt; 89 | position: relative; 90 | margin-left: -15pt; 91 | } 92 | 93 | .hMySQL { 94 | color: #222; 95 | font-size: 103pt; 96 | line-height: 105pt; 97 | position: relative; 98 | margin-left: -8pt; 99 | } 100 | 101 | input, textarea { 102 | font-size: 20pt; 103 | display: block; 104 | margin-bottom: 20pt; 105 | } 106 | 107 | /* All that ridiculous input styling below is to remedy the Crome's ugly autofill styling */ 108 | input[type=text]:focus { 109 | border: 1px #00bbff solid !important; 110 | outline: none !important; 111 | background-color: #00BBFF !important; 112 | box-shadow: 0 0 0px 1000px #00BBFF inset !important; 113 | color: white !important; 114 | -webkit-text-fill-color: white !important; 115 | } 116 | 117 | input:-webkit-autofill, 118 | input:-webkit-autofill:hover, 119 | input:-webkit-autofill:focus, 120 | input:-webkit-autofill:active, 121 | input[type=text] { 122 | border: 1px #555 solid; 123 | width: 100%; 124 | 125 | box-shadow: 0 0 0px 1000px #fff inset; 126 | background-color: white; 127 | -webkit-text-fill-color: #222; 128 | color: #222; 129 | } 130 | 131 | input[type=button], 132 | input[type=submit] { 133 | float: right; 134 | padding: 10px 20px; 135 | background-color: #222; 136 | color: white; 137 | border: none; 138 | cursor: pointer; 139 | } 140 | 141 | input[type=button]:focus, 142 | input[type=button]:active, 143 | input[type=button]:hover, 144 | input[type=submit]:focus, 145 | input[type=submit]:active, 146 | input[type=submit]:hover { 147 | background-color: #00BBFF; 148 | } 149 | 150 | label { 151 | font-size: 20pt; 152 | display: block; 153 | } 154 | 155 | .column { 156 | width: 350px; 157 | float: left; 158 | } 159 | 160 | p { 161 | color: #555; 162 | font-size: 16pt; 163 | } 164 | 165 | .col2 { 166 | margin-left: 75pt; 167 | padding-top: 145pt; 168 | } 169 | 170 | .col3 { 171 | margin-left: 75pt; 172 | padding-top: 100pt; 173 | } 174 | 175 | .col4 { 176 | margin-left: 75pt; 177 | /* padding-top: 85pt; */ 178 | } 179 | 180 | .hCookies { 181 | color: #222; 182 | font-size: 73pt; 183 | line-height: 61pt; 184 | position: relative; 185 | margin-left: -7pt; 186 | } 187 | 188 | .hTemplates { 189 | color: #222; 190 | font-size: 55pt; 191 | line-height: 53pt; 192 | position: relative; 193 | margin-left: -3pt; 194 | } 195 | 196 | #visits-big { 197 | color: #222; 198 | font-size: 73pt; 199 | line-height: 61pt; 200 | position: relative; 201 | margin-left: -7pt; 202 | } 203 | 204 | #visits-big.w3 { 205 | font-size: 198pt; 206 | line-height: 190pt; 207 | margin-top: 35pt; 208 | margin-left: -30pt; 209 | } 210 | 211 | #visits-big.w4 { 212 | font-size: 142pt; 213 | line-height: 141pt; 214 | margin-top: 35pt; 215 | margin-left: -22pt; 216 | } 217 | 218 | #visits-big.w5 { 219 | font-size: 110pt; 220 | line-height: 124pt; 221 | margin-top: 35pt; 222 | margin-left: -17pt; 223 | } 224 | 225 | .typographyColumn p.wrapped { 226 | word-break: break-all; 227 | } 228 | 229 | .typographyColumn p.hasmargin { 230 | margin-bottom: 25pt; 231 | } 232 | 233 | .typographyColumn p em { 234 | background-color: #00BBFF; 235 | font-style: normal; 236 | color:white; 237 | } 238 | 239 | img { 240 | margin-bottom: 10pt; 241 | } 242 | 243 | .theAgePage { 244 | padding-top: 100pt; 245 | } 246 | 247 | .theAgePage p { 248 | font-size: 32pt; 249 | } 250 | 251 | .typographyColumn a { 252 | color: #00BBFF; 253 | text-decoration: none; 254 | } 255 | 256 | .typographyColumn a:hover { 257 | background-color: #00BBFF; 258 | color: white; 259 | text-decoration: none; 260 | } 261 | 262 | .typographyColumn a::after { 263 | content: " >>" 264 | } 265 | 266 | blink { 267 | color: #777; 268 | } 269 | 270 | .noblink blink { 271 | color: white; 272 | } 273 | 274 | #the-gallery { 275 | width: 1625px; 276 | margin-left: -812px; 277 | } 278 | 279 | #gallery { 280 | float: left; 281 | width: 1275px; 282 | } 283 | 284 | #gallery img { 285 | display: inline-block; 286 | width: 350px; 287 | margin-left: 75px; 288 | vertical-align: middle; 289 | } 290 | 291 | #the-gallery input[type=file] { 292 | width: 0.1px; 293 | height: 0.1px; 294 | opacity: 0; 295 | overflow: hidden; 296 | position: absolute; 297 | z-index: -1; 298 | } 299 | 300 | .fileinput label { 301 | font-size: 20pt; 302 | display: block; 303 | margin-bottom: 20pt; 304 | padding: 10px 20px; 305 | background-color: #222; 306 | color: white; 307 | border: none; 308 | cursor: pointer; 309 | } 310 | 311 | .fileinput label:hover { 312 | background-color: #00BBFF; 313 | } 314 | 315 | .progress { 316 | height: 20px; 317 | } 318 | 319 | .progress > div { 320 | background: #00BBFF; 321 | height: 100%; 322 | width: 0; 323 | } 324 | 325 | #ui-stash { 326 | /* display: none; */ 327 | position: absolute; 328 | width: 100%; 329 | height: 100%; 330 | top: 0; 331 | left: 0; 332 | z-index: -9999; 333 | } 334 | 335 | #ui-stash > * { 336 | display: none; 337 | position: relative; 338 | z-index: 9999 339 | } 340 | 341 | #theater { 342 | height: 100%; 343 | } 344 | 345 | #theater > .stage { 346 | background-color: rgba(0, 0, 0, 0.5); 347 | align-content: center; 348 | text-align: center; 349 | height: 100%; 350 | } 351 | 352 | #theater > .stage > .img { 353 | display: inline-block; 354 | line-height: 100%; 355 | position: relative; 356 | } 357 | 358 | #comments textarea { 359 | width: 100%; 360 | } 361 | 362 | #comments input[type=button] { 363 | display: block; 364 | } 365 | 366 | #comments-list > p { 367 | margin-bottom: 20px; 368 | } 369 | 370 | .footer { 371 | clear: both; 372 | text-align: center; 373 | padding-top: 150px; 374 | font-size: 12pt; 375 | color: #222; 376 | } 377 | 378 | .footer > a { 379 | color: #00BBFF; 380 | text-decoration: none; 381 | } 382 | 383 | .footer > .pipe { 384 | border-right: 1px #222 solid; 385 | margin: 0 30px; 386 | } 387 | 388 | /*****************************************************************************/ 389 | 390 | .cli-line .fmt-30 { 391 | color: #000000; 392 | } 393 | 394 | .cli-line .fmt-31 { 395 | color: #990044; 396 | } 397 | 398 | .cli-line .fmt-32 { 399 | color: #009944; 400 | } 401 | 402 | .cli-line .fmt-34 { 403 | color: #00BBFF; 404 | } 405 | 406 | .cli-line .fmt-35 { 407 | color: #990099; 408 | } 409 | 410 | .cli-line .fmt-36 { 411 | color: #009999; 412 | } 413 | 414 | .cli-line .fmt-37 { 415 | color: #777; 416 | } 417 | 418 | .cli-line .fmt-90 { 419 | color: #333; 420 | } 421 | 422 | .cli-line .fmt-91 { 423 | color: #FF0000; 424 | } 425 | 426 | .cli-line .fmt-92 { 427 | color: #00FF00; 428 | } 429 | 430 | .cli-line .fmt-94 { 431 | color: #0000FF; 432 | } 433 | 434 | .cli-line .fmt-95 { 435 | color: #FF00FF; 436 | } 437 | 438 | .cli-line .fmt-96 { 439 | color: #00FFFF; 440 | } 441 | 442 | .cli-line .fmt-97 { 443 | color: #FFF; 444 | } 445 | 446 | .cli-line .fmt-40 { 447 | background-color: #000000; 448 | } 449 | 450 | .cli-line .fmt-41 { 451 | background-color: #990044; 452 | } 453 | 454 | .cli-line .fmt-42 { 455 | background-color: #009944; 456 | } 457 | 458 | .cli-line .fmt-44 { 459 | background-color: #00BBFF; 460 | } 461 | 462 | .cli-line .fmt-45 { 463 | background-color: #990099; 464 | } 465 | 466 | .cli-line .fmt-46 { 467 | background-color: #009999; 468 | } 469 | 470 | .cli-line .fmt-47 { 471 | background-color: #777; 472 | } 473 | 474 | .cli-line .fmt-100 { 475 | background-color: #333; 476 | } 477 | 478 | .cli-line .fmt-101 { 479 | background-color: #FF0000; 480 | } 481 | 482 | .cli-line .fmt-102 { 483 | background-color: #00FF00; 484 | } 485 | 486 | .cli-line .fmt-104 { 487 | background-color: #0000FF; 488 | } 489 | 490 | .cli-line .fmt-105 { 491 | background-color: #FF00FF; 492 | } 493 | 494 | .cli-line .fmt-106 { 495 | background-color: #00FFFF; 496 | } 497 | 498 | .cli-line .fmt-107 { 499 | background-color: #FFF; 500 | } 501 | 502 | .cli-line .fmt-01, 503 | .cli-line .fmt-1 { 504 | font-weight: bold; 505 | } 506 | 507 | .cli-line .fmt-02, 508 | .cli-line .fmt-2 { 509 | opacity: 0.5; 510 | } 511 | 512 | .cli-line .fmt-04, 513 | .cli-line .fmt-4 { 514 | text-decoration: underline; 515 | } -------------------------------------------------------------------------------- /localhost/assets/app.js: -------------------------------------------------------------------------------- 1 | let blinkIntervalID; 2 | 3 | function blinkTheBlinks() { 4 | unblinkTheBlinks(); 5 | var blinkShown = true; 6 | blinkIntervalID = setInterval(() => { 7 | blinkShown=!blinkShown; 8 | document.body.parentElement.className = blinkShown ? "" : "noblink"; 9 | }, 300) 10 | } 11 | 12 | function unblinkTheBlinks() { 13 | blinkIntervalID && clearInterval(blinkIntervalID) 14 | } 15 | 16 | function request(method, url, data, cb) { 17 | let req = new XMLHttpRequest; 18 | req.open(method, url, true) 19 | 20 | if (method === 'POST') { 21 | req.setRequestHeader('Content-type', 'application/json'); 22 | } 23 | 24 | req.onreadystatechange = function (e) { 25 | if (this.readyState === 4) { 26 | if (this.status == 200) { 27 | cb && cb(JSON.parse(this.response)); 28 | } else { 29 | d = JSON.parse(this.response) 30 | if (d.message) { 31 | d.command && console.error(`Error at ${d.command}`); 32 | console.error(d.message); 33 | } else { 34 | console.error(d); 35 | } 36 | } 37 | } 38 | }; 39 | 40 | req.send(data ? JSON.stringify(data) : undefined); 41 | } 42 | 43 | function requestVisits() { 44 | request("GET", "/api/visits", undefined, (resp) => { 45 | document.getElementById('visits').innerHTML = resp.visits + "-ish"; 46 | 47 | let bv = document.getElementById('visits-big'); 48 | let val = "[" + resp.visits + "]"; 49 | bv.innerHTML = val; 50 | bv.className = "w" + val.length; 51 | }); 52 | } 53 | 54 | let UPLOAD_ID=1 55 | 56 | function submitForm(formID, url, cb) { 57 | (function(UID) { 58 | let form = document.getElementById(formID); 59 | let fd = new FormData(form); 60 | let req = new XMLHttpRequest(); 61 | 62 | let lastReceived = 0; 63 | req.onreadystatechange = function (e) { 64 | if (this.readyState === 3) { 65 | let newCode = this.responseText.substr(lastReceived); 66 | lastReceived = this.responseText.length; 67 | eval(newCode); 68 | } else if (this.readyState === 4) { 69 | let newCode = this.responseText.substr(lastReceived); 70 | lastReceived = this.responseText.length; 71 | eval(newCode); 72 | cb(bwf.get(UID)); 73 | } 74 | }; 75 | 76 | req.upload.addEventListener("progress", (e) => { 77 | console.log("Progress is ", e); 78 | }, false); 79 | 80 | req.open('POST', url, true); 81 | req.setRequestHeader("X-Bwf-Upload-ID", UID) 82 | req.send(fd); 83 | })(`upl-${formID}-${UPLOAD_ID++}`) 84 | } 85 | 86 | function progressBar(id, p) { 87 | document.getElementById(id).childNodes[0].style.width = `${p}%`; 88 | } 89 | 90 | let bwf = { 91 | renderUploadProgress: function($l, $cl) { 92 | progressBar('aPictureProgress', $l / $cl * 100); 93 | }, 94 | 95 | valueStash: {}, 96 | 97 | set: function(vn, v) { 98 | bwf.valueStash[vn] = v; 99 | }, 100 | 101 | get: function(vn) { 102 | return bwf.valueStash[vn]; 103 | } 104 | }; 105 | 106 | function $(id) { 107 | return document.getElementById(id); 108 | } 109 | 110 | function id(eid) { 111 | return $(eid); 112 | } 113 | 114 | function requestComments() { 115 | request('GET', `/api/comments?image=${imageID}`, undefined, (cmnts) => { 116 | for (let c of cmnts) { 117 | addComment(c); 118 | } 119 | }); 120 | } 121 | 122 | function addComment(cmnt) { 123 | let eP = document.createElement('p'); 124 | eP.innerHTML = `~# ${cmnt.message.replace(/\n/gi, "
")}_`; 125 | id('comments-list').appendChild(eP); 126 | } 127 | 128 | function submitComment(imageID, message) { 129 | request('POST', '/api/comments', { 130 | imageID: imageID, 131 | message: message, 132 | }); 133 | 134 | addComment({message: message}); 135 | } 136 | -------------------------------------------------------------------------------- /localhost/assets/img.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/x1n13y84issmd42/bashttpd/b1f56aed60688ff49f53fc11aa3f04fd62fffd3b/localhost/assets/img.png -------------------------------------------------------------------------------- /localhost/away/GET.sh: -------------------------------------------------------------------------------- 1 | resp.Redirect "http://example.com" 307 -------------------------------------------------------------------------------- /localhost/gallery/image/GET.sh: -------------------------------------------------------------------------------- 1 | ID=$(req.Query ID) 2 | ID=${ID//\//} 3 | path=$(realpath "$PROJECT/storage/images/$ID") 4 | log "The image path is $path" 5 | 6 | if [[ -f $path ]]; then 7 | imageURL="http://localhost:8080/$GALLERY_STORAGE/${ID}" 8 | resp.Status 200 9 | resp.Header "Content-Type" "text/html" 10 | resp.TemplateFile "image.html" 11 | else 12 | resp.Status 404 13 | resp.Header "Content-Type" "text/html" 14 | resp.TemplateFile "error.html" 15 | fi -------------------------------------------------------------------------------- /localhost/gallery/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | ~# Bashttpd 4 | 5 | 6 | 7 | 8 | 9 | 10 | 44 | 45 |
46 |
47 |
48 |
49 |
50 | 51 | 52 | 122 | -------------------------------------------------------------------------------- /localhost/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | ~# Bashttpd 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 |
13 |

Bashttpd

14 |

~# An HTTP server in pure Bash script_

15 | 16 |

BWF

17 |

~# A web application framework in Bash script for the refined souls_

18 |

~# It exists as a manifestation of minimalism in the world of virtualization of personal disciplne & responsibility, continous delivery of hype and ever growing stacks of disappointment_

19 |
20 | 21 |
22 | 23 |

~# And other binary data formats are supported, proudly served by cat_

24 |

FORMS

25 |

&

26 |

JSON

27 |

~# Form data & JSON APIs are at your service, easier than ever. Head to the Gallery page to experience that and something more_

28 |
29 | 30 |
31 |

MySQL

32 |

~# BWF makes working with MySQL databases a breeze_

33 |

TEMPLATES

34 |

~# You can have dynamic page content by expanding Bash variables inside your HTML markup. Submit the form below and witness_

35 |
36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 |
44 |
45 | 46 |
47 |

COOKIES

48 |

~# This is how we know that you are visiting this page for the wait for it time_

49 |

[.]

50 |

~# A nicer and bigger number to make this column taller and balance the page layout_

51 |
52 | 53 | 60 |
61 | 62 | 63 | 69 | -------------------------------------------------------------------------------- /localhost/ls/GET.sh: -------------------------------------------------------------------------------- 1 | COMMAND="ls -la --color=always ~" 2 | CLIOUTPUT=$(resp.CLI $(ls -la --color=always ~)) 3 | 4 | resp.Status 200 5 | resp.TemplateFile "ls.html" 6 | -------------------------------------------------------------------------------- /localhost/storage/images/UXRf0oub.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/x1n13y84issmd42/bashttpd/b1f56aed60688ff49f53fc11aa3f04fd62fffd3b/localhost/storage/images/UXRf0oub.jpg -------------------------------------------------------------------------------- /testdata/15.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/x1n13y84issmd42/bashttpd/b1f56aed60688ff49f53fc11aa3f04fd62fffd3b/testdata/15.bin -------------------------------------------------------------------------------- /testdata/20.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/x1n13y84issmd42/bashttpd/b1f56aed60688ff49f53fc11aa3f04fd62fffd3b/testdata/20.bin -------------------------------------------------------------------------------- /testdata/ABCD.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/x1n13y84issmd42/bashttpd/b1f56aed60688ff49f53fc11aa3f04fd62fffd3b/testdata/ABCD.bin -------------------------------------------------------------------------------- /testdata/genbin.go: -------------------------------------------------------------------------------- 1 | /* 2 | Generates binary files to use as test data for bashttpd development & testing. 3 | */ 4 | package main 5 | 6 | import ( 7 | "fmt" 8 | "io/ioutil" 9 | ) 10 | 11 | func main() { 12 | bytes15 := []byte("\n\n\n\x00\x00\x00\xaa\xbb\xcc\r\r\r\x00\xde\xad") 13 | bytes20 := []byte("\n\n\n\n\x00\x00\x00\x00\xaa\xbb\xcc\xdd\r\r\r\r\xde\xad\xbe\xef") 14 | bytesABCD := []byte("ABCD\n\n\n\nAB\rD\x00\x00\x00\x00\xde\xad\xbe\xef") 15 | bytesLS := []byte("\x00\xff\xff\xff\x00\x24\x80\xf4\x00\x24\x80\xf4\x00\x24\x80\xf4\x00\x24\x80\xf4\x2a\x24\x80\xf4\xc2") 16 | bytesICO := []byte("\xf4\x14\x24\x80\xf4\x00\x24\x80\xf4\x00\x24\x80\xf4\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\x24\x80\xf4\x00\x24\x80\xf4\x00\x24\x80\xf4\x00\x24\x80\xf4\x00\x24\x80\xf4\x00\x24\x80\xf4\x00\x24\x80\xf4\x1f\x24\x80\xf4\xe7\x24\x80\xf4\xff\x24\x80\xf4\xff\x24\x80\xf4\xa2\x24\x80\xf4\x0a\x24\x80\xf4\x00\x24\x80\xf4\x0f\x24\x80\xf4\xd8\x24\x80\xf4\xff\x24\x80\xf4\xf5\x24\x80\xf4\x2e\x24\x80\xf4\x00\x24\x80\xf4\x00\x24\x80\xf4\x00\x24\x80\xf4\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\x24\x80\xf4\x00\x24\x80\xf4\x00\x24\x80") 17 | bytesReqABCD := []byte("\x50\x4f\x53\x54\x20\x2f\x61\x70\x69\x2f\x75\x70\x6c\x6f\x61\x64\x20\x48\x54\x54\x50\x2f\x31\x2e\x31\x0d\x0a\x63\x61\x63\x68\x65\x2d\x63\x6f\x6e\x74\x72\x6f\x6c\x3a\x20\x6e\x6f\x2d\x63\x61\x63\x68\x65\x0d\x0a\x50\x6f\x73\x74\x6d\x61\x6e\x2d\x54\x6f\x6b\x65\x6e\x3a\x20\x37\x30\x66\x32\x61\x64\x38\x34\x2d\x34\x38\x65\x39\x2d\x34\x34\x62\x38\x2d\x38\x36\x37\x33\x2d\x32\x38\x61\x30\x30\x63\x62\x37\x37\x62\x61\x34\x0d\x0a\x43\x6f\x6e\x74\x65\x6e\x74\x2d\x54\x79\x70\x65\x3a\x20\x6d\x75\x6c\x74\x69\x70\x61\x72\x74\x2f\x66\x6f\x72\x6d\x2d\x64\x61\x74\x61\x3b\x20\x62\x6f\x75\x6e\x64\x61\x72\x79\x3d\x2d\x2d\x2d\x2d\x2d\x2d\x2d\x2d\x2d\x2d\x2d\x2d\x2d\x2d\x2d\x2d\x2d\x2d\x2d\x2d\x2d\x2d\x2d\x2d\x2d\x2d\x34\x33\x32\x33\x32\x33\x33\x31\x31\x30\x38\x35\x38\x34\x31\x36\x35\x36\x34\x37\x37\x33\x30\x30\x0d\x0a\x55\x73\x65\x72\x2d\x41\x67\x65\x6e\x74\x3a\x20\x50\x6f\x73\x74\x6d\x61\x6e\x52\x75\x6e\x74\x69\x6d\x65\x2f\x36\x2e\x34\x2e\x31\x0d\x0a\x41\x63\x63\x65\x70\x74\x3a\x20\x2a\x2f\x2a\x0d\x0a\x48\x6f\x73\x74\x3a\x20\x6c\x6f\x63\x61\x6c\x68\x6f\x73\x74\x3a\x38\x30\x38\x30\x0d\x0a\x63\x6f\x6f\x6b\x69\x65\x3a\x20\x76\x69\x73\x69\x74\x5f\x63\x6f\x75\x6e\x74\x65\x72\x3d\x39\x0d\x0a\x61\x63\x63\x65\x70\x74\x2d\x65\x6e\x63\x6f\x64\x69\x6e\x67\x3a\x20\x67\x7a\x69\x70\x2c\x20\x64\x65\x66\x6c\x61\x74\x65\x0d\x0a\x63\x6f\x6e\x74\x65\x6e\x74\x2d\x6c\x65\x6e\x67\x74\x68\x3a\x20\x34\x37\x36\x0d\x0a\x43\x6f\x6e\x6e\x65\x63\x74\x69\x6f\x6e\x3a\x20\x6b\x65\x65\x70\x2d\x61\x6c\x69\x76\x65\x0d\x0a\x0d\x0a\x2d\x2d\x2d\x2d\x2d\x2d\x2d\x2d\x2d\x2d\x2d\x2d\x2d\x2d\x2d\x2d\x2d\x2d\x2d\x2d\x2d\x2d\x2d\x2d\x2d\x2d\x2d\x2d\x34\x33\x32\x33\x32\x33\x33\x31\x31\x30\x38\x35\x38\x34\x31\x36\x35\x36\x34\x37\x37\x33\x30\x30\x0d\x0a\x43\x6f\x6e\x74\x65\x6e\x74\x2d\x44\x69\x73\x70\x6f\x73\x69\x74\x69\x6f\x6e\x3a\x20\x66\x6f\x72\x6d\x2d\x64\x61\x74\x61\x3b\x20\x6e\x61\x6d\x65\x3d\x22\x6e\x61\x6d\x65\x22\x0d\x0a\x0d\x0a\x45\x45\x45\x58\x58\x58\x20\x59\x6f\x75\x72\x20\x59\x4f\x4c\x4f\x2d\x4d\x61\x6e\x0d\x0a\x2d\x2d\x2d\x2d\x2d\x2d\x2d\x2d\x2d\x2d\x2d\x2d\x2d\x2d\x2d\x2d\x2d\x2d\x2d\x2d\x2d\x2d\x2d\x2d\x2d\x2d\x2d\x2d\x34\x33\x32\x33\x32\x33\x33\x31\x31\x30\x38\x35\x38\x34\x31\x36\x35\x36\x34\x37\x37\x33\x30\x30\x0d\x0a\x43\x6f\x6e\x74\x65\x6e\x74\x2d\x44\x69\x73\x70\x6f\x73\x69\x74\x69\x6f\x6e\x3a\x20\x66\x6f\x72\x6d\x2d\x64\x61\x74\x61\x3b\x20\x6e\x61\x6d\x65\x3d\x22\x61\x67\x65\x22\x0d\x0a\x0d\x0a\x33\x34\x35\x0d\x0a\x2d\x2d\x2d\x2d\x2d\x2d\x2d\x2d\x2d\x2d\x2d\x2d\x2d\x2d\x2d\x2d\x2d\x2d\x2d\x2d\x2d\x2d\x2d\x2d\x2d\x2d\x2d\x2d\x34\x33\x32\x33\x32\x33\x33\x31\x31\x30\x38\x35\x38\x34\x31\x36\x35\x36\x34\x37\x37\x33\x30\x30\x0d\x0a\x43\x6f\x6e\x74\x65\x6e\x74\x2d\x44\x69\x73\x70\x6f\x73\x69\x74\x69\x6f\x6e\x3a\x20\x66\x6f\x72\x6d\x2d\x64\x61\x74\x61\x3b\x20\x6e\x61\x6d\x65\x3d\x22\x74\x68\x65\x46\x69\x6c\x65\x49\x4c\x69\x6b\x65\x22\x3b\x20\x66\x69\x6c\x65\x6e\x61\x6d\x65\x3d\x22\x41\x42\x43\x44\x2e\x62\x69\x6e\x22\x0d\x0a\x43\x6f\x6e\x74\x65\x6e\x74\x2d\x54\x79\x70\x65\x3a\x20\x61\x70\x70\x6c\x69\x63\x61\x74\x69\x6f\x6e\x2f\x6f\x63\x74\x65\x74\x2d\x73\x74\x72\x65\x61\x6d\x0d\x0a\x0d\x0a\x41\x42\x43\x44\x0a\x0a\x0a\x0a\x41\x42\x0d\x44\x00\x00\x00\x00\xde\xad\xbe\xef\x0d\x0a\x2d\x2d\x2d\x2d\x2d\x2d\x2d\x2d\x2d\x2d\x2d\x2d\x2d\x2d\x2d\x2d\x2d\x2d\x2d\x2d\x2d\x2d\x2d\x2d\x2d\x2d\x2d\x2d\x34\x33\x32\x33\x32\x33\x33\x31\x31\x30\x38\x35\x38\x34\x31\x36\x35\x36\x34\x37\x37\x33\x30\x30\x2d\x2d\x0d\x0a") 18 | bytesReqICO := []byte("\x50\x4f\x53\x54\x20\x2f\x61\x70\x69\x2f\x75\x70\x6c\x6f\x61\x64\x20\x48\x54\x54\x50\x2f\x31\x2e\x31\x0d\x0a\x63\x61\x63\x68\x65" + 19 | "\x2d\x63\x6f\x6e\x74\x72\x6f\x6c\x3a\x20\x6e\x6f\x2d\x63\x61\x63\x68\x65\x0d\x0a\x50\x6f\x73\x74\x6d\x61\x6e\x2d\x54\x6f\x6b\x65" + 20 | "\x6e\x3a\x20\x36\x34\x34\x62\x65\x39\x34\x37\x2d\x32\x61\x63\x63\x2d\x34\x34\x35\x61\x2d\x62\x33\x35\x33\x2d\x39\x62\x30\x64\x35" + 21 | "\x66\x66\x31\x62\x39\x36\x37\x0d\x0a\x43\x6f\x6e\x74\x65\x6e\x74\x2d\x54\x79\x70\x65\x3a\x20\x6d\x75\x6c\x74\x69\x70\x61\x72\x74" + 22 | "\x2f\x66\x6f\x72\x6d\x2d\x64\x61\x74\x61\x3b\x20\x62\x6f\x75\x6e\x64\x61\x72\x79\x3d\x2d\x2d\x2d\x2d\x2d\x2d\x2d\x2d\x2d\x2d\x2d" + 23 | "\x2d\x2d\x2d\x2d\x2d\x2d\x2d\x2d\x2d\x2d\x2d\x2d\x2d\x2d\x2d\x37\x36\x39\x32\x33\x33\x39\x32\x33\x34\x33\x38\x38\x36\x33\x32\x30" + 24 | "\x33\x39\x34\x30\x33\x33\x37\x0d\x0a\x55\x73\x65\x72\x2d\x41\x67\x65\x6e\x74\x3a\x20\x50\x6f\x73\x74\x6d\x61\x6e\x52\x75\x6e\x74" + 25 | "\x69\x6d\x65\x2f\x36\x2e\x34\x2e\x31\x0d\x0a\x41\x63\x63\x65\x70\x74\x3a\x20\x2a\x2f\x2a\x0d\x0a\x48\x6f\x73\x74\x3a\x20\x6c\x6f" + 26 | "\x63\x61\x6c\x68\x6f\x73\x74\x3a\x38\x30\x38\x30\x0d\x0a\x63\x6f\x6f\x6b\x69\x65\x3a\x20\x76\x69\x73\x69\x74\x5f\x63\x6f\x75\x6e" + 27 | "\x74\x65\x72\x3d\x39\x0d\x0a\x61\x63\x63\x65\x70\x74\x2d\x65\x6e\x63\x6f\x64\x69\x6e\x67\x3a\x20\x67\x7a\x69\x70\x2c\x20\x64\x65" + 28 | "\x66\x6c\x61\x74\x65\x0d\x0a\x63\x6f\x6e\x74\x65\x6e\x74\x2d\x6c\x65\x6e\x67\x74\x68\x3a\x20\x35\x38\x37\x38\x0d\x0a\x43\x6f\x6e" + 29 | "\x6e\x65\x63\x74\x69\x6f\x6e\x3a\x20\x6b\x65\x65\x70\x2d\x61\x6c\x69\x76\x65\x0d\x0a\x0d\x0a\x2d\x2d\x2d\x2d\x2d\x2d\x2d\x2d\x2d" + 30 | "\x2d\x2d\x2d\x2d\x2d\x2d\x2d\x2d\x2d\x2d\x2d\x2d\x2d\x2d\x2d\x2d\x2d\x2d\x2d\x37\x36\x39\x32\x33\x33\x39\x32\x33\x34\x33\x38\x38" + 31 | "\x36\x33\x32\x30\x33\x39\x34\x30\x33\x33\x37\x0d\x0a\x43\x6f\x6e\x74\x65\x6e\x74\x2d\x44\x69\x73\x70\x6f\x73\x69\x74\x69\x6f\x6e" + 32 | "\x3a\x20\x66\x6f\x72\x6d\x2d\x64\x61\x74\x61\x3b\x20\x6e\x61\x6d\x65\x3d\x22\x6e\x61\x6d\x65\x22\x0d\x0a\x0d\x0a\x45\x45\x45\x58" + 33 | "\x58\x58\x20\x59\x6f\x75\x72\x20\x59\x4f\x4c\x4f\x2d\x4d\x61\x6e\x0d\x0a\x2d\x2d\x2d\x2d\x2d\x2d\x2d\x2d\x2d\x2d\x2d\x2d\x2d\x2d" + 34 | "\x2d\x2d\x2d\x2d\x2d\x2d\x2d\x2d\x2d\x2d\x2d\x2d\x2d\x2d\x37\x36\x39\x32\x33\x33\x39\x32\x33\x34\x33\x38\x38\x36\x33\x32\x30\x33" + 35 | "\x39\x34\x30\x33\x33\x37\x0d\x0a\x43\x6f\x6e\x74\x65\x6e\x74\x2d\x44\x69\x73\x70\x6f\x73\x69\x74\x69\x6f\x6e\x3a\x20\x66\x6f\x72" + 36 | "\x6d\x2d\x64\x61\x74\x61\x3b\x20\x6e\x61\x6d\x65\x3d\x22\x61\x67\x65\x22\x0d\x0a\x0d\x0a\x33\x34\x35\x0d\x0a\x2d\x2d\x2d\x2d\x2d" + 37 | "\x2d\x2d\x2d\x2d\x2d\x2d\x2d\x2d\x2d\x2d\x2d\x2d\x2d\x2d\x2d\x2d\x2d\x2d\x2d\x2d\x2d\x2d\x2d\x37\x36\x39\x32\x33\x33\x39\x32\x33" + 38 | "\x34\x33\x38\x38\x36\x33\x32\x30\x33\x39\x34\x30\x33\x33\x37\x0d\x0a\x43\x6f\x6e\x74\x65\x6e\x74\x2d\x44\x69\x73\x70\x6f\x73\x69" + 39 | "\x74\x69\x6f\x6e\x3a\x20\x66\x6f\x72\x6d\x2d\x64\x61\x74\x61\x3b\x20\x6e\x61\x6d\x65\x3d\x22\x74\x68\x65\x46\x69\x6c\x65\x49\x4c" + 40 | "\x69\x6b\x65\x22\x3b\x20\x66\x69\x6c\x65\x6e\x61\x6d\x65\x3d\x22\x75\x70\x6c\x6f\x61\x64\x6d\x65\x2e\x69\x63\x6f\x22\x0d\x0a\x43" + 41 | "\x6f\x6e\x74\x65\x6e\x74\x2d\x54\x79\x70\x65\x3a\x20\x69\x6d\x61\x67\x65\x2f\x78\x2d\x69\x63\x6f\x6e\x0d\x0a\x0d\x0a\x00\x00\x01" + 42 | "\x00\x02\x00\x10\x10\x00\x00\x01\x00\x20\x00\x68\x04\x00\x00\x26\x00\x00\x00\x20\x20\x00\x00\x01\x00\x20\x00\xa8\x10\x00\x00\x8e" + 43 | "\x04\x00\x00\x28\x00\x00\x00\x10\x00\x00\x00\x20\x00\x00\x00\x01\x00\x20\x00\x00\x00\x00\x00\x00\x04\x00\x00\x00\x00\x00\x00\x00" + 44 | "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + 45 | "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + 46 | "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xa9\xa3\x9e\xff\xa9\xa3\x9e\xff\xa9\xa3\x9e\xff\xa9" + 47 | "\xa3\x9e\xff\xa9\xa3\x9e\xff\xa9\xa3\x9e\xff\xa9\xa3\x9e\xff\xa9\xa3\x9e\xff\xa9\xa3\x9e\xff\xa9\xa3\x9e\xff\xa9\xa3\x9e\xff\x00" + 48 | "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xa9\xa3\x9e\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00" + 49 | "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xa9\xa3\x9e\xff\x00" + 50 | "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xa9\xa3\x9e\xff\x00\x00\x00\x00\x09\x70\xf2\xff\x09" + 51 | "\x70\xf2\xff\x09\x70\xf2\xff\x09\x70\xf2\xff\x09\x70\xf2\xff\x09\x70\xf2\xff\x09\x70\xf2\xff\x00\x00\x00\x00\xa9\xa3\x9e\xff\x00" + 52 | "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xa9\xa3\x9e\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00" + 53 | "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x09\x70\xf2\x13\x09\x70\xf2\x36\x00\x00\x00\x00\xa9\xa3\x9e\xff\x00" + 54 | "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x09\x70\xf2\x2f\x09" + 55 | "\x70\xf2\x5d\x09\x70\xf2\x89\x09\x70\xf2\xb6\x09\x70\xf2\xe2\x09\x70\xf2\xef\x09\x70\xf2\xbd\x00\x00\x00\x00\x00\x00\x00\x00\x00" + 56 | "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x09\x70\xf2\xc2\x09" + 57 | "\x70\xf2\xa5\x09\x70\xf2\x77\x09\x70\xf2\x49\x09\x70\xf2\x1d\x09\x70\xf2\x13\x09\x70\xf2\x71\x09\x70\xf2\x2f\x00\x00\x00\x00\x00" + 58 | "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + 59 | "\x00\x00\x00\x00\x00\x00\x00\x09\x70\xf2\x3d\x09\x70\xf2\xa5\x09\x70\xf2\xef\x09\x70\xf2\xa0\x09\x70\xf2\x2f\x00\x00\x00\x00\x00" + 60 | "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x09\x70\xf2\x0f\x09" + 61 | "\x70\xf2\x71\x09\x70\xf2\xd6\x09\x70\xf2\xd3\x09\x70\xf2\x6b\x09\x70\xf2\x0f\x09\x70\xf2\x3d\x09\x70\xf2\xe2\x09\x70\xf2\x27\x00" + 62 | "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x09\x70\xf2\x23\x09" + 63 | "\x70\xf2\xa0\x09\x70\xf2\x3a\x00\x00\x00\x00\x00\x00\x00\x00\x09\x70\xf2\x77\x09\x70\xf2\xe8\x09\x70\xf2\x46\x09\x70\xf2\x51\x09" + 64 | "\x70\xf2\x82\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + 65 | "\x00\x00\x00\x00\x00\x00\x00\x09\x70\xf2\x13\x09\x70\xf2\xb6\x09\x70\xf2\xc2\x09\x70\xf2\x1d\x09\x70\xf2\x18\x09\x70\xf2\xe8\x09" + 66 | "\x70\xf2\x36\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + 67 | "\x00\x00\x00\x09\x70\xf2\x3a\x09\x70\xf2\xe2\x09\x70\xf2\x89\x09\x70\xf2\x03\x00\x00\x00\x00\x09\x70\xf2\xb6\x09\x70\xf2\x7e\x00" + 68 | "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + 69 | "\x00\x00\x00\x09\x70\xf2\x64\x09\x70\xf2\x49\x00\x00\x00\x00\x00\x00\x00\x00\x09\x70\xf2\x6b\x09\x70\xf2\xc8\x09\x70\xf2\x03\x00" + 70 | "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + 71 | "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x09\x70\xf2\x27\x09\x70\xf2\xe8\x09\x70\xf2\x23\x00\x00\x00\x00\x00" + 72 | "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + 73 | "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x09\x70\xf2\xc2\x09\x70\xf2\x64\x00\x00\x00\x00\x00\x00\x00\x00\x00" + 74 | "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + 75 | "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x09\x70\xf2\x1d\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + 76 | "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\x00\x00\xc0\x07\x00\x00\xdf\xf7\x00\x00\xd0\x17\x00\x00\xdf\x97\x00\x00\xf0" + 77 | "\x1f\x00\x00\xf0\x0f\x00\x00\xfe\x0f\x00\x00\xf0\x07\x00\x00\xf1\x83\x00\x00\xfe\x03\x00\x00\xfc\x27\x00\x00\xfc\xc7\x00\x00\xff" + 78 | "\x8f\x00\x00\xff\x9f\x00\x00\xff\xbf\x00\x00\x28\x00\x00\x00\x20\x00\x00\x00\x40\x00\x00\x00\x01\x00\x20\x00\x00\x00\x00\x00\x00" + 79 | "\x10\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff" + 80 | "\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff" + 81 | "\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff" + 82 | "\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff" + 83 | "\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff" + 84 | "\xff\xff\x00\xff\xff\xff\x00\xa9\xa3\x9e\x00\xa9\xa3\x9e\x00\xa9\xa3\x9e\x00\xa9\xa3\x9e\x00\xa9\xa3\x9e\x00\xa9\xa3\x9e\x00\xa9" + 85 | "\xa3\x9e\x00\xa9\xa3\x9e\x00\xa9\xa3\x9e\x00\xa9\xa3\x9e\x00\xa9\xa3\x9e\x00\xa9\xa3\x9e\x00\xa9\xa3\x9e\x00\xa9\xa3\x9e\x00\xa9" + 86 | "\xa3\x9e\x00\xa9\xa3\x9e\x00\xa9\xa3\x9e\x00\xa9\xa3\x9e\x00\xa9\xa3\x9e\x00\xa9\xa3\x9e\x00\xa9\xa3\x9e\x00\xa9\xa3\x9e\x00\xff" + 87 | "\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff" + 88 | "\xff\xff\x00\xff\xff\xff\x00\xa9\xa3\x9e\x00\xa9\xa3\x9e\x00\xa9\xa3\x9e\x00\xa9\xa3\x9e\x00\xa9\xa3\x9e\x00\xa9\xa3\x9e\x00\xa9" + 89 | "\xa3\x9e\x00\xa9\xa3\x9e\x00\xa9\xa3\x9e\x00\xa9\xa3\x9e\x00\xa9\xa3\x9e\x00\xa9\xa3\x9e\x00\xa9\xa3\x9e\x00\xa9\xa3\x9e\x00\xa9" + 90 | "\xa3\x9e\x00\xa9\xa3\x9e\x00\xa9\xa3\x9e\x00\xa9\xa3\x9e\x00\xa9\xa3\x9e\x00\xa9\xa3\x9e\x00\xa9\xa3\x9e\x00\xa9\xa3\x9e\x00\xff" + 91 | "\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff" + 92 | "\xff\xff\x00\xff\xff\xff\x00\xa9\xa3\x9e\x00\xa9\xa3\x9e\xff\xa9\xa3\x9e\xff\xa9\xa3\x9e\xff\xa9\xa3\x9e\xff\xa9\xa3\x9e\xff\xa9" + 93 | "\xa3\x9e\xff\xa9\xa3\x9e\xff\xa9\xa3\x9e\xff\xa9\xa3\x9e\xff\xa9\xa3\x9e\xff\xa9\xa3\x9e\xff\xa9\xa3\x9e\xff\xa9\xa3\x9e\xff\xa9" + 94 | "\xa3\x9e\xff\xa9\xa3\x9e\xff\xa9\xa3\x9e\xff\xa9\xa3\x9e\xff\xa9\xa3\x9e\xff\xa9\xa3\x9e\xff\xa9\xa3\x9e\xff\xa9\xa3\x9e\x00\xff" + 95 | "\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff" + 96 | "\xff\xff\x00\xff\xff\xff\x00\xa9\xa3\x9e\x00\xa9\xa3\x9e\xff\xa9\xa3\x9e\xff\xa9\xa3\x9e\xff\xa9\xa3\x9e\xff\xa9\xa3\x9e\xff\xa9" + 97 | "\xa3\x9e\xff\xa9\xa3\x9e\xff\xa9\xa3\x9e\xff\xa9\xa3\x9e\xff\xa9\xa3\x9e\xff\xa9\xa3\x9e\xff\xa9\xa3\x9e\xff\xa9\xa3\x9e\xff\xa9" + 98 | "\xa3\x9e\xff\xa9\xa3\x9e\xff\xa9\xa3\x9e\xff\xa9\xa3\x9e\xff\xa9\xa3\x9e\xff\xa9\xa3\x9e\xff\xa9\xa3\x9e\xff\xa9\xa3\x9e\x00\xff" + 99 | "\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff" + 100 | "\xff\xff\x00\xff\xff\xff\x00\xa9\xa3\x9e\x00\xa9\xa3\x9e\xff\xa9\xa3\x9e\xff\x24\x80\xf4\x00\x24\x80\xf4\x00\x24\x80\xf4\x00\x24" + 101 | "\x80\xf4\x00\x24\x80\xf4\x00\x24\x80\xf4\x00\x24\x80\xf4\x00\x24\x80\xf4\x00\x24\x80\xf4\x00\x24\x80\xf4\x00\x24\x80\xf4\x00\x24" + 102 | "\x80\xf4\x00\x24\x80\xf4\x00\x24\x80\xf4\x00\x24\x80\xf4\x00\x24\x80\xf4\x00\xa9\xa3\x9e\xff\xa9\xa3\x9e\xff\x24\x80\xf4\x00\x24" + 103 | "\x80\xf4\x00\x24\x80\xf4\x00\x24\x80\xf4\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff" + 104 | "\xff\xff\x00\xff\xff\xff\x00\xa9\xa3\x9e\x00\xa9\xa3\x9e\xff\xa9\xa3\x9e\xff\x24\x80\xf4\x00\x24\x80\xf4\x00\x24\x80\xf4\x00\x24" + 105 | "\x80\xf4\x00\x24\x80\xf4\x00\x24\x80\xf4\x00\x24\x80\xf4\x00\x24\x80\xf4\x00\x24\x80\xf4\x00\x24\x80\xf4\x00\x24\x80\xf4\x00\x24" + 106 | "\x80\xf4\x00\x24\x80\xf4\x00\x24\x80\xf4\x00\x24\x80\xf4\x00\x24\x80\xf4\x00\xa9\xa3\x9e\xff\xa9\xa3\x9e\xff\x24\x80\xf4\x00\x24" + 107 | "\x80\xf4\x00\x24\x80\xf4\x00\x24\x80\xf4\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff" + 108 | "\xff\xff\x00\xff\xff\xff\x00\xa9\xa3\x9e\x00\xa9\xa3\x9e\xff\xa9\xa3\x9e\xff\x24\x80\xf4\x00\x24\x80\xf4\x00\x24\x80\xf4\xff\x24" + 109 | "\x80\xf4\xff\x24\x80\xf4\xff\x24\x80\xf4\xff\x24\x80\xf4\xff\x24\x80\xf4\xff\x24\x80\xf4\xff\x24\x80\xf4\xff\x24\x80\xf4\xff\x24" + 110 | "\x80\xf4\xff\x24\x80\xf4\xff\x24\x80\xf4\xff\x24\x80\xf4\x00\x24\x80\xf4\x00\xa9\xa3\x9e\xff\xa9\xa3\x9e\xff\x24\x80\xf4\x00\x24" + 111 | "\x80\xf4\x00\x24\x80\xf4\x00\x24\x80\xf4\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff" + 112 | "\xff\xff\x00\xff\xff\xff\x00\xa9\xa3\x9e\x00\xa9\xa3\x9e\xff\xa9\xa3\x9e\xff\x24\x80\xf4\x00\x24\x80\xf4\x00\x24\x80\xf4\xff\x24" + 113 | "\x80\xf4\xff\x24\x80\xf4\xff\x24\x80\xf4\xff\x24\x80\xf4\xff\x24\x80\xf4\xff\x24\x80\xf4\xff\x24\x80\xf4\xff\x24\x80\xf4\xff\x24" + 114 | "\x80\xf4\xff\x24\x80\xf4\xff\x24\x80\xf4\xff\x24\x80\xf4\x00\x24\x80\xf4\x00\xa9\xa3\x9e\xff\xa9\xa3\x9e\xff\x24\x80\xf4\x00\x24" + 115 | "\x80\xf4\x00\x24\x80\xf4\x00\x24\x80\xf4\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff" + 116 | "\xff\xff\x00\xff\xff\xff\x00\xa9\xa3\x9e\x00\xa9\xa3\x9e\xff\xa9\xa3\x9e\xff\x24\x80\xf4\x00\x24\x80\xf4\x00\x24\x80\xf4\x00\x24" + 117 | "\x80\xf4\x00\x24\x80\xf4\x00\x24\x80\xf4\x00\x24\x80\xf4\x00\x24\x80\xf4\x00\x24\x80\xf4\x00\x24\x80\xf4\x00\x24\x80\xf4\x00\x24" + 118 | "\x80\xf4\x00\x24\x80\xf4\x00\x24\x80\xf4\x00\x24\x80\xf4\x00\x24\x80\xf4\x00\xa9\xa3\x9e\xff\xa9\xa3\x9e\xff\x24\x80\xf4\x00\x24" + 119 | "\x80\xf4\x00\x24\x80\xf4\x00\x24\x80\xf4\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff" + 120 | "\xff\xff\x00\xff\xff\xff\x00\xa9\xa3\x9e\x00\xa9\xa3\x9e\xff\xa9\xa3\x9e\xff\x24\x80\xf4\x00\x24\x80\xf4\x00\x24\x80\xf4\x00\x24" + 121 | "\x80\xf4\x00\x24\x80\xf4\x00\x24\x80\xf4\x00\x24\x80\xf4\x00\x24\x80\xf4\x00\x24\x80\xf4\x00\x24\x80\xf4\x1e\x24\x80\xf4\x51\x24" + 122 | "\x80\xf4\x81\x24\x80\xf4\xb4\x24\x80\xf4\xe3\x24\x80\xf4\x12\x24\x80\xf4\x00\xa9\xa3\x9e\xff\xa9\xa3\x9e\xff\x24\x80\xf4\x00\x24" + 123 | "\x80\xf4\x00\x24\x80\xf4\x00\x24\x80\xf4\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff" + 124 | "\xff\xff\x00\xff\xff\xff\x00\xa9\xa3\x9e\x00\xa9\xa3\x9e\x00\xa9\xa3\x9e\x00\x24\x80\xf4\x00\x24\x80\xf4\x00\x24\x80\xf4\x00\x24" + 125 | "\x80\xf4\x02\x24\x80\xf4\x26\x24\x80\xf4\x57\x24\x80\xf4\x89\x24\x80\xf4\xba\x24\x80\xf4\xed\x24\x80\xf4\xff\x24\x80\xf4\xff\x24" + 126 | "\x80\xf4\xff\x24\x80\xf4\xff\x24\x80\xf4\xff\x24\x80\xf4\x4a\x24\x80\xf4\x00\x24\x80\xf4\x00\x24\x80\xf4\x00\x24\x80\xf4\x00\x24" + 127 | "\x80\xf4\x00\x24\x80\xf4\x00\x24\x80\xf4\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff" + 128 | "\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\x24\x80\xf4\x00\x24\x80\xf4\x00\x24\x80\xf4\xb8\x24" + 129 | "\x80\xf4\xf1\x24\x80\xf4\xff\x24\x80\xf4\xff\x24\x80\xf4\xff\x24\x80\xf4\xff\x24\x80\xf4\xff\x24\x80\xf4\xff\x24\x80\xf4\xf9\x24" + 130 | "\x80\xf4\xcd\x24\x80\xf4\x9a\x24\x80\xf4\x6a\x24\x80\xf4\x24\x24\x80\xf4\x00\x24\x80\xf4\x00\x24\x80\xf4\x00\x24\x80\xf4\x00\x24" + 131 | "\x80\xf4\x00\x24\x80\xf4\x00\x24\x80\xf4\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff" + 132 | "\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\x24\x80\xf4\x00\x24\x80\xf4\x00\x24\x80\xf4\xbd\x24" + 133 | "\x80\xf4\xff\x24\x80\xf4\xff\x24\x80\xf4\xf4\x24\x80\xf4\xc5\x24\x80\xf4\x94\x24\x80\xf4\x62\x24\x80\xf4\x31\x24\x80\xf4\x05\x24" + 134 | "\x80\xf4\x00\x24\x80\xf4\x34\x24\x80\xf4\xa3\x24\x80\xf4\xe6\x24\x80\xf4\x0d\x24\x80\xf4\x00\x24\x80\xf4\x00\x24\x80\xf4\x00\x24" + 135 | "\x80\xf4\x00\x24\x80\xf4\x00\x24\x80\xf4\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff" + 136 | "\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\x24\x80\xf4\x00\x24\x80\xf4\x00\x24\x80\xf4\x4a\x24" + 137 | "\x80\xf4\x5b\x24\x80\xf4\x29\x24\x80\xf4\x03\x24\x80\xf4\x00\x24\x80\xf4\x00\x24\x80\xf4\x00\x24\x80\xf4\x05\x24\x80\xf4\x58\x24" + 138 | "\x80\xf4\xc7\x24\x80\xf4\xff\x24\x80\xf4\xff\x24\x80\xf4\xff\x24\x80\xf4\x78\x24\x80\xf4\x00\x24\x80\xf4\x00\x24\x80\xf4\x00\x24" + 139 | "\x80\xf4\x00\x24\x80\xf4\x00\x24\x80\xf4\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff" + 140 | "\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\x24\x80\xf4\x00\x24\x80\xf4\x00\x24\x80\xf4\x00\x24" + 141 | "\x80\xf4\x00\x24\x80\xf4\x00\x24\x80\xf4\x00\x24\x80\xf4\x00\x24\x80\xf4\x15\x24\x80\xf4\x7c\x24\x80\xf4\xe4\x24\x80\xf4\xff\x24" + 142 | "\x80\xf4\xff\x24\x80\xf4\xff\x24\x80\xf4\xd9\x24\x80\xf4\x6c\x24\x80\xf4\x3d\x24\x80\xf4\x2c\x24\x80\xf4\x00\x24\x80\xf4\x00\x24" + 143 | "\x80\xf4\x00\x24\x80\xf4\x00\x24\x80\xf4\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff" + 144 | "\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\x24\x80\xf4\x00\x24\x80\xf4\x00\x24\x80\xf4\x00\x24" + 145 | "\x80\xf4\x00\x24\x80\xf4\x00\x24\x80\xf4\x32\x24\x80\xf4\x9f\x24\x80\xf4\xf8\x24\x80\xf4\xff\x24\x80\xf4\xff\x24\x80\xf4\xfe\x24" + 146 | "\x80\xf4\xb7\x24\x80\xf4\x49\x24\x80\xf4\x02\x24\x80\xf4\x62\x24\x80\xf4\xf7\x24\x80\xf4\xe3\x24\x80\xf4\x1c\x24\x80\xf4\x00\x24" + 147 | "\x80\xf4\x00\x24\x80\xf4\x00\x24\x80\xf4\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff" + 148 | "\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\x24\x80\xf4\x00\x24\x80\xf4\x00\x24\x80\xf4\x00\x24" + 149 | "\x80\xf4\x2a\x24\x80\xf4\xc2\x24\x80\xf4\xff\x24\x80\xf4\xff\x24\x80\xf4\xff\x24\x80\xf4\xf2\x24\x80\xf4\x94\x24\x80\xf4\x26\x24" + 150 | "\x80\xf4\x00\x24\x80\xf4\x08\x24\x80\xf4\x9f\x24\x80\xf4\xff\x24\x80\xf4\xff\x24\x80\xf4\xe9\x24\x80\xf4\x46\x24\x80\xf4\x23\x24" + 151 | "\x80\xf4\x00\x24\x80\xf4\x00\x24\x80\xf4\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff" + 152 | "\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\x24\x80\xf4\x00\x24\x80\xf4\x00\x24\x80\xf4\x00\x24" + 153 | "\x80\xf4\x0f\x24\x80\xf4\xee\x24\x80\xf4\xff\x24\x80\xf4\xdc\x24\x80\xf4\x70\x24\x80\xf4\x0f\x24\x80\xf4\x00\x24\x80\xf4\x00\x24" + 154 | "\x80\xf4\x24\x24\x80\xf4\xcf\x24\x80\xf4\xff\x24\x80\xf4\xff\x24\x80\xf4\xc3\x24\x80\xf4\x1c\x24\x80\xf4\xae\x24\x80\xf4\xf3\x24" + 155 | "\x80\xf4\x66\x24\x80\xf4\x00\x24\x80\xf4\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff" + 156 | "\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\x24\x80\xf4\x00\x24\x80\xf4\x00\x24\x80\xf4\x00\x24" + 157 | "\x80\xf4\x00\x24\x80\xf4\x5a\x24\x80\xf4\x4c\x24\x80\xf4\x02\x24\x80\xf4\x00\x24\x80\xf4\x00\x24\x80\xf4\x00\x24\x80\xf4\x50\x24" + 158 | "\x80\xf4\xef\x24\x80\xf4\xff\x24\x80\xf4\xfe\x24\x80\xf4\x8e\x24\x80\xf4\x05\x24\x80\xf4\x7e\x24\x80\xf4\xff\x24\x80\xf4\xff\x24" + 159 | "\x80\xf4\x8e\x24\x80\xf4\x00\x24\x80\xf4\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff" + 160 | "\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\x24\x80\xf4\x00\x24\x80\xf4\x00\x24\x80\xf4\x00\x24" + 161 | "\x80\xf4\x00\x24\x80\xf4\x00\x24\x80\xf4\x00\x24\x80\xf4\x00\x24\x80\xf4\x00\x24\x80\xf4\x04\x24\x80\xf4\x88\x24\x80\xf4\xfe\x24" + 162 | "\x80\xf4\xff\x24\x80\xf4\xf1\x24\x80\xf4\x54\x24\x80\xf4\x00\x24\x80\xf4\x4d\x24\x80\xf4\xfe\x24\x80\xf4\xff\x24\x80\xf4\xbb\x24" + 163 | "\x80\xf4\x03\x24\x80\xf4\x00\x24\x80\xf4\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff" + 164 | "\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\x24\x80\xf4\x00\x24\x80\xf4\x00\x24\x80\xf4\x00\x24" + 165 | "\x80\xf4\x00\x24\x80\xf4\x00\x24\x80\xf4\x00\x24\x80\xf4\x00\x24\x80\xf4\x18\x24\x80\xf4\xc0\x24\x80\xf4\xff\x24\x80\xf4\xff\x24" + 166 | "\x80\xf4\xd3\x24\x80\xf4\x26\x24\x80\xf4\x00\x24\x80\xf4\x28\x24\x80\xf4\xf0\x24\x80\xf4\xff\x24\x80\xf4\xde\x24\x80\xf4\x14\x24" + 167 | "\x80\xf4\x00\x24\x80\xf4\x00\x24\x80\xf4\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff" + 168 | "\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\x24\x80\xf4\x00\x24\x80\xf4\x00\x24\x80\xf4\x00\x24" + 169 | "\x80\xf4\x00\x24\x80\xf4\x00\x24\x80\xf4\x00\x24\x80\xf4\x1f\x24\x80\xf4\xe7\x24\x80\xf4\xff\x24\x80\xf4\xff\x24\x80\xf4\xa2\x24" + 170 | "\x80\xf4\x0a\x24\x80\xf4\x00\x24\x80\xf4\x0f\x24\x80\xf4\xd8\x24\x80\xf4\xff\x24\x80\xf4\xf5\x24\x80\xf4\x2e\x24\x80\xf4\x00\x24" + 171 | "\x80\xf4\x00\x24\x80\xf4\x00\x24\x80\xf4\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff" + 172 | "\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\x24\x80\xf4\x00\x24\x80\xf4\x00\x24\x80\xf4\x00\x24" + 173 | "\x80\xf4\x00\x24\x80\xf4\x00\x24\x80\xf4\x00\x24\x80\xf4\x00\x24\x80\xf4\x96\x24\x80\xf4\xf8\x24\x80\xf4\x67\x24\x80\xf4\x00\x24" + 174 | "\x80\xf4\x00\x24\x80\xf4\x02\x24\x80\xf4\xb4\x24\x80\xf4\xff\x24\x80\xf4\xfe\x24\x80\xf4\x56\x24\x80\xf4\x00\x24\x80\xf4\x00\x24" + 175 | "\x80\xf4\x00\x24\x80\xf4\x00\x24\x80\xf4\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff" + 176 | "\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\x24\x80\xf4\x00\x24\x80\xf4\x00\x24\x80\xf4\x00\x24" + 177 | "\x80\xf4\x00\x24\x80\xf4\x00\x24\x80\xf4\x00\x24\x80\xf4\x00\x24\x80\xf4\x03\x24\x80\xf4\x2a\x24\x80\xf4\x00\x24\x80\xf4\x00\x24" + 178 | "\x80\xf4\x00\x24\x80\xf4\x82\x24\x80\xf4\xff\x24\x80\xf4\xff\x24\x80\xf4\x88\x24\x80\xf4\x00\x24\x80\xf4\x00\x24\x80\xf4\x00\x24" + 179 | "\x80\xf4\x00\x24\x80\xf4\x00\x24\x80\xf4\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff" + 180 | "\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\x24\x80\xf4\x00\x24\x80\xf4\x00\x24\x80\xf4\x00\x24" + 181 | "\x80\xf4\x00\x24\x80\xf4\x00\x24\x80\xf4\x00\x24\x80\xf4\x00\x24\x80\xf4\x00\x24\x80\xf4\x00\x24\x80\xf4\x00\x24\x80\xf4\x00\x24" + 182 | "\x80\xf4\x53\x24\x80\xf4\xfe\x24\x80\xf4\xff\x24\x80\xf4\xb7\x24\x80\xf4\x03\x24\x80\xf4\x00\x24\x80\xf4\x00\x24\x80\xf4\x00\x24" + 183 | "\x80\xf4\x00\x24\x80\xf4\x00\x24\x80\xf4\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff" + 184 | "\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\x24\x80\xf4\x00\x24\x80\xf4\x00\x24\x80\xf4\x00\x24" + 185 | "\x80\xf4\x00\x24\x80\xf4\x00\x24\x80\xf4\x00\x24\x80\xf4\x00\x24\x80\xf4\x00\x24\x80\xf4\x00\x24\x80\xf4\x00\x24\x80\xf4\x0f\x24" + 186 | "\x80\xf4\xef\x24\x80\xf4\xff\x24\x80\xf4\xdb\x24\x80\xf4\x10\x24\x80\xf4\x00\x24\x80\xf4\x00\x24\x80\xf4\x00\x24\x80\xf4\x00\x24" + 187 | "\x80\xf4\x00\x24\x80\xf4\x00\x24\x80\xf4\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff" + 188 | "\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\x24\x80\xf4\x00\x24\x80\xf4\x00\x24\x80\xf4\x00\x24" + 189 | "\x80\xf4\x00\x24\x80\xf4\x00\x24\x80\xf4\x00\x24\x80\xf4\x00\x24\x80\xf4\x00\x24\x80\xf4\x00\x24\x80\xf4\x00\x24\x80\xf4\x00\x24" + 190 | "\x80\xf4\x34\x24\x80\xf4\xc8\x24\x80\xf4\x2c\x24\x80\xf4\x00\x24\x80\xf4\x00\x24\x80\xf4\x00\x24\x80\xf4\x00\x24\x80\xf4\x00\x24" + 191 | "\x80\xf4\x00\x24\x80\xf4\x00\x24\x80\xf4\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff" + 192 | "\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\x24\x80\xf4\x00\x24\x80\xf4\x00\x24\x80\xf4\x00\x24" + 193 | "\x80\xf4\x00\x24\x80\xf4\x00\x24\x80\xf4\x00\x24\x80\xf4\x00\x24\x80\xf4\x00\x24\x80\xf4\x00\x24\x80\xf4\x00\x24\x80\xf4\x00\x24" + 194 | "\x80\xf4\x00\x24\x80\xf4\x00\x24\x80\xf4\x00\x24\x80\xf4\x00\x24\x80\xf4\x00\x24\x80\xf4\x00\x24\x80\xf4\x00\x24\x80\xf4\x00\x24" + 195 | "\x80\xf4\x00\x24\x80\xf4\x00\x24\x80\xf4\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff" + 196 | "\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\x24\x80\xf4\x00\x24\x80\xf4\x00\x24\x80\xf4\x00\x24" + 197 | "\x80\xf4\x00\x24\x80\xf4\x00\x24\x80\xf4\x00\x24\x80\xf4\x00\x24\x80\xf4\x00\x24\x80\xf4\x00\x24\x80\xf4\x00\x24\x80\xf4\x00\x24" + 198 | "\x80\xf4\x00\x24\x80\xf4\x00\x24\x80\xf4\x00\x24\x80\xf4\x00\x24\x80\xf4\x00\x24\x80\xf4\x00\x24\x80\xf4\x00\x24\x80\xf4\x00\x24" + 199 | "\x80\xf4\x00\x24\x80\xf4\x00\x24\x80\xf4\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff" + 200 | "\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff" + 201 | "\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff" + 202 | "\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff" + 203 | "\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff" + 204 | "\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff" + 205 | "\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff" + 206 | "\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff" + 207 | "\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xfc" + 208 | "\x00\x00\x3f\xfc\x00\x00\x3f\xfc\xff\xff\x3f\xfc\xff\xff\x3f\xfc\xc0\x03\x3f\xfc\xc0\x03\x3f\xfc\xff\xff\x3f\xfc\xff\x81\x3f\xff" + 209 | "\xe0\x01\xff\xff\xc0\x01\xff\xff\xc0\x10\xff\xff\xc3\x80\xff\xff\xfe\x00\x7f\xff\xf8\x00\x3f\xff\xe0\x10\x1f\xff\xe0\x60\x0f\xff" + 210 | "\xf1\xc0\x0f\xff\xff\x02\x0f\xff\xfe\x04\x1f\xff\xfc\x08\x3f\xff\xfe\x30\x7f\xff\xfe\x70\xff\xff\xff\xe0\xff\xff\xff\xc1\xff\xff" + 211 | "\xff\xe3\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x0d\x0a\x2d\x2d\x2d\x2d\x2d\x2d\x2d\x2d\x2d\x2d\x2d" + 212 | "\x2d\x2d\x2d\x2d\x2d\x2d\x2d\x2d\x2d\x2d\x2d\x2d\x2d\x2d\x2d\x2d\x2d\x37\x36\x39\x32\x33\x33\x39\x32\x33\x34\x33\x38\x38\x36\x33" + 213 | "\x32\x30\x33\x39\x34\x30\x33\x33\x37\x2d\x2d\x0d\x0a") 214 | 215 | var err error 216 | err = ioutil.WriteFile("./15.bin", bytes15, 0777) 217 | err = ioutil.WriteFile("./20.bin", bytes20, 0777) 218 | err = ioutil.WriteFile("./ABCD.bin", bytesABCD, 0777) 219 | err = ioutil.WriteFile("./ls.bin", bytesLS, 0777) 220 | err = ioutil.WriteFile("./ico.bin", bytesICO, 0777) 221 | err = ioutil.WriteFile("./req_abcd.bin", bytesReqABCD, 0777) 222 | err = ioutil.WriteFile("./req_ico.bin", bytesReqICO, 0777) 223 | 224 | if err != nil { 225 | fmt.Println("Error", err) 226 | } 227 | 228 | fmt.Println("Very data, much bytes. Wow.") 229 | } 230 | -------------------------------------------------------------------------------- /testdata/ico.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/x1n13y84issmd42/bashttpd/b1f56aed60688ff49f53fc11aa3f04fd62fffd3b/testdata/ico.bin -------------------------------------------------------------------------------- /testdata/llq.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "message": "Betmiga 50mg mot urge inkontinens\nRelvar Ellipta 92/22 mikrogram mot astma\n\"Aerius\" mot allergi ", 4 | "gender": "Kvinne", 5 | "age": 29, 6 | "deadline": "2019-08-07T23:59:59.563Z", 7 | "isPublished": false 8 | } 9 | ] -------------------------------------------------------------------------------- /testdata/ls.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/x1n13y84issmd42/bashttpd/b1f56aed60688ff49f53fc11aa3f04fd62fffd3b/testdata/ls.bin -------------------------------------------------------------------------------- /testdata/req_ABCD.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/x1n13y84issmd42/bashttpd/b1f56aed60688ff49f53fc11aa3f04fd62fffd3b/testdata/req_ABCD.bin -------------------------------------------------------------------------------- /testdata/req_ico.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/x1n13y84issmd42/bashttpd/b1f56aed60688ff49f53fc11aa3f04fd62fffd3b/testdata/req_ico.bin -------------------------------------------------------------------------------- /testdata/req_png.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/x1n13y84issmd42/bashttpd/b1f56aed60688ff49f53fc11aa3f04fd62fffd3b/testdata/req_png.bin -------------------------------------------------------------------------------- /testdata/uploadme.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/x1n13y84issmd42/bashttpd/b1f56aed60688ff49f53fc11aa3f04fd62fffd3b/testdata/uploadme.ico -------------------------------------------------------------------------------- /testdata/uploadme.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/x1n13y84issmd42/bashttpd/b1f56aed60688ff49f53fc11aa3f04fd62fffd3b/testdata/uploadme.jpg -------------------------------------------------------------------------------- /testdata/uploadme.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/x1n13y84issmd42/bashttpd/b1f56aed60688ff49f53fc11aa3f04fd62fffd3b/testdata/uploadme.png -------------------------------------------------------------------------------- /testdata/uploadme.txt: -------------------------------------------------------------------------------- 1 | uploadme 2 | 123 3 | 123 4 | 123 A 5 | /// 6 | 7 | /// 8 | &&& -------------------------------------------------------------------------------- /testdata/uploadme.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | false 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 1 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | --------------------------------------------------------------------------------