├── .gitignore ├── LICENSE ├── README.md ├── README_EN.md ├── data ├── crawl_mode_suffix.txt ├── dict_mode_dict.txt ├── dictmult │ ├── BAK.min.txt │ ├── BAK.txt │ └── LEAKS.txt ├── fuzz_mode_dir.txt ├── fuzz_mode_ext.txt ├── fuzzmult │ ├── fuzz_mode_dir.txt │ └── fuzz_mode_ext.txt └── user_agents.txt ├── dirmap.conf ├── dirmap.py ├── doc ├── blast_mode.png ├── crawl_mode.png ├── dict_mode.png ├── dirmap.png ├── donate.jpg ├── fuzz_mode.png └── recursive_scan.png ├── lib ├── __init__.py ├── controller │ ├── __init__.py │ ├── bruter.py │ └── engine.py ├── core │ ├── __init__.py │ ├── common.py │ ├── data.py │ ├── datatype.py │ ├── enums.py │ ├── option.py │ └── setting.py ├── parse │ ├── __init__.py │ └── cmdline.py ├── plugins │ ├── __init__.py │ └── inspector.py └── utils │ ├── __init__.py │ ├── config.py │ └── console.py ├── requirement.txt └── thirdlib ├── IPy ├── .gitignore ├── AUTHORS ├── COPYING ├── ChangeLog ├── IPy.py ├── MANIFEST.in ├── Makefile ├── README ├── example │ ├── confbuilder │ └── confbuilder.py ├── setup.py ├── test │ ├── test.rst │ ├── test_IPy.py │ └── test_fuzz.py └── test_doc.py └── colorama ├── __init__.py ├── ansi.py ├── ansitowin32.py ├── initialise.py ├── win32.py └── winterm.py /.gitignore: -------------------------------------------------------------------------------- 1 | __pycache__/ 2 | __init__.pyc 3 | *.py[cod] 4 | output/ 5 | test_dict.txt 6 | url.txt 7 | tester.py 8 | launch.json -------------------------------------------------------------------------------- /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 | . 675 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 7 | # Dirmap 8 | 9 | [English](./README_EN.md) 10 | 11 | 一个高级web目录扫描工具,功能将会强于DirBuster、Dirsearch、cansina、御剑 12 | 13 | ![dirmap](doc/dirmap.png) 14 | 15 | # 需求分析 16 | 17 | 经过大量调研,总结一个优秀的web目录扫描工具至少具备以下功能: 18 | 19 | - 并发引擎 20 | - 能使用字典 21 | - 能纯爆破 22 | - 能爬取页面动态生成字典 23 | - 能fuzz扫描 24 | - 自定义请求 25 | - 自定义响应结果处理... 26 | 27 | 那么接下来看看Dirmap的**特点**吧 28 | 29 | # 功能特点 30 | 31 | 1. 支持n个target\*n个payload并发 32 | 2. 支持递归扫描 33 | 3. 支持自定义需要递归扫描的状态码 34 | 4. 支持(单|多)字典扫描 35 | 5. 支持自定义字符集爆破 36 | 6. 支持爬虫动态字典扫描 37 | 7. 支持自定义标签fuzz目标url 38 | 8. 自定义请求User-Agent 39 | 9. 自定义请求随机延时 40 | 10. 自定义请求超时时间 41 | 11. 自定义请求代理 42 | 12. 自定义正则表达式匹配假性404页面 43 | 13. 自定义要处理的响应状态码 44 | 14. 自定义跳过大小为x的页面 45 | 15. 自定义显示content-type 46 | 16. 自定义显示页面大小 47 | 17. 按域名去重复保存结果 48 | 49 | # 使用方法 50 | 51 | ## 环境准备 52 | 53 | ```shell 54 | git clone https://github.com/H4ckForJob/dirmap.git && cd dirmap && python3 -m pip install -r requirement.txt 55 | ``` 56 | 57 | ## 快速使用 58 | 59 | ### 输入目标 60 | 61 | 单目标,默认为http 62 | 63 | ```shell 64 | python3 dirmap.py -i https://target.com -lcf 65 | ``` 66 | 67 | ```shell 68 | python3 dirmap.py -i 192.168.1.1 -lcf 69 | ``` 70 | 71 | 子网(CIDR格式) 72 | 73 | ```shell 74 | python3 dirmap.py -i 192.168.1.0/24 -lcf 75 | ``` 76 | 77 | 网络范围 78 | 79 | ```shell 80 | python3 dirmap.py -i 192.168.1.1-192.168.1.100 -lcf 81 | ``` 82 | 83 | ### 文件读取 84 | 85 | ```shell 86 | python3 dirmap.py -iF targets.txt -lcf 87 | ``` 88 | 89 | `targets.txt`中支持上述格式 90 | 91 | ### 结果保存 92 | 93 | 1. 结果将自动保存在项目根目录下的`output`文件夹中 94 | 2. 每一个目标生成一个txt,命名格式为`目标域名.txt` 95 | 3. 结果自动去重复,不用担心产生大量冗余 96 | 97 | ## 高级使用 98 | 99 | 自定义dirmap配置,开始探索dirmap高级功能 100 | 101 | 暂时采用加载配置文件的方式进行详细配置,**不支持使用命令行参数进行详细配置**! 102 | 103 | 编辑项目根目录下的`dirmap.conf`,进行配置 104 | 105 | `dirmap.conf`配置详解 106 | 107 | ``` 108 | #递归扫描处理配置 109 | [RecursiveScan] 110 | #是否开启递归扫描:关闭:0;开启:1 111 | conf.recursive_scan = 0 112 | #遇到这些状态码,开启递归扫描。默认配置[301,403] 113 | conf.recursive_status_code = [301,403] 114 | #URL超过这个长度就退出扫描 115 | conf.recursive_scan_max_url_length = 60 116 | #这些后缀名不递归扫 117 | conf.recursive_blacklist_exts = ["html",'htm','shtml','png','jpg','webp','bmp','js','css','pdf','ini','mp3','mp4'] 118 | #设置排除扫描的目录。默认配置空。其他配置:e.g:['/test1','/test2'] 119 | #conf.exclude_subdirs = ['/test1','/test2'] 120 | conf.exclude_subdirs = "" 121 | 122 | #扫描模式处理配置(4个模式,1次只能选择1个) 123 | [ScanModeHandler] 124 | #字典模式:关闭:0;单字典:1;多字典:2 125 | conf.dict_mode = 1 126 | #单字典模式的路径 127 | conf.dict_mode_load_single_dict = "dict_mode_dict.txt" 128 | #多字典模式的路径,默认配置dictmult 129 | conf.dict_mode_load_mult_dict = "dictmult" 130 | #爆破模式:关闭:0;开启:1 131 | conf.blast_mode = 0 132 | #生成字典最小长度。默认配置3 133 | conf.blast_mode_min = 3 134 | #生成字典最大长度。默认配置3 135 | conf.blast_mode_max = 3 136 | #默认字符集:a-z。暂未使用。 137 | conf.blast_mode_az = "abcdefghijklmnopqrstuvwxyz" 138 | #默认字符集:0-9。暂未使用。 139 | conf.blast_mode_num = "0123456789" 140 | #自定义字符集。默认配置"abc"。使用abc构造字典 141 | conf.blast_mode_custom_charset = "abc" 142 | #自定义继续字符集。默认配置空。 143 | conf.blast_mode_resume_charset = "" 144 | #爬虫模式:关闭:0;开启:1 145 | conf.crawl_mode = 0 146 | #用于生成动态敏感文件payload的后缀字典 147 | conf.crawl_mode_dynamic_fuzz_suffix = "crawl_mode_suffix.txt" 148 | #解析robots.txt文件。暂未实现。 149 | conf.crawl_mode_parse_robots = 0 150 | #解析html页面的xpath表达式 151 | conf.crawl_mode_parse_html = "//*/@href | //*/@src | //form/@action" 152 | #是否进行动态爬虫字典生成。默认配置1,开启爬虫动态字典生成。其他配置:e.g:关闭:0;开启:1 153 | conf.crawl_mode_dynamic_fuzz = 1 154 | #Fuzz模式:关闭:0;单字典:1;多字典:2 155 | conf.fuzz_mode = 0 156 | #单字典模式的路径。 157 | conf.fuzz_mode_load_single_dict = "fuzz_mode_dir.txt" 158 | #多字典模式的路径。默认配置:fuzzmult 159 | conf.fuzz_mode_load_mult_dict = "fuzzmult" 160 | #设置fuzz标签。默认配置{dir}。使用{dir}标签当成字典插入点,将http://target.com/{dir}.php替换成http://target.com/字典中的每一行.php。其他配置:e.g:{dir};{ext} 161 | #conf.fuzz_mode_label = "{ext}" 162 | conf.fuzz_mode_label = "{dir}" 163 | 164 | #处理payload配置。暂未实现。 165 | [PayloadHandler] 166 | 167 | #处理请求配置 168 | [RequestHandler] 169 | #自定义请求头。默认配置空。其他配置:e.g:test1=test1,test2=test2 170 | #conf.request_headers = "test1=test1,test2=test2" 171 | conf.request_headers = "" 172 | #自定义请求User-Agent。默认配置chrome的ua。 173 | conf.request_header_ua = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36" 174 | #自定义请求cookie。默认配置空,不设置cookie。其他配置e.g:cookie1=cookie1; cookie2=cookie2; 175 | #conf.request_header_cookie = "cookie1=cookie1; cookie2=cookie2" 176 | conf.request_header_cookie = "" 177 | #自定义401认证。暂未实现。因为自定义请求头功能可满足该需求(懒XD) 178 | conf.request_header_401_auth = "" 179 | #自定义请求方法。默认配置get方法。其他配置:e.g:get;head 180 | #conf.request_method = "head" 181 | conf.request_method = "get" 182 | #自定义每个请求超时时间。默认配置3秒。 183 | conf.request_timeout = 3 184 | #随机延迟(0-x)秒发送请求。参数必须是整数。默认配置0秒,无延迟。 185 | conf.request_delay = 0 186 | #自定义单个目标,请求协程线程数。默认配置30线程 187 | conf.request_limit = 30 188 | #自定义最大重试次数。暂未实现。 189 | conf.request_max_retries = 1 190 | #设置持久连接。是否使用session()。暂未实现。 191 | conf.request_persistent_connect = 0 192 | #302重定向。默认False,不重定向。其他配置:e.g:True;False 193 | conf.redirection_302 = False 194 | #payload后添加后缀。默认空,扫描时,不添加后缀。其他配置:e.g:txt;php;asp;jsp 195 | #conf.file_extension = "txt" 196 | conf.file_extension = "" 197 | 198 | #处理响应配置 199 | [ResponseHandler] 200 | #设置要记录的响应状态。默认配置[200],记录200状态码。其他配置:e.g:[200,403,301] 201 | #conf.response_status_code = [200,403,301] 202 | conf.response_status_code = [200] 203 | #是否记录content-type响应头。默认配置1记录 204 | #conf.response_header_content_type = 0 205 | conf.response_header_content_type = 1 206 | #是否记录页面大小。默认配置1记录 207 | #conf.response_size = 0 208 | conf.response_size = 1 209 | #是否自动检测404页面。默认配置True,开启自动检测404.其他配置参考e.g:True;False 210 | #conf.auto_check_404_page = False 211 | conf.auto_check_404_page = True 212 | #自定义匹配503页面正则。暂未实现。感觉用不着,可能要废弃。 213 | #conf.custom_503_page = "page 503" 214 | conf.custom_503_page = "" 215 | #自定义正则表达式,匹配页面内容 216 | #conf.custom_response_page = "([0-9]){3}([a-z]){3}test" 217 | conf.custom_response_page = "" 218 | #跳过显示页面大小为x的页面,若不设置,请配置成"None",默认配置“None”。其他大小配置参考e.g:None;0b;1k;1m 219 | #conf.skip_size = "0b" 220 | conf.skip_size = "None" 221 | 222 | #代理选项 223 | [ProxyHandler] 224 | #代理配置。默认设置“None”,不开启代理。其他配置e.g:{"http":"http://127.0.0.1:8080","https":"https://127.0.0.1:8080"} 225 | #conf.proxy_server = {"http":"http://127.0.0.1:8080","https":"https://127.0.0.1:8080"} 226 | conf.proxy_server = None 227 | 228 | #Debug选项 229 | [DebugMode] 230 | #打印payloads并退出 231 | conf.debug = 0 232 | 233 | #update选项 234 | [CheckUpdate] 235 | #github获取更新。暂未实现。 236 | conf.update = 0 237 | ``` 238 | 239 | # TODO 240 | 241 | - [x] 命令行参数解析全局初始化 242 | - [x] engine初始化 243 | - [x] 设置线程数 244 | - [x] target初始化 245 | - [x] 自动解析处理输入格式( -i,inputTarget) 246 | - [x] IP 247 | - [x] Domain 248 | - [x] URL 249 | - [x] IP/MASK 250 | - [x] IP Start-End 251 | - [x] 文件读入(-iF,inputLocalFile) 252 | - [ ] bruter初始化 253 | - [ ] 加载配置方式() 254 | - [ ] 读取命令行参数值 255 | - [x] 读取配置文件(-lcf,loadConfigFile) 256 | - [x] 递归模式选项(RecursiveScan) 257 | - [x] 递归扫描(-rs,recursive_scan) 258 | - [x] 需要递归的状态码(-rd,recursive_status_code) 259 | - [x] 排除某些目录(-es,exclude_subdirs) 260 | - [ ] 扫描模式选项(ScanModeHandler) 261 | - [x] 字典模式(-dm,dict_mode) 262 | - [x] 加载单个字典(-dmlsd,dict_mode_load_single_dict) 263 | - [x] 加载多个字典(-dmlmd,dict_mode_load_mult_dict) 264 | - [ ] 爆破模式(-bm,blast_mode) 265 | - [x] 爆破目录长度范围(必选) 266 | - [x] 最小长度(-bmmin,blast_mode_min) 267 | - [x] 最大长度(-bmmax,blast_mode_max) 268 | - [ ] 基于默认字符集 269 | - [ ] 基于a-z 270 | - [ ] 基于0-9 271 | - [x] 基于自定义字符集(-bmcc,blast_mode_custom_charset) 272 | - [x] 断点续生成payload(-bmrc,blast_mode_resume_charset) 273 | - [ ] 爬虫模式(-cm,crawl_mode) 274 | - [x] 自定义解析标签(-cmph,crawl_mode_parse_html)(a:href,img:src,form:action,script:src,iframe:src,div:src,frame:src,embed:src) 275 | - [ ] 解析robots.txt(-cmpr,crawl_mode_parse_robots) 276 | - [x] 爬虫类动态fuzz扫描(-cmdf,crawl_mode_dynamic_fuzz) 277 | - [x] fuzz模式(-fm,fuzz_mode) 278 | - [x] fuzz单个字典(-fmlsd,fuzz_mode_load_single_dict) 279 | - [x] fuzz多个字典(-fmlmd,fuzz_mode_load_mult_dict) 280 | - [x] fuzz标签(-fml,fuzz_mode_label) 281 | - [ ] 请求优化选项(RequestHandler) 282 | - [x] 自定义请求超时(-rt,request_timeout) 283 | - [x] 自定义请求延时(-rd,request_delay) 284 | - [x] 限制单个目标主机协程数扫描(-rl,request_limit) 285 | - [ ] 限制重试次数(-rmr,request_max_retries) 286 | - [ ] http持久连接(-rpc,request_persistent_connect) 287 | - [x] 自定义请求方法(-rm,request_method)(get、head) 288 | - [x] 302状态处理(-r3,redirection_302)(是否重定向) 289 | - [x] 自定义header 290 | - [x] 自定义其他header(-rh,request_headers)(解决需要401认证) 291 | - [x] 自定义ua(-rhua,request_header_ua) 292 | - [x] 自定义cookie(-rhc,request_header_cookie) 293 | - [ ] 字典处理选项(PayloadHandler) 294 | - [ ] 字典处理(payload修改-去斜杠) 295 | - [ ] 字典处理(payload修改-首字符加斜杠) 296 | - [ ] 字典处理(payload修改-单词首字母大写) 297 | - [ ] 字典处理(payload修改-去扩展) 298 | - [ ] 字典处理(payload修改-去除非字母数字) 299 | - [ ] 响应结果处理模块(ResponseHandler) 300 | - [x] 跳过大小为x字节的文件(-ss,skip_size) 301 | - [x] 自动检测404页面(-ac4p,auto_check_404_page) 302 | - [ ] 自定义503页面(-c5p,custom_503_page) 303 | - [ ] 自定义正则匹配响应内容并进行某种操作 304 | - [x] 自定义正则匹配响应(-crp,custom_response_page) 305 | - [ ] 某种操作(暂时未定义) 306 | - [x] 输出结果为自定义状态码(-rsc,response_status_code) 307 | - [x] 输出payload为完整路径(默认输出完成url) 308 | - [x] 输出结果展示content-type 309 | - [x] 自动去重复保存结果 310 | - [ ] 状态处理模块(StatusHandler) 311 | - [ ] 状态显示(等待开始、进行中、暂停中、异常、完成) 312 | - [x] 进度显示 313 | - [ ] 状态控制(开始、暂停、继续、停止) 314 | - [ ] 续扫模块(暂未配置) 315 | - [ ] 断点续扫 316 | - [ ] 选行续扫 317 | - [ ] 日志记录模块(ScanLogHandler) 318 | - [ ] 扫描日志 319 | - [ ] 错误日志 320 | - [ ] 代理模块(ProxyHandler) 321 | - [x] 单个代理(-ps,proxy_server) 322 | - [ ] 代理池 323 | - [x] 调试模式选项(DebugMode) 324 | - [x] debug(--debug) 325 | - [ ] 检查更新选项(CheckUpdate) 326 | - [ ] update(--update) 327 | 328 | # 默认字典文件 329 | 330 | 字典文件存放在项目根目录中的`data`文件夹中 331 | 332 | 1. dict_mode_dict.txt “字典模式”字典,使用dirsearch默认字典 333 | 2. crawl_mode_suffix.txt “爬虫模式”字典,使用FileSensor默认字典 334 | 3. fuzz_mode_dir.txt “fuzz模式”字典,使用DirBuster默认字典 335 | 4. fuzz_mode_ext.txt “fuzz模式”字典,使用常见后缀制作的字典 336 | 5. dictmult 该目录为“字典模式”默认多字典文件夹,包含:BAK.min.txt(备份文件小字典),BAK.txt(备份文件大字典),LEAKS.txt(信息泄露文件字典) 337 | 6. fuzzmult 该目录为“fuzz模式”默认多字典文件夹,包含:fuzz_mode_dir.txt(默认目录字典),fuzz_mode_ext.txt(默认后缀字典) 338 | 339 | # 已知缺陷 340 | 341 | 1. “爬虫模式”只爬取了目标的当前页面,用于生成动态字典。项目将来会将“爬虫模块”与“生成动态字典功能”分离。 342 | 2. 关于bruter.py第517行`bar.log.start()`出错。解决方案:请安装progressbar2。卸载progressbar。防止导入同名模块。感谢某位表哥提醒。 343 | 344 | ```shell 345 | 执行命令: 346 | python3 -m pip uninstall progressbar 347 | python3 -m pip install progressbar2 348 | ``` 349 | 350 | # 维护工作 351 | 352 | 1. 若使用过程中出现问题,欢迎发issue 353 | 2. 本项目正在维护,未来将会有新的功能加入,具体参照“TODO”列表,未打勾项 354 | 355 | # 致谢声明 356 | 357 | dirmap在编写过程中,借鉴了大量的优秀开源项目的模式与思想,特此说明并表示感谢。 358 | 359 | - [Sqlmap](https://github.com/sqlmapproject/sqlmap) 360 | - [POC-T](https://github.com/Xyntax/POC-T) 361 | - [Saucerframe](https://github.com/saucer-man/saucerframe) 362 | - [gwhatweb](https://github.com/boy-hack/gwhatweb) 363 | - [dirsearch](https://github.com/maurosoria/dirsearch) 364 | - [cansina](https://github.com/deibit/cansina) 365 | - [weakfilescan](https://github.com/ring04h/weakfilescan) 366 | - [FileSensor](https://github.com/Xyntax/FileSensor) 367 | - [BBscan](https://github.com/lijiejie/BBScan) 368 | - [werdy](https://github.com/derv82/werdy) 369 | 370 | # 联系作者 371 | 372 | mail: xxlin.ujs@qq.com 373 | 374 | ![donate](doc/donate.jpg) -------------------------------------------------------------------------------- /README_EN.md: -------------------------------------------------------------------------------- 1 | 7 | # Dirmap 8 | 9 | [中文](./README.md) 10 | 11 | An advanced web directory scanning tool that will be more powerful than DirBuster, Dirsearch, cansina, and Yu Jian 12 | 13 | ![dirmap](doc/dirmap.png) 14 | 15 | # Demand analysis 16 | 17 | After a lot of research, summarizing an excellent web directory scanning tool has at least the following features: 18 | 19 | - Concurrency engine 20 | - Can use the dictionary 21 | - Can be purely blasted 22 | - Can crawl the page dynamically to generate a dictionary 23 | - Can fuzz scan 24 | - Custom request 25 | - Custom response processing... 26 | 27 | Then take a look at the **features** of Dirmap. 28 | 29 | # Features 30 | 31 | 1. Support n target\*n payload concurrent 32 | 2. Support recursive scanning 33 | 3. Support custom status codes that require recursive scanning 34 | 4. Support (single | multi) dictionary scan 35 | 5. Support custom character set blasting 36 | 6. Support crawler dynamic dictionary scanning 37 | 7. Support custom label fuzz target url 38 | 8. Custom Request User-Agent 39 | 9. Custom request random delay 40 | 10. Custom request timeout 41 | 11. Custom Request Broker 42 | 12. Custom Regular Expressions Match False 404 Pages 43 | 13. Customize the response status code to process 44 | 14. Customize skipping pages of size x 45 | 15. Custom display content-type 46 | 16. Customize the display page size 47 | 17. Save the results by domain name and remove duplicates 48 | 49 | # Introduction 50 | 51 | ## Environment and download 52 | 53 | ```shell 54 | git clone https://github.com/H4ckForJob/dirmap.git && cd dirmap && python3 -m pip install -r requirement.txt 55 | ``` 56 | 57 | ## Quick use 58 | 59 | ### Input target 60 | 61 | Single target, default is http 62 | 63 | ```shell 64 | python3 dirmap.py -i https://target.com -lcf 65 | ``` 66 | 67 | ```shell 68 | python3 dirmap.py -i 192.168.1.1 -lcf 69 | ``` 70 | 71 | Subnet(CIDR format) 72 | 73 | ```shell 74 | python3 dirmap.py -i 192.168.1.0/24 -lcf 75 | ``` 76 | 77 | Network range 78 | 79 | ```shell 80 | python3 dirmap.py -i 192.168.1.1-192.168.1.100 -lcf 81 | ``` 82 | 83 | ### Read from file 84 | 85 | ```shell 86 | python3 dirmap.py -iF targets.txt -lcf 87 | ``` 88 | 89 | The above format is supported in `targets.txt` 90 | 91 | ### Result save 92 | 93 | 1. The result will be automatically saved in the `output` folder in the project root directory. 94 | 2. Each target generates a txt with the naming format `domain/ip.txt` 95 | 3. The result is automatically deduplicated 96 | 97 | ## Advanced 98 | 99 | Customize the dirmap configuration and start exploring dirmap advanced features 100 | 101 | Temporarily configure the configuration file by loading the configuration file. ** It is not supported to use the command line parameters for detailed configuration**! 102 | 103 | Edit `dirmap.conf` in the root directory of the project to configure it.Detailed instructions for use in `dirmap.conf` 104 | 105 | # TODO 106 | 107 | - [x] command line parameter parsing global initialization 108 | - [x] Cngine Initialization 109 | - [x] set the number of threads 110 | - [x] Target Initialization 111 | - [x] automatic parsing of input formats( -i,inputTarget) 112 | - [x] IP 113 | - [x] Domain 114 | - [x] URL 115 | - [x] IP/MASK 116 | - [x] IP Start-End 117 | - [x] file reading(-iF,inputLocalFile) 118 | - [ ] Bruter Initialization 119 | - [ ] Load Configuration Mode() 120 | - [ ] read command line parameter values 121 | - [x] read configuration file(-lcf,loadConfigFile) 122 | - [x] Recursive Mode Option(RecursiveScan) 123 | - [x] recursive scan (-rs, recursive_scan) 124 | - [x] status code requiring recursion (-rd, recursive_status_code) 125 | - [x] exclude certain directories (-es, exclude_subdirs) 126 | - [ ] Scan Mode Option (ScanModeHandler) 127 | - [x] dictionary mode (-dm, dict_mode) 128 | - [x] load a single dictionary (-dmlsd, dict_mode_load_single_dict) 129 | - [x] load multiple dictionaries (-dmlmd, dict_mode_load_mult_dict) 130 | - [ ] blast mode (-bm, blast_mode) 131 | - [x] blasting directory length range (required) 132 | - [x] minimum length (-bmmin, blast_mode_min) 133 | - [x] maximum length (-bmmax, blast_mode_max) 134 | - [ ] based on the default character set 135 | - [ ] based on a-z 136 | - [ ] based on 0-9 137 | - [x] based on custom character set (-bmcc, blast_mode_custom_charset) 138 | - [x] breakpoint resume generating payload(-bmrc, blast_mode_resume_charset) 139 | - [ ] crawler mode (-cm, crawl_mode) 140 | - [x] custom parsing tags (-cmph, crawl_mode_parse_html) (a:href, img:src, form:action,script:src,iframe:src,div:src,frame:src,embed:src) 141 | - [ ] parsing robots.txt (-cmpr, crawl_mode_parse_robots) 142 | - [x] crawler dynamic fuzz scan (-cmdf, crawl_mode_dynamic_fuzz) 143 | - [x] fuzz mode (-fm, fuzz_mode) 144 | - [x] fuzz single dictionary (-fmlsd, fuzz_mode_load_single_dict) 145 | - [x] fuzz multiple dictionaries (-fmlmd, fuzz_mode_load_mult_dict) 146 | - [x] fuzz tag (-fml, fuzz_mode_label) 147 | - [ ] Request Optimization Option (RequestHandler) 148 | - [x] custom request timeout (-rt, request_timeout) 149 | - [x] custom request delay (-rd, request_delay) 150 | - [x] limit single target host coroutine scan (-rl, request_limit) 151 | - [ ] limit the number of retries (-rmr, request_max_retries) 152 | - [ ] http persistent connection (-rpc, request_persistent_connect) 153 | - [x] custom request method (-rm, request_method) (get, head) 154 | - [x] 302 state processing (-r3, redirection_302) (redirected) 155 | - [x] custom header 156 | - [x] customize other headers (-rh, request_headers) (resolve 401 authentication required) 157 | - [x] custom ua(-rhua,request_header_ua) 158 | - [x] custom cookie (-rhc, request_header_cookie) 159 | - [ ] Dictionary Processing Option (PayloadHandler) 160 | - [ ] dictionary processing (payload modification - de-slash) 161 | - [ ] dictionary processing (payload modification - first character plus slash) 162 | - [ ] dictionary processing (payload modification - initial capitalization of words) 163 | - [ ] dictionary processing (payload modification - de-extension) 164 | - [ ] dictionary processing (payload modification - remove non-alphanumeric) 165 | - [ ] Response Result Processing Module (ResponseHandler) 166 | - [x] skips files of size x bytes (-ss, skip_size) 167 | - [x] automatically detect 404 pages (-ac4p, auto_check_404_page) 168 | - [ ] custom 503 page (-c5p, custom_503_page) 169 | - [ ] customize regular matching response content and perform some action 170 | - [x] custom regular match response (-crp, custom_response_page) 171 | - [ ] some operation (temporarily undefined) 172 | - [x] output is a custom status code (-rsc, response_status_code) 173 | - [x] output payload to full path (default output completion url) 174 | - [x] output results show content-type 175 | - [x] automatically repeats the results 176 | - [ ] Status Processing Module (StatusHandler) 177 | - [ ] status display (waiting for start, ongoing, paused, abnormal, completed) 178 | - [x] progress display 179 | - [ ] status control (start, pause, resume, stop) 180 | - [ ] continued scanning module (not yet configured) 181 | - [ ] breakpoint continuous sweep 182 | - [ ] line selection continues 183 | - [ ] Logging Module (ScanLogHandler) 184 | - [ ] scan log 185 | - [ ] error log 186 | - [ ] Proxy Module (ProxyHandler) 187 | - [x] single agent (-ps, proxy_server) 188 | - [ ] proxy pool 189 | - [x] Debug Mode Option (DebugMode) 190 | - [x] debug(--debug) 191 | - [ ] Check For Update Options (CheckUpdate) 192 | - [ ] update(--update) 193 | 194 | # Default dictionary 195 | 196 | The dictionary file is stored in the `data` folder in the project root directory. 197 | 198 | 1. dict_mode_dict.txt "dictionary mode" dictionary, using `dirsearch` default dictionary 199 | 2. crawl_mode_suffix.txt "crawler mode" dictionary, using the `FileSensor` default dictionary 200 | 3. fuzz_mode_dir.txt "fuzz mode" dictionary, using the `DirBuster` default dictionary 201 | 4. fuzz_mode_ext.txt "fuzz mode" dictionary, a dictionary made with common suffixes 202 | 5. dictmult This directory is the "dictionary mode" default multi-dictionary folder, including: BAK.min.txt (backup file small dictionary), BAK.txt (backup file large dictionary), LEAKS.txt (information leak file dictionary) 203 | 6. fuzzmult This directory is the default multi-dictionary folder of "fuzz mode", including: fuzz_mode_dir.txt (default directory dictionary), fuzz_mode_ext.txt (default suffix dictionary) 204 | 205 | # Known bug 206 | 207 | 1. "crawler mode" only crawls the current page of the target and is used to generate a dynamic dictionary. The project will separate the "crawler module" from the "generate dynamic dictionary function" in the future. 208 | 2. About bruter.py line 517 `bar.log.start()` error. Solution: Please install progressbar2. Uninstall the progressbar. Prevent the import of modules of the same name. Thanks to a brother for reminding. 209 | 210 | ```shell 211 | python3 -m pip uninstall progressbar 212 | python3 -m pip install progressbar2 213 | ``` 214 | 215 | # Maintenance 216 | 217 | 1. If there is a problem during use, please feel free to issue an issue. 218 | 2. The project is under maintenance and new features will be added in the future. Please refer to the “TODO” list for details. 219 | 220 | # Acknowledgement 221 | 222 | In the process of writing dirmap, I borrowed a lot of models and ideas from excellent open source projects. I would like to express my gratitude. 223 | 224 | - [Sqlmap](https://github.com/sqlmapproject/sqlmap) 225 | - [POC-T](https://github.com/Xyntax/POC-T) 226 | - [Saucerframe](https://github.com/saucer-man/saucerframe) 227 | - [gwhatweb](https://github.com/boy-hack/gwhatweb) 228 | - [dirsearch](https://github.com/maurosoria/dirsearch) 229 | - [cansina](https://github.com/deibit/cansina) 230 | - [weakfilescan](https://github.com/ring04h/weakfilescan) 231 | - [FileSensor](https://github.com/Xyntax/FileSensor) 232 | - [BBscan](https://github.com/lijiejie/BBScan) 233 | - [werdy](https://github.com/derv82/werdy) 234 | 235 | # Contact 236 | 237 | mail: xxlin.ujs@qq.com 238 | 239 | ![donate](doc/donate.jpg) -------------------------------------------------------------------------------- /data/crawl_mode_suffix.txt: -------------------------------------------------------------------------------- 1 | {FULL}~ 2 | {FULL}- 3 | {FULL}_ 4 | {NAME}~.{EXT} 5 | {NAME}-.{EXT} 6 | {NAME}_.{EXT} 7 | {FULL}0 8 | {FULL}1 9 | {FULL}2 10 | {FULL}3 11 | {NAME}0.{EXT} 12 | {NAME}1.{EXT} 13 | {NAME}2.{EXT} 14 | {NAME}3.{EXT} 15 | {FULL}_0 16 | {FULL}_1 17 | {FULL}_2 18 | {FULL}_3 19 | {NAME}(1).{EXT} 20 | {NAME}(2).{EXT} 21 | {NAME}(3).{EXT} 22 | {FULL}__ 23 | {FULL}_bak 24 | {FULL}.bak 25 | {FULL}.bak~ 26 | {NAME}.bak.{EXT} 27 | {NAME}_bak.{EXT} 28 | {FULL}.source 29 | {FULL}_source 30 | {NAME}.source.{EXT} 31 | {NAME}_source.{EXT} 32 | {FULL}.zip 33 | {FULL}.rar 34 | {FULL}.tar.gz 35 | {FULL}.tar.xz 36 | {FULL}.7z 37 | {FULL}_old 38 | {FULL}.old 39 | {NAME}_old.{EXT} 40 | {NAME}.old.{EXT} 41 | {FULL}_new 42 | {FULL}.new 43 | {NAME}_new.{EXT} 44 | {NAME}.new.{EXT} 45 | {FULL}.swo 46 | {FULL}.swp 47 | {FULL}.save 48 | {FULL}_save 49 | {NAME}_save.{EXT} 50 | {NAME}.save.{EXT} 51 | .{FULL}.swp 52 | .{FULL}.un~ -------------------------------------------------------------------------------- /data/dictmult/BAK.min.txt: -------------------------------------------------------------------------------- 1 | 0.rar 2 | 0.tar 3 | 0.tar.gz 4 | 0.zip 5 | admin.rar 6 | admin.tar 7 | admin.tar.gz 8 | admin.zip 9 | backup.rar 10 | backups.rar 11 | backups.tar 12 | backups.tar.gz 13 | backups.zip 14 | backup.tar 15 | backup.tar.gz 16 | backup.zip 17 | bak.rar 18 | bak.tar 19 | bak.tar.gz 20 | bak.zip 21 | data.rar 22 | data.tar 23 | data.tar.gz 24 | data.zip 25 | flashfxp.rar 26 | flashfxp.tar 27 | flashfxp.tar.gz 28 | flashfxp.zip 29 | ftp.rar 30 | ftp.tar 31 | ftp.tar.gz 32 | ftp.zip 33 | git.rar 34 | git.tar 35 | git.tar.gz 36 | git.zip 37 | htdocs.rar 38 | htdocs.tar 39 | htdocs.tar.gz 40 | htdocs.zip 41 | html.rar 42 | html.tar 43 | html.tar.gz 44 | html.zip 45 | svn.rar 46 | svn.tar 47 | svn.tar.gz 48 | svn.zip 49 | web.rar 50 | web.tar 51 | web.tar.gz 52 | web.zip 53 | www.rar 54 | wwwroot.rar 55 | wwwroot.tar 56 | wwwroot.tar.gz 57 | wwwroot.zip 58 | www.tar 59 | www.tar.gz 60 | www.zip 61 | 1.rar 62 | 1.tar 63 | 1.tar.gz 64 | 1.zip 65 | 2.rar 66 | 2.tar 67 | 2.tar.gz 68 | 2.zip 69 | 3.rar 70 | 3.tar 71 | 3.tar.gz 72 | 3.zip 73 | 4.rar 74 | 4.tar 75 | 4.tar.gz 76 | 4.zip 77 | 5.rar 78 | 5.tar 79 | 5.tar.gz 80 | 5.zip 81 | 6.rar 82 | 6.tar 83 | 6.tar.gz 84 | 6.zip 85 | 7.rar 86 | 7.tar 87 | 7.tar.gz 88 | 7.zip 89 | 8.rar 90 | 8.tar 91 | 8.tar.gz 92 | 8.zip 93 | 9.rar 94 | 9.tar 95 | 9.tar.gz 96 | 9.zip 97 | -------------------------------------------------------------------------------- /data/dictmult/LEAKS.txt: -------------------------------------------------------------------------------- 1 | CVS/Entries 2 | CVS/Root 3 | druid 4 | druid/ 5 | druid/index.html 6 | .ds_store 7 | .ds_Store 8 | .DS_store 9 | .DS_Store 10 | .git 11 | .git/config 12 | .hg 13 | .idea 14 | .index.asp.swp 15 | .index.jsp.swp 16 | .index.php.swp 17 | nginx.conf 18 | /secure/ManageFilters.jspa?filterView=popular 19 | .svn 20 | WEB-INF/database.properties 21 | WEB-INF/web.xml 22 | WS_FTP.LOG 23 | .bash_history 24 | .env 25 | Thumbs.db 26 | .gitignore 27 | .vs 28 | .vscode 29 | .vscode/settings.json 30 | .github -------------------------------------------------------------------------------- /data/fuzz_mode_ext.txt: -------------------------------------------------------------------------------- 1 | php 2 | asp 3 | aspx 4 | html 5 | txt 6 | doc 7 | jsp 8 | py 9 | zip 10 | rar 11 | 7z 12 | bak 13 | -------------------------------------------------------------------------------- /data/fuzzmult/fuzz_mode_ext.txt: -------------------------------------------------------------------------------- 1 | php 2 | asp 3 | aspx 4 | html 5 | txt 6 | doc 7 | jsp 8 | py 9 | zip 10 | rar 11 | 7z 12 | bak 13 | -------------------------------------------------------------------------------- /dirmap.conf: -------------------------------------------------------------------------------- 1 | #Recursive scan options 2 | [RecursiveScan] 3 | #recursive scan:Close:0;Open:1 4 | conf.recursive_scan = 0 5 | #Recursive scanning if these status codes 6 | conf.recursive_status_code = [301,403] 7 | #Exit the scan when the URL exceeds this length 8 | conf.recursive_scan_max_url_length = 60 9 | #These suffix names are not recursive 10 | conf.recursive_blacklist_exts = ["html",'htm','shtml','png','jpg','webp','bmp','js','css','pdf','ini','mp3','mp4'] 11 | #The directory does not scan 12 | #conf.exclude_subdirs = ['/test1','/test2'] 13 | conf.exclude_subdirs = "" 14 | 15 | #Processing scan mode 16 | [ScanModeHandler] 17 | #Dict mode:Close :0;single dict:1;multiple dict:2 18 | conf.dict_mode = 1 19 | #Single dictionary file path 20 | conf.dict_mode_load_single_dict = "dict_mode_dict.txt" 21 | #Multiple dictionary file path 22 | conf.dict_mode_load_mult_dict = "dictmult" 23 | #Blast mode:tips:Use "conf.file_extension" options for suffixes 24 | conf.blast_mode = 0 25 | #Minimum length of character set 26 | conf.blast_mode_min = 3 27 | #Maximum length of character set 28 | conf.blast_mode_max = 3 29 | #The default character set:a-z 30 | conf.blast_mode_az = "abcdefghijklmnopqrstuvwxyz" 31 | #The default character set:0-9 32 | conf.blast_mode_num = "0123456789" 33 | #Custom character set 34 | conf.blast_mode_custom_charset = "abc" 35 | #Custom continue to generate blast dictionary location 36 | conf.blast_mode_resume_charset = "" 37 | #Crawl mode:Close :0;Open:1 38 | conf.crawl_mode = 1 39 | #Crawl mode dynamic fuzz suffix dict 40 | conf.crawl_mode_dynamic_fuzz_suffix = "crawl_mode_suffix.txt" 41 | #Parse robots.txt file 42 | conf.crawl_mode_parse_robots = 0 43 | #An xpath expression used by a crawler to parse an HTML document 44 | conf.crawl_mode_parse_html = "//*/@href | //*/@src | //form/@action" 45 | #Whether to turn on the dynamically generated payloads:close:0;open:1 46 | conf.crawl_mode_dynamic_fuzz = 1 47 | #Fuzz mode:Close :0;single dict:1;multiple dict:2 48 | conf.fuzz_mode = 0 49 | #Single dictionary file path.You can customize the dictionary path. The labels are just a flag for insert dict. 50 | conf.fuzz_mode_load_single_dict = "fuzz_mode_dir.txt" 51 | #Multiple dictionary file path 52 | conf.fuzz_mode_load_mult_dict = "fuzzmult" 53 | #Set the label of fuzz.e.g:{dir};{ext} 54 | #conf.fuzz_mode_label = "{dir}" 55 | conf.fuzz_mode_label = "{dir}" 56 | 57 | #Processing payloads 58 | [PayloadHandler] 59 | 60 | #Processing requests 61 | [RequestHandler] 62 | #Custom request header.e.g:test1=test1,test2=test2 63 | conf.request_headers = "" 64 | #Custom request user-agent 65 | conf.request_header_ua = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36" 66 | #Custom request cookie.e.g:cookie1=cookie1; cookie2=cookie2; 67 | conf.request_header_cookie = "" 68 | #Custom 401 certification 69 | conf.request_header_401_auth = "" 70 | #Custom request methods (get, head) 71 | conf.request_method = "get" 72 | #Custom per request timeout in x sec. 73 | conf.request_timeout = 3 74 | #Custom per request delay random(0-x) secends.The parameter must be an integer. 75 | conf.request_delay = 0 76 | #Custom all request limit,default 30 coroutines 77 | conf.request_limit = 30 78 | #Custom request max retries 79 | conf.request_max_retries = 1 80 | #Whether to open an HTTP persistent connection 81 | conf.request_persistent_connect = 0 82 | #Whether to follow 302 redirection 83 | conf.redirection_302 = False 84 | #Payload add file extension 85 | conf.file_extension = "" 86 | 87 | #Processing responses 88 | [ResponseHandler] 89 | #Sets the response status code to record 90 | conf.response_status_code = [200] 91 | #Whether to record content-type 92 | conf.response_header_content_type = 1 93 | #Whether to record page size 94 | conf.response_size = 1 95 | #Auto check 404 page 96 | conf.auto_check_404_page = True 97 | #Custom 503 page regex 98 | conf.custom_503_page = "page 503" 99 | #Custom regular match response content 100 | # conf.custom_response_page = "([0-9]){3}([a-z]){3}test" 101 | conf.custom_response_page = "" 102 | #Skip files of size x bytes.you must be set "None",if don't want to skip any file.e.g:None;0b;1k;1m 103 | conf.skip_size = "None" 104 | 105 | #Processing proxy 106 | [ProxyHandler] 107 | #proxy:e.g:{"http":"http://127.0.0.1:8080","https":"https://127.0.0.1:8080"} 108 | #conf.proxy_server = {"http":"http://127.0.0.1:8080","https":"https://127.0.0.1:8080"} 109 | conf.proxy_server = None 110 | 111 | #Debug option 112 | [DebugMode] 113 | #Print payloads and exit the program 114 | conf.debug = 0 115 | 116 | #update option 117 | [CheckUpdate] 118 | #Get the latest code from github(Not yet available) 119 | conf.update = 0 -------------------------------------------------------------------------------- /dirmap.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | 4 | ''' 5 | @Author: xxlin 6 | @LastEditors: xxlin 7 | @Date: 2019-04-10 13:27:59 8 | @LastEditTime: 2023-07-25 15:56:04 9 | ''' 10 | 11 | import os 12 | import sys 13 | 14 | from gevent import monkey 15 | monkey.patch_all() 16 | from lib.controller.engine import run 17 | from lib.core.common import banner, outputscreen, setPaths 18 | from lib.core.data import cmdLineOptions, conf, paths 19 | from lib.core.option import initOptions 20 | from lib.parse.cmdline import cmdLineParser 21 | 22 | 23 | 24 | 25 | def main(): 26 | """ 27 | main fuction of dirmap 28 | """ 29 | # Make sure the version of python you are using is high enough 30 | if sys.version_info < (3, 8): 31 | outputscreen.error("Sorry, dirmap requires Python 3.8 or higher\n") 32 | sys.exit(1) 33 | # anyway output thr banner information 34 | banner() 35 | 36 | # set paths of project 37 | paths.ROOT_PATH = os.getcwd() 38 | setPaths() 39 | 40 | # received command >> cmdLineOptions 41 | cmdLineOptions.update(cmdLineParser().__dict__) 42 | 43 | # loader script,target,working way(threads? gevent?),output_file from cmdLineOptions 44 | # and send it to conf 45 | initOptions(cmdLineOptions) 46 | 47 | # run! 48 | try: 49 | run() 50 | except KeyboardInterrupt: 51 | outputscreen.success("[+] exit") 52 | sys.exit(1) 53 | 54 | if __name__ == "__main__": 55 | main() 56 | -------------------------------------------------------------------------------- /doc/blast_mode.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/H4ckForJob/dirmap/020eec1ed538d65f9e0b97f3fc386c054a7a350e/doc/blast_mode.png -------------------------------------------------------------------------------- /doc/crawl_mode.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/H4ckForJob/dirmap/020eec1ed538d65f9e0b97f3fc386c054a7a350e/doc/crawl_mode.png -------------------------------------------------------------------------------- /doc/dict_mode.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/H4ckForJob/dirmap/020eec1ed538d65f9e0b97f3fc386c054a7a350e/doc/dict_mode.png -------------------------------------------------------------------------------- /doc/dirmap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/H4ckForJob/dirmap/020eec1ed538d65f9e0b97f3fc386c054a7a350e/doc/dirmap.png -------------------------------------------------------------------------------- /doc/donate.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/H4ckForJob/dirmap/020eec1ed538d65f9e0b97f3fc386c054a7a350e/doc/donate.jpg -------------------------------------------------------------------------------- /doc/fuzz_mode.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/H4ckForJob/dirmap/020eec1ed538d65f9e0b97f3fc386c054a7a350e/doc/fuzz_mode.png -------------------------------------------------------------------------------- /doc/recursive_scan.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/H4ckForJob/dirmap/020eec1ed538d65f9e0b97f3fc386c054a7a350e/doc/recursive_scan.png -------------------------------------------------------------------------------- /lib/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | 4 | ''' 5 | @Author: xxlin 6 | @LastEditors: xxlin 7 | @Date: 2019-04-11 16:11:27 8 | @LastEditTime: 2019-04-11 20:04:09 9 | ''' 10 | -------------------------------------------------------------------------------- /lib/controller/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | 4 | ''' 5 | @Author: xxlin 6 | @LastEditors: xxlin 7 | @Date: 2019-04-11 20:11:08 8 | @LastEditTime: 2019-04-11 20:11:12 9 | ''' 10 | -------------------------------------------------------------------------------- /lib/controller/bruter.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | 4 | ''' 5 | @Author: xxlin 6 | @LastEditors: xxlin 7 | @Date: 2019-03-14 09:49:05 8 | @LastEditTime: 2023-07-25 16:32:33 9 | ''' 10 | 11 | import configparser 12 | import hashlib 13 | import os 14 | import random 15 | import re 16 | import sys 17 | import time 18 | import urllib 19 | 20 | import gevent 21 | import progressbar 22 | import requests 23 | from gevent.queue import Queue 24 | from lxml import etree 25 | 26 | from lib.core.common import intToSize, outputscreen, urlSimilarCheck 27 | from lib.core.data import bar, conf, paths, payloads, tasks, th 28 | from lib.utils.config import ConfigFileParser 29 | from lib.plugins.inspector import Inspector 30 | 31 | #防止ssl未校验时出现提示信息 32 | requests.packages.urllib3.disable_warnings() 33 | 34 | #dict_mode的payloads 35 | payloads.dict_mode_dict = set() 36 | #crawl_mode的payloads 37 | payloads.crawl_mode_dynamic_fuzz_temp_dict = set() 38 | payloads.similar_urls_set = set() 39 | payloads.crawl_mode_dynamic_fuzz_dict = list() 40 | #blast_mode的payload 41 | payloads.blast_mode_custom_charset_dict = list() 42 | #fuzz_mode的payload 43 | payloads.fuzz_mode_dict = list() 44 | 45 | #创建all_tasks队列 46 | tasks.all_task = Queue() 47 | tasks.task_length = 0 48 | tasks.task_count = 0 49 | 50 | #创建crawl_tasks队列 51 | tasks.crawl_task = Queue() 52 | 53 | #假性404页面md5列表 54 | conf.autodiscriminator_md5 = set() 55 | 56 | bar.log = progressbar.ProgressBar() 57 | 58 | def saveResults(domain,msg): 59 | ''' 60 | @description: 结果保存,以"域名.txt"命名,url去重复 61 | @param {domain:域名,msg:保存的信息} 62 | @return: null 63 | ''' 64 | filename = domain +'.txt' 65 | conf.output_path = os.path.join(paths.OUTPUT_PATH, filename) 66 | #判断文件是否存在,若不存在则创建该文件 67 | if not os.path.exists(conf.output_path): 68 | with open(conf.output_path,'w+') as temp: 69 | pass 70 | with open(conf.output_path,'r+') as result_file: 71 | old = result_file.read() 72 | if msg+'\n' in old: 73 | pass 74 | else: 75 | result_file.write(msg+'\n') 76 | 77 | def loadConf(): 78 | ''' 79 | @description: 加载扫描配置(以后将使用参数,而非从文件加载) 80 | @param {type} 81 | @return: 82 | ''' 83 | 84 | conf.recursive_scan = eval(ConfigFileParser().recursive_scan()) 85 | conf.recursive_scan_max_url_length = eval(ConfigFileParser().recursive_scan_max_url_length()) 86 | conf.recursive_status_code = eval(ConfigFileParser().recursive_status_code()) 87 | conf.recursive_blacklist_exts = eval(ConfigFileParser().recursive_blacklist_exts()) 88 | conf.exclude_subdirs = eval(ConfigFileParser().exclude_subdirs()) 89 | 90 | conf.dict_mode = eval(ConfigFileParser().dict_mode()) 91 | conf.dict_mode_load_single_dict = os.path.join(paths.DATA_PATH,eval(ConfigFileParser().dict_mode_load_single_dict())) 92 | conf.dict_mode_load_mult_dict = os.path.join(paths.DATA_PATH,eval(ConfigFileParser().dict_mode_load_mult_dict())) 93 | conf.blast_mode = eval(ConfigFileParser().blast_mode()) 94 | conf.blast_mode_min = eval(ConfigFileParser().blast_mode_min()) 95 | conf.blast_mode_max = eval(ConfigFileParser().blast_mode_max()) 96 | conf.blast_mode_az = eval(ConfigFileParser().blast_mode_az()) 97 | conf.blast_mode_num = eval(ConfigFileParser().blast_mode_num()) 98 | conf.blast_mode_custom_charset = eval(ConfigFileParser().blast_mode_custom_charset()) 99 | conf.blast_mode_resume_charset = eval(ConfigFileParser().blast_mode_resume_charset()) 100 | conf.crawl_mode = eval(ConfigFileParser().crawl_mode()) 101 | conf.crawl_mode_dynamic_fuzz_suffix = eval(ConfigFileParser().crawl_mode_dynamic_fuzz_suffix()) 102 | conf.crawl_mode_parse_robots = eval(ConfigFileParser().crawl_mode_parse_robots()) 103 | conf.crawl_mode_parse_html = eval(ConfigFileParser().crawl_mode_parse_html()) 104 | conf.crawl_mode_dynamic_fuzz = eval(ConfigFileParser().crawl_mode_dynamic_fuzz()) 105 | conf.fuzz_mode = eval(ConfigFileParser().fuzz_mode()) 106 | conf.fuzz_mode_load_single_dict = os.path.join(paths.DATA_PATH,eval(ConfigFileParser().fuzz_mode_load_single_dict())) 107 | conf.fuzz_mode_load_mult_dict = os.path.join(paths.DATA_PATH,eval(ConfigFileParser().fuzz_mode_load_mult_dict())) 108 | conf.fuzz_mode_label = eval(ConfigFileParser().fuzz_mode_label()) 109 | 110 | conf.request_headers = eval(ConfigFileParser().request_headers()) 111 | conf.request_header_ua = eval(ConfigFileParser().request_header_ua()) 112 | conf.request_header_cookie = eval(ConfigFileParser().request_header_cookie()) 113 | conf.request_header_401_auth = eval(ConfigFileParser().request_header_401_auth()) 114 | conf.request_timeout = eval(ConfigFileParser().request_timeout()) 115 | conf.request_delay = eval(ConfigFileParser().request_delay()) 116 | conf.request_limit = eval(ConfigFileParser().request_limit()) 117 | conf.request_max_retries = eval(ConfigFileParser().request_max_retries()) 118 | conf.request_persistent_connect = eval(ConfigFileParser().request_persistent_connect()) 119 | conf.request_method = eval(ConfigFileParser().request_method()) 120 | conf.redirection_302 = eval(ConfigFileParser().redirection_302()) 121 | conf.file_extension = eval(ConfigFileParser().file_extension()) 122 | 123 | conf.response_status_code = eval(ConfigFileParser().response_status_code()) 124 | conf.response_header_content_type = eval(ConfigFileParser().response_header_content_type()) 125 | conf.response_size = eval(ConfigFileParser().response_size()) 126 | conf.auto_check_404_page = eval(ConfigFileParser().auto_check_404_page()) 127 | conf.custom_503_page = eval(ConfigFileParser().custom_503_page()) 128 | conf.custom_response_page = eval(ConfigFileParser().custom_response_page()) 129 | conf.skip_size = eval(ConfigFileParser().skip_size()) 130 | 131 | conf.proxy_server = eval(ConfigFileParser().proxy_server()) 132 | 133 | conf.debug = eval(ConfigFileParser().debug()) 134 | conf.update = eval(ConfigFileParser().update()) 135 | 136 | def recursiveScan(response_url,all_payloads): 137 | ''' 138 | @description: 检测出一级目录后,一级目录后遍历添加所有payload,继续检测 139 | @param {type} 140 | @return: 141 | ''' 142 | if not conf.recursive_scan: 143 | return 144 | # 当前url后缀在黑名单内,不进行递归 145 | if response_url.split('.')[-1].lower() in conf.recursive_blacklist_exts: 146 | return 147 | #XXX:payloads字典要固定格式 148 | for payload in all_payloads: 149 | #判断是否排除。若在排除的目录列表中,则排除。self.excludeSubdirs排除的列表,配置文件中,形如:/test、/test1 150 | if payload in [directory for directory in conf.exclude_subdirs]: 151 | return 152 | #payload拼接,处理/重复或缺失 153 | if response_url.endswith('/') and payload.startswith('/'): 154 | # /重复,url和payload都有/,删去payload的/前缀 155 | payload = payload[1:] 156 | elif (not response_url.endswith('/')) and (not payload.startswith('/')): 157 | # /缺失,url和payload都不包含/,在payload前追加/ 158 | payload = '/'+payload 159 | #拼接payload,限制url长度,入队tasks 160 | newpayload=response_url+payload 161 | if(len(newpayload) < int(conf.recursive_scan_max_url_length)): 162 | tasks.all_task.put(response_url + payload) 163 | 164 | def loadSingleDict(path): 165 | ''' 166 | @description: 添加单个字典文件 167 | @param {path:字典文件路径} 168 | @return: 169 | ''' 170 | try: 171 | outputscreen.success('[+] Load dict:{}'.format(path)) 172 | #加载文件时,使用utf-8编码,防止出现编码问题 173 | with open(path,encoding='utf-8') as single_file: 174 | return single_file.read().splitlines() 175 | except Exception as e: 176 | outputscreen.error('[x] plz check file path!\n[x] error:{}'.format(e)) 177 | sys.exit() 178 | 179 | def loadMultDict(path): 180 | ''' 181 | @description: 添加多个字典文件 182 | @param {path:字典文件路径} 183 | @return: 184 | ''' 185 | tmp_list = [] 186 | try: 187 | for file in os.listdir(path): 188 | #FIXME:这里解决dict和fuzz模式加载多字典问题,但是loadMultDict变得臃肿,后期需要处理 189 | if conf.dict_mode and conf.fuzz_mode: 190 | outputscreen.error('[x] Can not use dict and fuzz mode at the same time!') 191 | sys.exit() 192 | if conf.dict_mode == 2: 193 | tmp_list.extend(loadSingleDict(os.path.join(conf.dict_mode_load_mult_dict,file))) 194 | if conf.fuzz_mode == 2: 195 | tmp_list.extend(loadSingleDict(os.path.join(conf.fuzz_mode_load_mult_dict,file))) 196 | return tmp_list 197 | except Exception as e: 198 | outputscreen.error('[x] plz check file path!\n[x] error:{}'.format(e)) 199 | sys.exit() 200 | 201 | def loadSuffix(path): 202 | ''' 203 | @description: 添加动态爬虫字典后缀规则 204 | @param {type} 205 | @return: 206 | ''' 207 | try: 208 | with open(path) as f: 209 | #要去掉#开头的字典 210 | payloads.suffix = set(f.read().split('\n')) - {'', '#'} 211 | except Exception as e: 212 | outputscreen.error('[x] plz check file path!\n[x] error:{}'.format(e)) 213 | sys.exit() 214 | 215 | def generateCrawlDict(base_url): 216 | ''' 217 | @description: 生成动态爬虫字典 218 | @param {base_url:} 219 | @return: 220 | ''' 221 | def _splitFilename(filename): 222 | 223 | full_filename = filename.rstrip('.') 224 | extension = full_filename.split('.')[-1] 225 | name = '.'.join(full_filename.split('.')[:-1]) 226 | 227 | return name, extension 228 | 229 | url = base_url.split('?')[0].rstrip('/') 230 | if not urllib.parse.urlparse(url).path: 231 | return list() 232 | 233 | path = '/'.join(url.split('/')[:-1]) 234 | filename = url.split('/')[-1] 235 | 236 | # Check if target CMS uses route instead of static file 237 | isfile = True if '.' in filename else False 238 | 239 | if isfile: 240 | name, extension = _splitFilename(filename) 241 | 242 | final_urls = list() 243 | for each in payloads.suffix: 244 | new_filename = path + '/' + each.replace('{FULL}', filename) 245 | if isfile: 246 | new_filename = new_filename.replace('{NAME}', name).replace('{EXT}', extension) 247 | else: 248 | if '{NAME}' in each or '{EXT}' in each: 249 | continue 250 | final_urls.append(urllib.parse.urlparse(new_filename.replace('..', '.')).path) 251 | 252 | return final_urls 253 | 254 | def generateBlastDict(): 255 | ''' 256 | @description: 生成纯暴力字典,支持断点续生成 257 | @param {type} 258 | @return: 259 | ''' 260 | if conf.blast_mode_min > conf.blast_mode_max: 261 | outputscreen.error("[x] The minimum length should be less than or equal to the maximum length") 262 | sys.exit(1) 263 | the_min = conf.blast_mode_min 264 | if conf.blast_mode_resume_charset != '': 265 | the_min = len(conf.blast_mode_resume_charset) 266 | if conf.blast_mode_min > the_min or conf.blast_mode_max < the_min: 267 | outputscreen.error('[+] Invalid resume length: %d\n\n' % the_min) 268 | the_min = conf.blast_mode_min 269 | conf.blast_mode_resume_charset = '' 270 | for length in range(the_min, conf.blast_mode_max + 1): 271 | generateLengthDict(length) 272 | conf.blast_mode_resume_charset = '' 273 | return payloads.blast_mode_custom_charset_dict 274 | 275 | def generateLengthDict(length): 276 | ''' 277 | @description: 生成length长度的字典 278 | @param {type} 279 | @return: 280 | ''' 281 | lst = [0] * length 282 | if len(conf.blast_mode_resume_charset) == length and conf.blast_mode_resume_charset != '': 283 | #enumerate()用于将一个可遍历的数据对象(如列表、元组或字符串)组合为一个索引序列 284 | for i, letter in enumerate(conf.blast_mode_resume_charset): 285 | if conf.blast_mode_custom_charset.find(letter) == -1: 286 | outputscreen.error('[+] Invalid resume string: "%s"\n\n' % conf.blast_mode_resume_charset) 287 | lst = [0] * length 288 | break 289 | lst[i] = conf.blast_mode_custom_charset.find(letter) 290 | lines_max = 1 291 | for l in lst: 292 | lines_max *= (len(conf.blast_mode_custom_charset) - l) 293 | i = length - 1 294 | print_it = True 295 | while i >= 0: 296 | if print_it: 297 | temp = '' 298 | for j in lst: 299 | temp += conf.blast_mode_custom_charset[j] 300 | payloads.blast_mode_custom_charset_dict.append(temp) 301 | print_it = False 302 | lst[i] += 1 303 | if lst[i] >= len(conf.blast_mode_custom_charset): 304 | lst[i] = 0 305 | i -= 1 306 | else: 307 | i = length - 1 308 | print_it = True 309 | 310 | def generateSingleFuzzDict(path): 311 | ''' 312 | @description: 单字典。生成fuzz字典 313 | @param {type} 314 | @return: 315 | ''' 316 | fuzz_path = urllib.parse.urlparse(conf.url).path 317 | #替换label进行fuzz字典生成 318 | if conf.fuzz_mode_label in fuzz_path: 319 | for i in loadSingleDict(path): 320 | payloads.fuzz_mode_dict.append(fuzz_path.replace(conf.fuzz_mode_label,i)) 321 | return payloads.fuzz_mode_dict 322 | else: 323 | outputscreen.error("[x] Please set the fuzz label") 324 | sys.exit(1) 325 | def generateMultFuzzDict(path): 326 | ''' 327 | @description: 多字典。生成fuzz字典 328 | @param {type} 329 | @return: 330 | ''' 331 | fuzz_path = urllib.parse.urlparse(conf.url).path 332 | #替换label进行fuzz字典生成 333 | if conf.fuzz_mode_label in fuzz_path: 334 | for i in loadMultDict(path): 335 | payloads.fuzz_mode_dict.append(fuzz_path.replace(conf.fuzz_mode_label,i)) 336 | return payloads.fuzz_mode_dict 337 | else: 338 | outputscreen.error("[x] Please set the fuzz label") 339 | sys.exit(1) 340 | 341 | def scanModeHandler(): 342 | ''' 343 | @description: 扫描模式处理,加载payloads 344 | @param {type} 345 | @return: 346 | ''' 347 | if conf.recursive_scan: 348 | msg = '[*] Use recursive scan: Yes' 349 | outputscreen.warning('\r'+msg+' '*(th.console_width-len(msg)+1)) 350 | else: 351 | msg = '[*] Use recursive scan: No' 352 | outputscreen.warning('\r'+msg+' '*(th.console_width-len(msg)+1)) 353 | payloadlists=[] 354 | # fuzz模式处理,只能单独加载 355 | if conf.fuzz_mode: 356 | outputscreen.warning('[*] Use fuzz mode') 357 | if conf.fuzz_mode == 1: 358 | return generateSingleFuzzDict(conf.fuzz_mode_load_single_dict) 359 | if conf.fuzz_mode == 2: 360 | return generateMultFuzzDict(conf.fuzz_mode_load_mult_dict) 361 | # 其他模式处理,可同时加载 362 | else: 363 | if conf.dict_mode: 364 | outputscreen.warning('[*] Use dict mode') 365 | if conf.dict_mode == 1: 366 | payloadlists.extend(loadSingleDict(conf.dict_mode_load_single_dict)) 367 | elif conf.dict_mode == 2: 368 | payloadlists.extend(loadMultDict(conf.dict_mode_load_mult_dict)) 369 | else: 370 | outputscreen.error("[-] You must select a dict") 371 | sys.exit() 372 | if conf.blast_mode: 373 | outputscreen.warning('[*] Use blast mode') 374 | outputscreen.warning('[*] Use char set: {}'.format(conf.blast_mode_custom_charset)) 375 | outputscreen.warning('[*] Use paylaod min length: {}'.format(conf.blast_mode_min)) 376 | outputscreen.warning('[*] Use paylaod max length: {}'.format(conf.blast_mode_max)) 377 | payloadlists.extend(generateBlastDict()) 378 | #TODO:递归爬取url 379 | if conf.crawl_mode: 380 | outputscreen.warning('[*] Use crawl mode') 381 | #自定义header 382 | headers = {} 383 | if conf.request_headers: 384 | try: 385 | for header in conf.request_headers.split(','): 386 | k, v = header.split('=') 387 | #print(k,v) 388 | headers[k] = v 389 | except Exception as e: 390 | outputscreen.error("[x] Check personalized headers format: header=value,header=value.\n[x] error:{}".format(e)) 391 | # sys.exit() 392 | #自定义ua 393 | if conf.request_header_ua: 394 | headers['User-Agent'] = conf.request_header_ua 395 | #自定义cookie 396 | if conf.request_header_cookie: 397 | headers['Cookie'] = conf.request_header_cookie 398 | try: 399 | response = requests.get(conf.url, headers=headers, timeout=conf.request_timeout, verify=False, allow_redirects=conf.redirection_302, proxies=conf.proxy_server) 400 | #获取页面url 401 | if (response.status_code in conf.response_status_code) and response.text: 402 | html = etree.HTML(response.text) 403 | #加载自定义xpath用于解析html 404 | urls = html.xpath(conf.crawl_mode_parse_html) 405 | for url in urls: 406 | #去除相似url 407 | if urlSimilarCheck(url): 408 | #判断:1.是否同域名 2.netloc是否为空(值空时为同域)。若满足1或2,则添加到temp payload 409 | if (urllib.parse.urlparse(url).netloc == urllib.parse.urlparse(conf.url).netloc) or urllib.parse.urlparse(url).netloc == '': 410 | payloads.crawl_mode_dynamic_fuzz_temp_dict.add(url) 411 | payloads.crawl_mode_dynamic_fuzz_temp_dict = payloads.crawl_mode_dynamic_fuzz_temp_dict - {'#', ''} 412 | if conf.crawl_mode_dynamic_fuzz: 413 | #加载动态fuzz后缀,TODO:独立动态生成字典模块 414 | loadSuffix(os.path.join(paths.DATA_PATH,conf.crawl_mode_dynamic_fuzz_suffix)) 415 | #生成新爬虫动态字典 416 | for i in payloads.crawl_mode_dynamic_fuzz_temp_dict: 417 | payloads.crawl_mode_dynamic_fuzz_dict.extend(generateCrawlDict(i)) 418 | for i in payloads.crawl_mode_dynamic_fuzz_temp_dict: 419 | payloads.crawl_mode_dynamic_fuzz_dict.append(urllib.parse.urlparse(i).path) 420 | payloadlists.extend(set(payloads.crawl_mode_dynamic_fuzz_dict)) 421 | else: 422 | for i in payloads.crawl_mode_dynamic_fuzz_temp_dict: 423 | payloads.crawl_mode_dynamic_fuzz_dict.append(urllib.parse.urlparse(i).path) 424 | payloadlists.extend(set(payloads.crawl_mode_dynamic_fuzz_dict)) 425 | except (requests.exceptions.ConnectionError, requests.exceptions.Timeout, requests.exceptions.ReadTimeout) as e: 426 | outputscreen.error("[x] Crawler network connection error!plz check whether the target is accessible, error info:{}".format(e)) 427 | # sys.exit() 428 | 429 | if payloadlists: 430 | return payloadlists 431 | else: 432 | outputscreen.error("[-] You have to select at least one mode , plz check mode config") 433 | sys.exit() 434 | 435 | def responseHandler(response): 436 | ''' 437 | @description: 处理响应结果 438 | @param {type} 439 | @return: 440 | ''' 441 | #结果处理阶段 442 | try: 443 | size = intToSize(int(response.headers['content-length'])) 444 | except (KeyError, ValueError): 445 | size = intToSize(len(response.content)) 446 | #跳过大小为skip_size的页面 447 | if size == conf.skip_size: 448 | return 449 | 450 | #自动识别404-判断是否与获取404页面特征匹配 451 | if conf.auto_check_404_page: 452 | if hashlib.md5(response.content).hexdigest() in conf.autodiscriminator_md5: 453 | return 454 | 455 | #自定义状态码显示 456 | if response.status_code in conf.response_status_code: 457 | msg = '[{}]'.format(str(response.status_code)) 458 | if conf.response_header_content_type: 459 | msg += '[{}]'.format(response.headers.get('content-type')) 460 | if conf.response_size: 461 | msg += '[{}] '.format(str(size)) 462 | msg += response.url 463 | if response.status_code in [200]: 464 | outputscreen.success('\r'+msg+' '*(th.console_width-len(msg)+1)) 465 | elif response.status_code in [301,302]: 466 | outputscreen.warning('\r'+msg+' '*(th.console_width-len(msg)+1)) 467 | elif response.status_code in [403,404]: 468 | outputscreen.error('\r'+msg+' '*(th.console_width-len(msg)+1)) 469 | else: 470 | outputscreen.info('\r'+msg+' '*(th.console_width-len(msg)+1)) 471 | #已去重复,结果保存。NOTE:此处使用response.url进行文件名构造,解决使用-iL参数时,不能按照域名来命名文件名的问题 472 | #使用replace(),替换`:`,修复window下不能创建有`:`的文件问题 473 | saveResults(urllib.parse.urlparse(response.url).netloc.replace(':','_'),msg) 474 | #关于递归扫描。响应在自定义状态码中时,添加判断是否进行递归扫描 475 | if response.status_code in conf.recursive_status_code: 476 | if conf.recursive_scan: 477 | recursiveScan(response.url,payloads.all_payloads) 478 | 479 | #自定义正则匹配响应 480 | if conf.custom_response_page: 481 | pattern = re.compile(conf.custom_response_page) 482 | if pattern.search(response.text): 483 | outputscreen.info('[!] Custom response information matched\n[!] use regular expression:{}\n[!] matched page:{}'.format(conf.custom_response_page,response.text)) 484 | 485 | def worker(): 486 | ''' 487 | @description: 封包发包穷举器 488 | @param {type} 489 | @return: 490 | ''' 491 | payloads.current_payload = tasks.all_task.get() 492 | #1自定义封包阶段 493 | #自定义header 494 | headers = {} 495 | if conf.request_headers: 496 | try: 497 | for header in conf.request_headers.split(','): 498 | k, v = header.split('=') 499 | #print(k,v) 500 | headers[k] = v 501 | except Exception as e: 502 | outputscreen.error("[x] Check personalized headers format: header=value,header=value.\n[x] error:{}".format(e)) 503 | sys.exit() 504 | #自定义ua 505 | if conf.request_header_ua: 506 | headers['User-Agent'] = conf.request_header_ua 507 | #自定义cookie 508 | if conf.request_header_cookie: 509 | headers['Cookie'] = conf.request_header_cookie 510 | 511 | try: 512 | #2进入发送请求流程 513 | #延迟请求 514 | if conf.request_delay: 515 | random_sleep_second = random.randint(0,abs(conf.request_delay)) 516 | time.sleep(random_sleep_second) 517 | 518 | response = requests.request(conf.request_method, payloads.current_payload, headers=headers, timeout=conf.request_timeout, verify=False, allow_redirects=conf.redirection_302, proxies=conf.proxy_server) 519 | #3进入结果处理流程 520 | responseHandler(response) 521 | except requests.exceptions.Timeout as e: 522 | #outputscreen.error('[x] timeout! url:{}'.format(payloads.current_payload)) 523 | pass 524 | except Exception as e: 525 | # outputscreen.error('[x] error:{}'.format(e)) 526 | pass 527 | finally: 528 | #更新进度条 529 | tasks.task_count += 1 530 | bar.log.update(tasks.task_count) 531 | 532 | def boss(): 533 | ''' 534 | @description: worker控制器 535 | @param {type} 536 | @return: 537 | ''' 538 | while not tasks.all_task.empty(): 539 | worker() 540 | 541 | def bruter(url): 542 | ''' 543 | @description: 扫描插件入口函数 544 | @param {url:目标} 545 | @return: 546 | ''' 547 | 548 | #url初始化 549 | conf.parsed_url = urllib.parse.urlparse(url) 550 | #填补协议 551 | if conf.parsed_url.scheme != 'http' and conf.parsed_url.scheme != 'https': 552 | url = 'http://' + url 553 | conf.parsed_url = urllib.parse.urlparse(url) 554 | #全局target的url,给crawl、fuzz模块使用。XXX:要放在填补url之前,否则fuzz模式会出现这样的问题:https://target.com/phpinfo.{dir}/ 555 | conf.url = url 556 | #填补url后的/ 557 | if not url.endswith('/'): 558 | url = url + '/' 559 | 560 | #打印当前target 561 | msg = '[+] Current target: {}'.format(url) 562 | outputscreen.success('\r'+msg+' '*(th.console_width-len(msg)+1)) 563 | #自动识别404-预先获取404页面特征 564 | if conf.auto_check_404_page: 565 | outputscreen.warning("[*] Launching auto check 404") 566 | # Autodiscriminator (probably deprecated by future diagnostic subsystem) 567 | i = Inspector(url) 568 | (result, notfound_type) = i.check_this() 569 | if notfound_type == Inspector.TEST404_MD5 or notfound_type == Inspector.TEST404_OK: 570 | conf.autodiscriminator_md5.add(result) 571 | 572 | #加载payloads 573 | payloads.all_payloads = scanModeHandler() 574 | #FIXME:设置后缀名。当前以拼接方式实现,遍历一遍payload。 575 | try: 576 | if conf.file_extension: 577 | outputscreen.warning('[+] Use file extentsion: {}'.format(conf.file_extension)) 578 | for i in range(len(payloads.all_payloads)): 579 | payloads.all_payloads[i] += conf.file_extension 580 | except: 581 | outputscreen.error('[+] plz check extension!') 582 | sys.exit() 583 | #debug模式,打印所有payload,并退出 584 | if conf.debug: 585 | outputscreen.blue('[+] all payloads:{}'.format(payloads.all_payloads)) 586 | sys.exit() 587 | #payload入队task队列 588 | for payload in payloads.all_payloads: 589 | #FIXME:添加fuzz模式时,引入的url_payload构造判断 590 | if conf.fuzz_mode: 591 | url_payload = conf.parsed_url.scheme + '://' + conf.parsed_url.netloc + payload 592 | else: 593 | url_payload = url + payload 594 | #print(url_payload) 595 | #payload入队,等待处理 596 | tasks.all_task.put(url_payload) 597 | #设置进度条长度,若是递归模式或爬虫模式,则不设置任务队列长度,即无法显示进度,仅显示耗时 598 | if not conf.recursive_scan: 599 | #NOTE:这里取所有payloads的长度*target数量计算任务总数,修复issue#2 600 | tasks.task_length = len(payloads.all_payloads)*conf.target_nums 601 | bar.log.start(tasks.task_length) 602 | #FIXME:循环任务数不能一次性取完所有的task,暂时采用每次执行30个任务。这样写还能解决hub.LoopExit的bug 603 | while not tasks.all_task.empty(): 604 | all_task = [gevent.spawn(boss) for i in range(conf.request_limit)] 605 | gevent.joinall(all_task) 606 | -------------------------------------------------------------------------------- /lib/controller/engine.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | 4 | ''' 5 | @Author: xxlin 6 | @LastEditors: xxlin 7 | @Date: 2019-04-10 13:27:58 8 | @LastEditTime: 2019-05-01 20:19:40 9 | ''' 10 | 11 | import gevent 12 | import sys 13 | import time 14 | import traceback 15 | from lib.core.data import conf,paths,th 16 | from lib.core.common import outputscreen 17 | from lib.core.enums import BRUTER_RESULT_STATUS 18 | from lib.utils.console import getTerminalSize 19 | from lib.controller.bruter import bruter 20 | 21 | def initEngine(): 22 | # init control parameter 23 | th.result = [] 24 | th.thread_num = conf.thread_num 25 | th.target = conf.target 26 | #是否继续扫描标志位 27 | th.is_continue = True 28 | #控制台宽度 29 | th.console_width = getTerminalSize()[0] - 2 30 | #记录开始时间 31 | th.start_time = time.time() 32 | msg = '[+] Set the number of thread: %d' % th.thread_num 33 | outputscreen.success(msg) 34 | 35 | def scan(): 36 | while True: 37 | #协程模式 38 | if th.target.qsize() > 0 and th.is_continue: 39 | target = str(th.target.get(timeout=1.0)) 40 | else: 41 | break 42 | try: 43 | #对每个target进行检测 44 | bruter(target) 45 | except Exception: 46 | #抛出异常时,添加errmsg键值 47 | th.errmsg = traceback.format_exc() 48 | th.is_continue = False 49 | 50 | def run(): 51 | initEngine() 52 | # Coroutine mode 53 | outputscreen.success('[+] Coroutine mode') 54 | gevent.joinall([gevent.spawn(scan) for i in range(0, th.thread_num)]) 55 | if 'errmsg' in th: 56 | outputscreen.error(th.errmsg) 57 | -------------------------------------------------------------------------------- /lib/core/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | 4 | ''' 5 | @Author: xxlin 6 | @LastEditors: xxlin 7 | @Date: 2019-04-11 16:11:27 8 | @LastEditTime: 2019-04-11 20:11:24 9 | ''' 10 | -------------------------------------------------------------------------------- /lib/core/common.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | 4 | ''' 5 | @Author: xxlin 6 | @LastEditors: ttttmr 7 | @Date: 2019-04-10 13:27:58 8 | @LastEditTime: 2019-05-29 16:49:22 9 | ''' 10 | 11 | import os.path 12 | import sys 13 | import urllib 14 | import ipaddress 15 | import re 16 | 17 | from lib.core.data import cmdLineOptions, conf, paths, payloads 18 | from lib.core.enums import COLOR 19 | from lib.core.setting import BANNER 20 | from thirdlib.colorama import Back, Fore, Style, init 21 | 22 | init(autoreset=True) 23 | class Outputscreen: 24 | """ 25 | 显示颜色类 26 | """ 27 | def info(self, s): 28 | print(Style.BRIGHT+Fore.WHITE + str(s) + Fore.RESET+Style.RESET_ALL) 29 | 30 | def success(self, s): 31 | print(Style.BRIGHT+Fore.GREEN + str(s) + Fore.RESET+Style.RESET_ALL) 32 | 33 | def warning(self, s): 34 | print(Style.BRIGHT+Fore.CYAN + str(s) + Fore.RESET+Style.RESET_ALL) 35 | 36 | def error(self, s): 37 | print(Style.BRIGHT+Fore.RED + str(s) + Fore.RESET+Style.RESET_ALL) 38 | 39 | # for banner 40 | def blue(self, s): 41 | print(Style.BRIGHT+Fore.BLUE + str(s) + Fore.RESET+Style.RESET_ALL) 42 | 43 | #创建outputscreen对象,用于输出各种颜色的信息 44 | outputscreen=Outputscreen() 45 | 46 | def setPaths(): 47 | """ 48 | 设置全局绝对路径 49 | """ 50 | # 根目录 51 | root_path = paths.ROOT_PATH 52 | # datapath 53 | paths.DATA_PATH = os.path.join(root_path, "data") 54 | paths.OUTPUT_PATH = os.path.join(root_path, "output") 55 | paths.CONFIG_PATH = os.path.join(root_path, "dirmap.conf") 56 | if not os.path.exists(paths.OUTPUT_PATH): 57 | os.mkdir(paths.OUTPUT_PATH) 58 | if not os.path.exists(paths.DATA_PATH): 59 | os.mkdir(paths.DATA_PATH) 60 | 61 | # paths.WEAK_PASS = os.path.join(paths.DATA_PATH, "pass100.txt") 62 | # paths.LARGE_WEAK_PASS = os.path.join(paths.DATA_PATH, "pass1000.txt") 63 | # paths.UA_LIST_PATH = os.path.join(paths.DATA_PATH, "user-agents.txt") 64 | 65 | if os.path.isfile(paths.CONFIG_PATH): 66 | pass 67 | else: 68 | msg = 'Config files missing, it may cause an issue.\n' 69 | outputscreen.error(msg) 70 | sys.exit(0) 71 | 72 | #print(root_path,paths.DATA_PATH,paths.SCRIPT_PATH,paths.OUTPUT_PATH,paths.CONFIG_PATH) 73 | #print(paths.WEAK_PASS,paths.LARGE_WEAK_PASS,paths.UA_LIST_PATH) 74 | 75 | def banner(): 76 | ''' 77 | @description: 打印banner 78 | @param {type} 79 | @return: 80 | ''' 81 | outputscreen.blue(BANNER) 82 | 83 | # 将'192.168.1.1-192.168.1.100'分解成ip地址列表 84 | def genIP(ip_range): 85 | ''' 86 | print (genIP('192.18.1.1-192.168.1.3')) 87 | ['192.168.1.1', '192.168.1.2', '192.168.1.3'] 88 | ''' 89 | # from https://segmentfault.com/a/1190000010324211 90 | def num2ip (num): 91 | return '%s.%s.%s.%s' % ((num >> 24) & 0xff, (num >> 16) & 0xff, (num >> 8) & 0xff, (num & 0xff)) 92 | 93 | def ip2num(ip): 94 | ips = [int(x) for x in ip.split('.')] 95 | return ips[0]<< 24 | ips[1]<< 16 | ips[2] << 8 | ips[3] 96 | 97 | start ,end = [ip2num(x) for x in ip_range.split('-')] 98 | return [num2ip(num) for num in range(start,end+1) if num & 0xff] 99 | 100 | # 识别目标,转换成列表形式 101 | def parseTarget(target): 102 | lists=[] 103 | ipv4withmask_re=re.compile("^(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])\.(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])\.(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])\.(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])/(3[0-2]|[1-2]?[0-9])$") 104 | ipv4range_re=re.compile("^(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])\.(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])\.(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])\.(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])-(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])\.(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])\.(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])\.(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])$") 105 | try: 106 | # 尝试解析url 107 | parsed_url=urllib.parse.urlparse(target) 108 | # 判断不带http 109 | if parsed_url.scheme != 'http': 110 | # 判断IP/Mask格式 111 | if ipv4withmask_re.search(parsed_url.path): 112 | # 是子网还是网址 e.g. 192.168.1.1/24 or http://192.168.1.1/24 113 | infomsg = "[*] %s is IP/Mask[Y] or URL(http://)[n]? [Y/n]" %target 114 | outputscreen.info(infomsg) 115 | flag =input() 116 | if flag in ('N', 'n', 'no', 'No', 'NO'): 117 | # 按链接处理 e.g. http://192.168.1.1/24 118 | lists.append(target) 119 | else: 120 | # 按子网处理 e.g. 192.168.1.1/24 121 | lists=list(ipaddress.ip_interface(target).network) 122 | # 判断网络范围格式 e.g. 192.168.1.1-192.168.1.100 123 | elif ipv4range_re.search(target): 124 | lists=genIP(target) 125 | # 按照链接处理 126 | else: 127 | lists.append(target) 128 | # 为http://格式 129 | else: 130 | lists.append(target) 131 | except: 132 | # 识别失败 133 | pass 134 | return lists 135 | 136 | def intToSize(bytes): 137 | ''' 138 | @description: bits大小转换,对人类友好 139 | @param {type} 140 | @return: 141 | ''' 142 | b = 1024 * 1024 * 1024 * 1024 143 | a = ['t','g','m','k',''] 144 | for i in a: 145 | if bytes >= b: 146 | return '%.2f%sb' % (float(bytes) / float(b), i) 147 | b /= 1024 148 | return '0b' 149 | 150 | def urlSimilarCheck(url): 151 | ''' 152 | @description: url相似度分析,当url路径和参数键值类似时,则判为重复,参考某人爬虫 153 | @param {type} 154 | @return: 非重复返回True 155 | ''' 156 | url_struct = urllib.parse.urlparse(url) 157 | query_key = '|'.join(sorted([i.split('=')[0] for i in url_struct.query.split('&')])) 158 | url_hash = hash(url_struct.path + query_key) 159 | if url_hash not in payloads.similar_urls_set: 160 | payloads.similar_urls_set.add(url_hash) 161 | return True 162 | return False 163 | -------------------------------------------------------------------------------- /lib/core/data.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | 4 | ''' 5 | @Author: xxlin 6 | @LastEditors: xxlin 7 | @Date: 2019-04-10 13:27:58 8 | @LastEditTime: 2019-04-10 17:46:40 9 | ''' 10 | 11 | from lib.core.datatype import AttribDict 12 | 13 | # dirmap paths 14 | paths = AttribDict() 15 | 16 | # object to store original command line options 17 | cmdLineOptions = AttribDict() 18 | 19 | # object to share within function and classes command 20 | # line options and settings 21 | conf = AttribDict() 22 | 23 | # object to control engine 24 | th = AttribDict() 25 | 26 | #创建payloads字典对象存储payloads 27 | payloads = AttribDict() 28 | 29 | #创建tasks字典对象存储tasks 30 | tasks = AttribDict() 31 | 32 | #创建进度条对象存储进度 33 | bar = AttribDict() -------------------------------------------------------------------------------- /lib/core/datatype.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | 4 | ''' 5 | @Author: xxlin 6 | @LastEditors: xxlin 7 | @Date: 2019-04-10 13:27:58 8 | @LastEditTime: 2019-04-10 17:48:54 9 | ''' 10 | 11 | import copy 12 | import types 13 | 14 | class AttribDict(dict): 15 | """ 16 | This class defines the project object, inheriting from Python data 17 | type dictionary. 18 | 19 | >>> foo = AttribDict() 20 | >>> foo.bar = 1 21 | >>> foo.bar 22 | 1 23 | """ 24 | 25 | def __init__(self, indict=None, attribute=None): 26 | if indict is None: 27 | indict = {} 28 | 29 | # Set any attributes here - before initialisation 30 | # these remain as normal attributes 31 | self.attribute = attribute 32 | dict.__init__(self, indict) 33 | self.__initialised = True 34 | 35 | # After initialisation, setting attributes 36 | # is the same as setting an item 37 | 38 | def __getattr__(self, item): 39 | """ 40 | Maps values to attributes 41 | Only called if there *is NOT* an attribute with this name 42 | """ 43 | 44 | try: 45 | return self.__getitem__(item) 46 | except KeyError: 47 | raise AttributeError("unable to access item '%s'" % item) 48 | 49 | def __setattr__(self, item, value): 50 | """ 51 | Maps attributes to values 52 | Only if we are initialised 53 | """ 54 | 55 | # This test allows attributes to be set in the __init__ method 56 | if "_AttribDict__initialised" not in self.__dict__: 57 | return dict.__setattr__(self, item, value) 58 | 59 | # Any normal attributes are handled normally 60 | elif item in self.__dict__: 61 | dict.__setattr__(self, item, value) 62 | 63 | else: 64 | self.__setitem__(item, value) 65 | 66 | def __getstate__(self): 67 | return self.__dict__ 68 | 69 | def __setstate__(self, dict): 70 | self.__dict__ = dict 71 | 72 | def __deepcopy__(self, memo): 73 | retVal = self.__class__() 74 | memo[id(self)] = retVal 75 | 76 | for attr in dir(self): 77 | if not attr.startswith('_'): 78 | value = getattr(self, attr) 79 | if not isinstance(value, (types.BuiltinFunctionType, types.FunctionType, types.MethodType)): 80 | setattr(retVal, attr, copy.deepcopy(value, memo)) 81 | 82 | for key, value in self.items(): 83 | retVal.__setitem__(key, copy.deepcopy(value, memo)) 84 | 85 | return retVal 86 | 87 | -------------------------------------------------------------------------------- /lib/core/enums.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | 4 | ''' 5 | @Author: xxlin 6 | @LastEditors: xxlin 7 | @Date: 2019-04-10 13:27:58 8 | @LastEditTime: 2019-04-14 10:36:27 9 | ''' 10 | 11 | class COLOR: 12 | black = 30 # 黑色 13 | red = 31 # 红色 14 | green = 32 # 绿色 15 | yellow = 33 # 黄色 16 | blue = 34 # 蓝色 17 | purple = 35 # 紫红色 18 | cyan = 36 # 青蓝色 19 | white = 37 # 白色 20 | 21 | class BRUTER_RESULT_STATUS: 22 | FAIL = 0 23 | SUCCESS = 1 24 | RETRAY = 2 25 | -------------------------------------------------------------------------------- /lib/core/option.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | 4 | ''' 5 | @Author: xxlin 6 | @LastEditors: ttttmr 7 | @Date: 2019-04-10 13:27:58 8 | @LastEditTime: 2019-05-29 16:52:42 9 | ''' 10 | 11 | import imp 12 | import os 13 | import queue 14 | import sys 15 | import time 16 | import ipaddress 17 | 18 | from lib.controller.bruter import loadConf 19 | from lib.core.common import parseTarget, outputscreen 20 | from lib.core.data import conf, paths 21 | from thirdlib.IPy.IPy import IP 22 | 23 | def initOptions(args): 24 | EngineRegister(args) 25 | BruterRegister(args) 26 | TargetRegister(args) 27 | 28 | 29 | def EngineRegister(args): 30 | """ 31 | 加载并发引擎模块 32 | """ 33 | conf.engine_mode = 'coroutine' 34 | 35 | #设置线程数 36 | if args.thread_num > 200 or args.thread_num < 1: 37 | msg = '[*] Invalid input in [-t](range: 1 to 200), has changed to default(30)' 38 | outputscreen.warning(msg) 39 | conf.thread_num = 30 40 | return 41 | conf.thread_num = args.thread_num 42 | 43 | def BruterRegister(args): 44 | """ 45 | 配置bruter模块 46 | """ 47 | 48 | if args.load_config_file: 49 | #加载配置文件 50 | loadConf() 51 | else: 52 | outputscreen.error("[+] Function development, coming soon!please use -lcf parameter") 53 | if args.debug: 54 | conf.debug = args.debug 55 | else: 56 | conf.debug = args.debug 57 | sys.exit() 58 | 59 | def TargetRegister(args): 60 | """ 61 | 加载目标模块 62 | """ 63 | msg = '[*] Initialize targets...' 64 | outputscreen.warning(msg) 65 | 66 | #初始化目标队列 67 | conf.target = queue.Queue() 68 | 69 | # 用户输入入队 70 | if args.target_input: 71 | # 尝试解析目标地址 72 | try: 73 | lists=parseTarget(args.target_input) 74 | except: 75 | helpmsg = "Invalid input in [-i], Example: -i [http://]target.com or 192.168.1.1[/24] or 192.168.1.1-192.168.1.100" 76 | outputscreen.error(helpmsg) 77 | sys.exit() 78 | # 判断处理量 79 | if (len(lists))>100000: 80 | warnmsg = "[*] Loading %d targets, Maybe it's too much, continue? [y/N]" % (len(lists)) 81 | outputscreen.warning(warnmsg) 82 | flag =input() 83 | if flag in ('Y', 'y', 'yes', 'YES','Yes'): 84 | pass 85 | else: 86 | msg = '[-] User quit!' 87 | outputscreen.warning(msg) 88 | sys.exit() 89 | msg = '[+] Load targets from: %s' % args.target_input 90 | outputscreen.success(msg) 91 | # save to conf 92 | for target in lists: 93 | conf.target.put(target) 94 | conf.target_nums = conf.target.qsize() 95 | 96 | # 文件读入入队 97 | elif args.target_file: 98 | if not os.path.isfile(args.target_file): 99 | msg = '[-] TargetFile not found: %s' % args.target_file 100 | outputscreen.error(msg) 101 | sys.exit() 102 | msg = '[+] Load targets from: %s' % args.target_file 103 | outputscreen.success(msg) 104 | with open(args.target_file, 'r', encoding='utf-8') as f: 105 | targets = f.readlines() 106 | for target in targets: 107 | target=target.strip('\n') 108 | parsed_target=parseTarget(target) 109 | for i in parsed_target: 110 | conf.target.put(i) 111 | conf.target_nums = conf.target.qsize() 112 | 113 | #验证目标数量 114 | if conf.target.qsize() == 0: 115 | errormsg = msg = '[!] No targets found.Please load targets with [-i|-iF]' 116 | outputscreen.error(errormsg) 117 | sys.exit() -------------------------------------------------------------------------------- /lib/core/setting.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | 4 | ''' 5 | @Author: xxlin 6 | @LastEditors: xxlin 7 | @Date: 2019-04-10 13:27:58 8 | @LastEditTime: 2019-04-11 15:58:40 9 | ''' 10 | VERSION = "1.1" 11 | BANNER = f""" 12 | ##### # ##### # # ## ##### 13 | # # # # # ## ## # # # # 14 | # # # # # # ## # # # # # 15 | # # # ##### # # ###### ##### 16 | # # # # # # # # # # 17 | ##### # # # # # # # # v{VERSION} 18 | """ -------------------------------------------------------------------------------- /lib/parse/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | 4 | ''' 5 | @Author: xxlin 6 | @LastEditors: xxlin 7 | @Date: 2019-04-11 20:11:49 8 | @LastEditTime: 2019-04-11 20:11:53 9 | ''' 10 | -------------------------------------------------------------------------------- /lib/parse/cmdline.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | 4 | ''' 5 | @Author: xxlin 6 | @LastEditors: ttttmr 7 | @Date: 2019-04-10 13:27:58 8 | @LastEditTime: 2019-07-22 21:46:31 9 | ''' 10 | 11 | import sys 12 | import argparse 13 | 14 | def cmdLineParser(): 15 | """ 16 | This function parses the command line parameters and arguments 17 | """ 18 | parser = argparse.ArgumentParser(usage="python3 dirmap.py -i https://target.com -lcf") 19 | 20 | # engine 21 | engine = parser.add_argument_group("Engine", "Engine config") 22 | engine.add_argument("-t","--thread", dest="thread_num", type=int, default=30, 23 | help="num of threads, default 30") 24 | 25 | # target 26 | target = parser.add_argument_group("Target","Target config") 27 | target.add_argument("-i", metavar="TARGET", dest="target_input", type=str, default="", 28 | help="scan a target or network (e.g. [http://]target.com , 192.168.1.1[/24] , 192.168.1.1-192.168.1.100)") 29 | target.add_argument("-iF", metavar="FILE", dest="target_file", type=str, default="", 30 | help="load targets from targetFile (e.g. urls.txt)") 31 | 32 | # bruter 33 | bruter = parser.add_argument_group("Bruter", "Bruter config") 34 | bruter.add_argument("-lcf", "--loadConfigFile", dest="load_config_file", default=True, action="store_true", 35 | help="Load the configuration through the configuration file") 36 | bruter.add_argument("--debug", dest="debug",default=False, action="store_true", 37 | help="Print payloads and exit") 38 | 39 | if len(sys.argv) == 1: 40 | sys.argv.append("-h") 41 | args = parser.parse_args() 42 | return args 43 | -------------------------------------------------------------------------------- /lib/plugins/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | 4 | ''' 5 | @Author: xxlin 6 | @LastEditors: xxlin 7 | @Date: 2019-04-11 20:11:08 8 | @LastEditTime: 2019-04-11 20:11:12 9 | ''' 10 | -------------------------------------------------------------------------------- /lib/plugins/inspector.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | 4 | ''' 5 | @Author: xxlin 6 | @LastEditors: ttttmr 7 | @Date: 2019-05-01 12:07:54 8 | @LastEditTime: 2019-06-26 00:25:14 9 | ''' 10 | 11 | import hashlib 12 | import random 13 | import sys 14 | import urllib 15 | 16 | import requests 17 | 18 | from lib.core.common import outputscreen 19 | from lib.core.data import th, conf 20 | 21 | USER_AGENT = "Mozilla/5.0 (Windows; U; MSIE 10.0; Windows NT 9.0; es-ES)" 22 | user_agent = {"user-agent": USER_AGENT} 23 | 24 | 25 | class Inspector: 26 | """This class mission is to examine the behaviour of the application when on 27 | purpose an inexistent page is requested""" 28 | TEST404_OK = 0 29 | TEST404_MD5 = 1 30 | TEST404_STRING = 2 31 | TEST404_URL = 3 32 | TEST404_NONE = 4 33 | 34 | def __init__(self, target): 35 | self.target = target 36 | 37 | def _give_it_a_try(self): 38 | """Every time this method is called it will request a random resource 39 | the target domain. Return value is a dictionary with values as 40 | HTTP response code, resquest size, md5 of the content and the content 41 | itself. If there were a redirection it will record the new url""" 42 | s = [] 43 | for n in range(0, 42): 44 | random.seed() 45 | s.append(chr(random.randrange(97, 122))) 46 | s = "".join(s) 47 | target = self.target + s 48 | 49 | outputscreen.success("[+] Checking with: {}".format(target)) 50 | 51 | try: 52 | page = requests.get(target, headers=user_agent, verify=False,timeout=5, proxies=conf.proxy_server) 53 | content = page.content 54 | result = { 55 | 'target': urllib.parse.urlparse(target).netloc, 56 | 'code': str(page.status_code), 57 | 'size': len(content), 58 | 'md5': hashlib.md5(content).hexdigest(), 59 | 'content': content, 60 | 'location': None 61 | } 62 | 63 | if len(page.history) >= 1: 64 | result['location'] = page.url 65 | return result 66 | except: 67 | result = { 68 | 'target': urllib.parse.urlparse(target).netloc, 69 | 'code': '', 70 | 'size': '', 71 | 'md5': '', 72 | 'content': '', 73 | 'location': None 74 | } 75 | return result 76 | 77 | def check_this(self): 78 | """Get the a request and decide what to do""" 79 | first_result = self._give_it_a_try() 80 | 81 | if first_result['code'] == '404': 82 | #msg = '[+] Target: {} Got a nice 404, problems not expected' 83 | #outputscreen.success("\r{}{}".format(self.target,' '*(th.console_width-len(msg)+len(self.target)+1))) 84 | # Ok, resquest gave a 404 so we should not find problems 85 | return '', Inspector.TEST404_OK 86 | 87 | elif first_result['code'] == '302' or first_result['location']: 88 | location = first_result['location'] 89 | return location, Inspector.TEST404_URL 90 | else: 91 | return first_result['md5'], Inspector.TEST404_MD5 92 | 93 | # We give up here :( 94 | return '', Inspector.TEST404_NONE 95 | 96 | if __name__ == '__main__': 97 | i = Inspector(sys.argv[1]) 98 | print(i.check_this()) 99 | -------------------------------------------------------------------------------- /lib/utils/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | 4 | ''' 5 | @Author: xxlin 6 | @LastEditors: xxlin 7 | @Date: 2019-04-11 16:11:27 8 | @LastEditTime: 2019-04-11 20:04:19 9 | ''' 10 | -------------------------------------------------------------------------------- /lib/utils/config.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | 4 | ''' 5 | @Author: xxlin 6 | @LastEditors: ttttmr 7 | @Date: 2019-04-11 09:49:16 8 | @LastEditTime: 2019-05-29 16:49:43 9 | ''' 10 | 11 | from configparser import ConfigParser 12 | from lib.core.data import paths 13 | from lib.core.common import outputscreen 14 | 15 | 16 | class ConfigFileParser: 17 | @staticmethod 18 | def _get_option(section, option): 19 | try: 20 | cf = ConfigParser() 21 | cf.read(paths.CONFIG_PATH) 22 | return cf.get(section=section, option=option) 23 | except: 24 | outputscreen.warning('Missing essential options, please check your config-file.') 25 | return '' 26 | 27 | 28 | def recursive_scan(self): 29 | return self._get_option('RecursiveScan','conf.recursive_scan') 30 | def recursive_status_code(self): 31 | return self._get_option('RecursiveScan','conf.recursive_status_code') 32 | def recursive_scan_max_url_length(self): 33 | return self._get_option('RecursiveScan','conf.recursive_scan_max_url_length') 34 | def recursive_blacklist_exts(self): 35 | return self._get_option('RecursiveScan','conf.recursive_blacklist_exts') 36 | def exclude_subdirs(self): 37 | return self._get_option('RecursiveScan','conf.exclude_subdirs') 38 | 39 | def dict_mode(self): 40 | return self._get_option('ScanModeHandler','conf.dict_mode') 41 | def dict_mode_load_single_dict(self): 42 | return self._get_option('ScanModeHandler','conf.dict_mode_load_single_dict') 43 | def dict_mode_load_mult_dict(self): 44 | return self._get_option('ScanModeHandler','conf.dict_mode_load_mult_dict') 45 | def blast_mode(self): 46 | return self._get_option('ScanModeHandler','conf.blast_mode') 47 | def blast_mode_min(self): 48 | return self._get_option('ScanModeHandler','conf.blast_mode_min') 49 | def blast_mode_max(self): 50 | return self._get_option('ScanModeHandler','conf.blast_mode_max') 51 | def blast_mode_az(self): 52 | return self._get_option('ScanModeHandler','conf.blast_mode_az') 53 | def blast_mode_num(self): 54 | return self._get_option('ScanModeHandler','conf.blast_mode_num') 55 | def blast_mode_custom_charset(self): 56 | return self._get_option('ScanModeHandler','conf.blast_mode_custom_charset') 57 | def blast_mode_resume_charset(self): 58 | return self._get_option('ScanModeHandler','conf.blast_mode_resume_charset') 59 | def crawl_mode(self): 60 | return self._get_option('ScanModeHandler','conf.crawl_mode') 61 | def crawl_mode_dynamic_fuzz_suffix(self): 62 | return self._get_option('ScanModeHandler','conf.crawl_mode_dynamic_fuzz_suffix') 63 | def crawl_mode_parse_robots(self): 64 | return self._get_option('ScanModeHandler','conf.crawl_mode_parse_robots') 65 | def crawl_mode_parse_html(self): 66 | return self._get_option('ScanModeHandler','conf.crawl_mode_parse_html') 67 | def crawl_mode_dynamic_fuzz(self): 68 | return self._get_option('ScanModeHandler','conf.crawl_mode_dynamic_fuzz') 69 | def fuzz_mode(self): 70 | return self._get_option('ScanModeHandler','conf.fuzz_mode') 71 | def fuzz_mode_load_single_dict(self): 72 | return self._get_option('ScanModeHandler','conf.fuzz_mode_load_single_dict') 73 | def fuzz_mode_load_mult_dict(self): 74 | return self._get_option('ScanModeHandler','conf.fuzz_mode_load_mult_dict') 75 | def fuzz_mode_label(self): 76 | return self._get_option('ScanModeHandler','conf.fuzz_mode_label') 77 | 78 | def request_headers(self): 79 | return self._get_option('RequestHandler','conf.request_headers') 80 | def request_header_ua(self): 81 | return self._get_option('RequestHandler','conf.request_header_ua') 82 | def request_header_cookie(self): 83 | return self._get_option('RequestHandler','conf.request_header_cookie') 84 | def request_header_401_auth(self): 85 | return self._get_option('RequestHandler','conf.request_header_401_auth') 86 | def request_timeout(self): 87 | return self._get_option('RequestHandler','conf.request_timeout') 88 | def request_delay(self): 89 | return self._get_option('RequestHandler','conf.request_delay') 90 | def request_limit(self): 91 | return self._get_option('RequestHandler','conf.request_limit') 92 | def request_max_retries(self): 93 | return self._get_option('RequestHandler','conf.request_max_retries') 94 | def request_persistent_connect(self): 95 | return self._get_option('RequestHandler','conf.request_persistent_connect') 96 | def request_method(self): 97 | return self._get_option('RequestHandler','conf.request_method') 98 | def redirection_302(self): 99 | return self._get_option('RequestHandler','conf.redirection_302') 100 | def file_extension(self): 101 | return self._get_option('RequestHandler','conf.file_extension') 102 | 103 | def response_status_code(self): 104 | return self._get_option('ResponseHandler','conf.response_status_code') 105 | def response_header_content_type(self): 106 | return self._get_option('ResponseHandler','conf.response_header_content_type') 107 | def response_size(self): 108 | return self._get_option('ResponseHandler','conf.response_size') 109 | def auto_check_404_page(self): 110 | return self._get_option('ResponseHandler','conf.auto_check_404_page') 111 | def custom_503_page(self): 112 | return self._get_option('ResponseHandler','conf.custom_503_page') 113 | def custom_response_page(self): 114 | return self._get_option('ResponseHandler','conf.custom_response_page') 115 | def skip_size(self): 116 | return self._get_option('ResponseHandler','conf.skip_size') 117 | 118 | def proxy_server(self): 119 | return self._get_option('ProxyHandler','conf.proxy_server') 120 | 121 | def debug(self): 122 | return self._get_option('DebugMode','conf.debug') 123 | def update(self): 124 | return self._get_option('CheckUpdate','conf.update') 125 | -------------------------------------------------------------------------------- /lib/utils/console.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | 4 | ''' 5 | @Author: xxlin 6 | @LastEditors: xxlin 7 | @Date: 2019-04-10 13:27:58 8 | @LastEditTime: 2019-04-11 15:57:17 9 | ''' 10 | 11 | """ 12 | getTerminalSize() 13 | - get width and height of console 14 | - works on linux,os x,windows,cygwin(windows) 15 | """ 16 | 17 | import os 18 | 19 | __all__ = ['getTerminalSize'] 20 | 21 | 22 | def getTerminalSize(): 23 | import platform 24 | current_os = platform.system() 25 | tuple_xy = None 26 | if current_os == 'Windows': 27 | tuple_xy = _getTerminalSize_windows() 28 | if tuple_xy is None: 29 | tuple_xy = _getTerminalSize_tput() 30 | # needed for window's python in cygwin's xterm! 31 | if current_os == 'Linux' or current_os == 'Darwin' or current_os.startswith('CYGWIN'): 32 | tuple_xy = _getTerminalSize_linux() 33 | if tuple_xy is None: 34 | tuple_xy = (80, 25) # default value 35 | return tuple_xy 36 | 37 | 38 | def _getTerminalSize_windows(): 39 | res = None 40 | try: 41 | from ctypes import windll, create_string_buffer 42 | 43 | # stdin handle is -10 44 | # stdout handle is -11 45 | # stderr handle is -12 46 | 47 | h = windll.kernel32.GetStdHandle(-12) 48 | csbi = create_string_buffer(22) 49 | res = windll.kernel32.GetConsoleScreenBufferInfo(h, csbi) 50 | except Exception: 51 | return None 52 | if res: 53 | import struct 54 | (bufx, bufy, curx, cury, wattr, 55 | left, top, right, bottom, maxx, maxy) = struct.unpack("hhhhHhhhhhh", csbi.raw) 56 | sizex = right - left + 1 57 | sizey = bottom - top + 1 58 | return sizex, sizey 59 | else: 60 | return None 61 | 62 | 63 | def _getTerminalSize_tput(): 64 | # get terminal width 65 | # src: http://stackoverflow.com/questions/263890/how-do-i-find-the-width-height-of-a-terminal-window 66 | try: 67 | import subprocess 68 | proc = subprocess.Popen(["tput", "cols"], stdin=subprocess.PIPE, stdout=subprocess.PIPE) 69 | output = proc.communicate(input=None) 70 | cols = int(output[0]) 71 | proc = subprocess.Popen(["tput", "lines"], stdin=subprocess.PIPE, stdout=subprocess.PIPE) 72 | output = proc.communicate(input=None) 73 | rows = int(output[0]) 74 | return (cols, rows) 75 | except Exception: 76 | return None 77 | 78 | 79 | def _getTerminalSize_linux(): 80 | def ioctl_GWINSZ(fd): 81 | try: 82 | import fcntl, termios, struct 83 | cr = struct.unpack('hh', fcntl.ioctl(fd, termios.TIOCGWINSZ, '1234')) 84 | except Exception: 85 | return None 86 | return cr 87 | 88 | cr = ioctl_GWINSZ(0) or ioctl_GWINSZ(1) or ioctl_GWINSZ(2) 89 | if not cr: 90 | try: 91 | fd = os.open(os.ctermid(), os.O_RDONLY) 92 | cr = ioctl_GWINSZ(fd) 93 | os.close(fd) 94 | except Exception: 95 | pass 96 | if not cr: 97 | try: 98 | cr = (env.get('LINES'), env.get('COLUMNS')) 99 | except Exception: 100 | return None 101 | return int(cr[1]), int(cr[0]) 102 | 103 | -------------------------------------------------------------------------------- /requirement.txt: -------------------------------------------------------------------------------- 1 | gevent==20.12.1 2 | requests==2.31.0 3 | progressbar2==3.53.1 4 | lxml==4.5.0 -------------------------------------------------------------------------------- /thirdlib/IPy/.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | *.swp 3 | -------------------------------------------------------------------------------- /thirdlib/IPy/AUTHORS: -------------------------------------------------------------------------------- 1 | Authors 2 | ======= 3 | 4 | Jeff Ferland 5 | > Current maintainer, versions 0.76 through latest 6 | Victor Stinner 7 | > Maintainer, versions 0.5 through 0.75 8 | Maximillian Dornseif 9 | > IPy author and maintainer until the version 0.42 10 | 11 | Contributors 12 | ============ 13 | 14 | Aras Vaichas 15 | > fix __getitem__ for IP object of size 1 16 | Arfrever Frehtes Taifersar Arahesis 17 | > IP('0.0.0.0/0') + IP('0.0.0.0/0') raises an error 18 | Bernhard Frauendienst 19 | > fix IPv6 parser 20 | James Teh 21 | > make_net option patch 22 | Jean Gillaux 23 | > fix netmask "/0.0.0.0" 24 | Mark Johnston 25 | > bringing IPy to Python 2.3 26 | Matthew Flanagan 27 | > fix strCompressed() 28 | Robert Nickel 29 | > fix setup.py bdist_rpm 30 | Samuel Krempp 31 | > fix __cmp__() bug 32 | Shell, Hin-lik Hung 33 | > bring IPy to OpenBSD ports 34 | Skinny Puppy 35 | > __add__() now always returns a value 36 | Sander Steffann (sander AT steffann.nl) 37 | > Fix the IPv6 address range descriptions 38 | > Add IPSet functions 39 | William McVey 40 | > Fix IP.__nonzero__() 41 | David 42 | > Preserve IP object in comparisons 43 | Dan McGee 44 | > Cleaner bit-twiddling code 45 | 46 | -------------------------------------------------------------------------------- /thirdlib/IPy/COPYING: -------------------------------------------------------------------------------- 1 | Copyright (c) 2006, INL 2 | Copyright (c) 2001-2005, Maximillian Dornseif 3 | All rights reserved. 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | * Redistributions in binary form must reproduce the above copyright 10 | notice, this list of conditions and the following disclaimer in the 11 | documentation and/or other materials provided with the distribution. 12 | * Neither the name of IPy nor the names of its contributors may be used 13 | to endorse or promote products derived from this software without 14 | specific prior written permission. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND ANY 17 | EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 18 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | DISCLAIMED. IN NO EVENT SHALL THE REGENTS AND CONTRIBUTORS BE LIABLE FOR ANY 20 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 21 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 22 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 23 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 24 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 25 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | 27 | -------------------------------------------------------------------------------- /thirdlib/IPy/ChangeLog: -------------------------------------------------------------------------------- 1 | Version 0.83 (2015-04-04) 2 | ------------ 3 | * Add carrier grade NAT ranges 4 | * Unbreak lots of packing systems by not having a letter in the release version 5 | 6 | Version 0.82a (2014-10-07) 7 | ------------ 8 | * Fix version numbers in files 9 | * Correct x.next() -> next(x) python3 compatability 10 | 11 | Version 0.82 (2014-10-06) 12 | ------------ 13 | 14 | * Add support for array slices 15 | * Add __and__ and isdisjoint for IPSet 16 | * Fix a bug in IPSet where contains may incorrectly return false 17 | * Added some fuzz testing 18 | 19 | Version 0.81 (2013-04-08) 20 | ------------ 21 | 22 | * Correct reverseName() for IPv6 addresses, so IP('::1').reverseName() returns correct. 23 | * Add network mask awareness to v46map() 24 | * Fix Python 3 errors in IPSet class 25 | * Make IPSet base class be object when MutableSet isn't available, fixing 26 | errors in Python 2.5 27 | 28 | Version 0.80 (2013-03-26) 29 | ------------ 30 | 31 | * Drop support of Python older than 2.4 32 | * Python 3 does not need 2to3 conversion anymore (same code base) 33 | * Fix adding of non-adjacent networks: 34 | 192.168.0.0/24 + 192.168.255.0/24 made 192.168.0.0/23 35 | * Fix adding networks that don't create a valid subnet: 36 | 192.168.1.0/24 + 192.168.2.0/24 made 192.168.1.0/23 37 | * Fix adding with an IPv6 address where .int() was < 32 bits made IPy believe it 38 | was an IPv4 address: 39 | ::ffff:0/112 + ::1:0:0/112 made 255.255.0.0/111 40 | * Add support of IPSets 41 | * Add support for subtracting a network range 42 | * Prevent IPv4 and IPv6 ranges from saying they contain each other 43 | * Add a .v46map() method to convert mapped address ranges 44 | such as IP('::ffff:192.168.1.1'); RFC 4291 45 | * Change sort order to more natural: 46 | IPv4 before IPv6; less-specific prefixes first (/0 before /32) 47 | 48 | 49 | Version 0.76 (2013-03-19) 50 | ------------------------- 51 | 52 | * ip == other and ip != other doesn't fail with an exception anymore if other 53 | is not a IP object 54 | * Add IP.get_mac() method: get the 802.3 MAC address from IPv6 RFC 2464 55 | address. 56 | * Fix IP('::/0')[0]: return an IPv6 instead of an IPv4 address 57 | 58 | Version 0.75 (2011-04-12) 59 | ------------------------- 60 | 61 | * IP('::/0').netmask() gives IP('::') instead of IP('0.0.0.0') 62 | 63 | Version 0.74 (2011-02-16) 64 | ------------------------- 65 | 66 | * Fix tests for Python 3.1 and 3.2 67 | * ip.__nonzero__() and (ipa in ipb) return a bool instead of 0 or 1 68 | * IP('0.0.0.0/0') + IP('0.0.0.0/0') raises an error, fix written by Arfrever 69 | 70 | Version 0.73 (2011-02-15) 71 | ------------------------- 72 | 73 | * Support Python 3: setup.py runs 2to3 74 | * Update the ranges for IPv6 IPs 75 | * Fix reverseName() and reverseNames() for IPv4 in IPv6 addresses 76 | * Drop support of Python < 2.5 77 | 78 | Version 0.72 (2010-11-23) 79 | ------------------------- 80 | 81 | * Include examples and MANIFEST.in in source build (add them to 82 | MANIFEST.in) 83 | * Remove __rcsid__ constant from IPy module 84 | 85 | Version 0.71 (2010-10-01) 86 | ------------------------- 87 | 88 | * Use xrange() instead of range() 89 | * Use isinstance(x, int) instead of type(x) == types.IntType 90 | * Prepare support of Python3 (use integer division: x // y) 91 | * Fix IP(long) constructor: ensure that the address is not too large 92 | * Constructor raise a TypeError if the type is not int, long, 93 | str or unicode 94 | * 223.0.0.0/8 is now public (belongs to APNIC) 95 | 96 | Version 0.70 (2009-10-29) 97 | ------------------------- 98 | 99 | * New "major" version because it may break compatibility 100 | * Fix __cmp__(): IP('0.0.0.0/0') and IP('0.0.0.0') are not equal 101 | * Fix IP.net() of the network "::/0": "::" instead of "0.0.0.0". 102 | IPy 0.63 should fix this bug, but it wasn't. 103 | 104 | Version 0.64 (2009-08-19) 105 | ------------------------- 106 | 107 | * Create MANIFEST.in to fix setup.py bdist_rpm, fix by Robert Nickel 108 | 109 | Version 0.63 (2009-06-23) 110 | ------------------------- 111 | 112 | * Fix formatting of "IPv4 in IPv6" network, eg. IP('::ffff:192.168.10.0/120'), 113 | the netmask ("/120" in the example) was missing! 114 | 115 | Version 0.62 (2008-07-15) 116 | ------------------------- 117 | 118 | * Fix reverse DNS of IPv6 address: use ".ip6.arpa." suffix instead of 119 | deprecated ".ip6.int." suffix 120 | 121 | Version 0.61 (2008-06-12) 122 | ------------------------- 123 | 124 | * Patch from Aras Vaichas allowing the [-1] operator 125 | to work with an IP object of size 1. 126 | 127 | Version 0.60 (2008-05-16) 128 | ------------------------- 129 | 130 | * strCompressed() formats '::ffff:a.b.c.d' correctly 131 | * Use strCompressed() instead of strFullsize() to format IP addresses, 132 | ouput is smarter with IPv6 address 133 | * Remove check_addr_prefixlen because it generates invalid IP address 134 | 135 | -------------------------------------------------------------------------------- /thirdlib/IPy/MANIFEST.in: -------------------------------------------------------------------------------- 1 | include AUTHORS 2 | include ChangeLog 3 | include COPYING 4 | include MANIFEST.in 5 | include test_doc.py 6 | include example/confbuilder 7 | include example/confbuilder.py 8 | include test/test_IPy.py 9 | include test/test.rst 10 | 11 | -------------------------------------------------------------------------------- /thirdlib/IPy/Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: tests egg install clean 2 | 3 | PYTHON=python 4 | 5 | tests: 6 | @echo "[ run unit tests in python 2 ]" 7 | PYTHONPATH=$(PWD) $(PYTHON)2.6 test/test_IPy.py || exit $$? 8 | @echo "[ run unit tests in python 3 ]" 9 | PYTHONPATH=$(PWD) $(PYTHON)3.4 test/test_IPy.py || exit $$? 10 | @echo 11 | @echo "[ test README in python 2 ]" 12 | $(PYTHON)2.6 test_doc.py || exit $$? 13 | @echo "[ test README in python 3 ]" 14 | $(PYTHON)3.4 test_doc.py || exit $$? 15 | 16 | egg: clean 17 | $(PYTHON) setup.py sdist bdist_egg 18 | 19 | IPy.html: README 20 | rst2html README $@ --stylesheet=rest.css 21 | 22 | install: 23 | ./setup.py install 24 | 25 | clean: 26 | rm -rf IPy.html *.pyc build dist IPy.egg-info 27 | 28 | -------------------------------------------------------------------------------- /thirdlib/IPy/README: -------------------------------------------------------------------------------- 1 | IPy - class and tools for handling of IPv4 and IPv6 addresses and networks. 2 | 3 | Website: https://github.com/autocracy/python-ipy/ 4 | 5 | Presentation of the API 6 | ======================= 7 | 8 | The IP class allows a comfortable parsing and handling for most 9 | notations in use for IPv4 and IPv6 addresses and networks. It was 10 | greatly inspired by RIPE's Perl module NET::IP's interface but 11 | doesn't share the implementation. It doesn't share non-CIDR netmasks, 12 | so funky stuff like a netmask of 0xffffff0f can't be done here. 13 | 14 | >>> from IPy import IP 15 | >>> ip = IP('127.0.0.0/30') 16 | >>> for x in ip: 17 | ... print(x) 18 | ... 19 | 127.0.0.0 20 | 127.0.0.1 21 | 127.0.0.2 22 | 127.0.0.3 23 | >>> ip2 = IP('0x7f000000/30') 24 | >>> ip == ip2 25 | 1 26 | >>> ip.reverseNames() 27 | ['0.0.0.127.in-addr.arpa.', '1.0.0.127.in-addr.arpa.', '2.0.0.127.in-addr.arpa.', '3.0.0.127.in-addr.arpa.'] 28 | >>> ip.reverseName() 29 | '0-3.0.0.127.in-addr.arpa.' 30 | >>> ip.iptype() 31 | 'PRIVATE' 32 | 33 | 34 | Supports most IP address formats 35 | ================================ 36 | 37 | It can detect about a dozen different ways of expressing IP addresses 38 | and networks, parse them and distinguish between IPv4 and IPv6 addresses: 39 | 40 | >>> IP('10.0.0.0/8').version() 41 | 4 42 | >>> IP('::1').version() 43 | 6 44 | 45 | IPv4 addresses 46 | -------------- 47 | 48 | >>> print(IP(0x7f000001)) 49 | 127.0.0.1 50 | >>> print(IP('0x7f000001')) 51 | 127.0.0.1 52 | >>> print(IP('127.0.0.1')) 53 | 127.0.0.1 54 | >>> print(IP('10')) 55 | 10.0.0.0 56 | 57 | IPv6 addresses 58 | -------------- 59 | 60 | >>> print(IP('1080:0:0:0:8:800:200C:417A')) 61 | 1080::8:800:200c:417a 62 | >>> print(IP('1080::8:800:200C:417A')) 63 | 1080::8:800:200c:417a 64 | >>> print(IP('::1')) 65 | ::1 66 | >>> print(IP('::13.1.68.3')) 67 | ::d01:4403 68 | 69 | Network mask and prefixes 70 | ------------------------- 71 | 72 | >>> print(IP('127.0.0.0/8')) 73 | 127.0.0.0/8 74 | >>> print(IP('127.0.0.0/255.0.0.0')) 75 | 127.0.0.0/8 76 | >>> print(IP('127.0.0.0-127.255.255.255')) 77 | 127.0.0.0/8 78 | 79 | 80 | Derive network address 81 | =========================== 82 | 83 | IPy can transform an IP address into a network address by applying the given 84 | netmask: 85 | >>> print(IP('127.0.0.1/255.0.0.0', make_net=True)) 86 | 127.0.0.0/8 87 | 88 | This can also be done for existing IP instances: 89 | >>> print(IP('127.0.0.1').make_net('255.0.0.0')) 90 | 127.0.0.0/8 91 | 92 | 93 | Convert address to string 94 | ========================= 95 | 96 | Nearly all class methods which return a string have an optional 97 | parameter 'wantprefixlen' which controls if the prefixlen or netmask 98 | is printed. Per default the prefilen is always shown if the network 99 | contains more than one address:: 100 | 101 | wantprefixlen == 0 / None don't return anything 1.2.3.0 102 | wantprefixlen == 1 /prefix 1.2.3.0/24 103 | wantprefixlen == 2 /netmask 1.2.3.0/255.255.255.0 104 | wantprefixlen == 3 -lastip 1.2.3.0-1.2.3.255 105 | 106 | You can also change the defaults on an per-object basis by fiddling with 107 | the class members: 108 | 109 | * NoPrefixForSingleIp 110 | * WantPrefixLen 111 | 112 | Examples of string conversions: 113 | 114 | >>> IP('10.0.0.0/32').strNormal() 115 | '10.0.0.0' 116 | >>> IP('10.0.0.0/24').strNormal() 117 | '10.0.0.0/24' 118 | >>> IP('10.0.0.0/24').strNormal(0) 119 | '10.0.0.0' 120 | >>> IP('10.0.0.0/24').strNormal(1) 121 | '10.0.0.0/24' 122 | >>> IP('10.0.0.0/24').strNormal(2) 123 | '10.0.0.0/255.255.255.0' 124 | >>> IP('10.0.0.0/24').strNormal(3) 125 | '10.0.0.0-10.0.0.255' 126 | >>> ip = IP('10.0.0.0') 127 | >>> print(ip) 128 | 10.0.0.0 129 | >>> ip.NoPrefixForSingleIp = None 130 | >>> print(ip) 131 | 10.0.0.0/32 132 | >>> ip.WantPrefixLen = 3 133 | >>> print(ip) 134 | 10.0.0.0-10.0.0.0 135 | 136 | Work with multiple networks 137 | =========================== 138 | 139 | Simple addition of neighboring netblocks that can be aggregated will yield 140 | a parent network of both, but more complex range mapping and aggregation 141 | requires is available with the IPSet class which will hold any number of 142 | unique address ranges and will aggregate overlapping ranges. 143 | 144 | >>> from IPy import IP, IPSet 145 | >>> IP('10.0.0.0/22') - IP('10.0.2.0/24') 146 | IPSet([IP('10.0.0.0/23'), IP('10.0.3.0/24')]) 147 | >>> IPSet([IP('10.0.0.0/23'), IP('10.0.3.0/24'), IP('10.0.2.0/24')]) 148 | IPSet([IP('10.0.0.0/22')]) 149 | >>> s = IPSet([IP('10.0.0.0/22')]) 150 | >>> s.add(IP('192.168.1.0/29')) 151 | >>> s 152 | IPSet([IP('10.0.0.0/22'), IP('192.168.1.0/29')]) 153 | >>> s.discard(IP('192.168.1.2')) 154 | >>> s 155 | IPSet([IP('10.0.0.0/22'), IP('192.168.1.0/31'), IP('192.168.1.3'), IP('192.168.1.4/30')]) 156 | 157 | IPSet supports the `set` method `isdisjoint`: 158 | 159 | >>> s.isdisjoint(IPSet([IP('192.168.0.0/16')])) 160 | False 161 | >>> s.isdisjoint(IPSet([IP('172.16.0.0/12')])) 162 | True 163 | 164 | IPSet supports intersection: 165 | 166 | >>> s & IPSet([IP('10.0.0.0/8')]) 167 | IPSet([IP('10.0.0.0/22')]) 168 | 169 | Compatibility and links 170 | ======================= 171 | 172 | IPy 0.83 works on Python version 2.6 - 3.4. 173 | 174 | The IP module should work in Python 2.5 as long as the subtraction operation 175 | is not used. IPSet requires features of the collecitons class which appear 176 | in Python 2.6, though they can be backported. 177 | 178 | Eratta 179 | ====== 180 | 181 | When using IPv6 addresses, it is best to compare using IP().len() instead of 182 | len(IP). Addresses with an integer value > 64 bits can break the 2nd method. 183 | See http://stackoverflow.com/questions/15650878 for more info. 184 | 185 | Fuzz testing for IPSet will throw spurious errors when the IPSet module 186 | combines two smaller prefixes into a larger prefix that matches the random 187 | prefix tested against. 188 | 189 | This Python module is under BSD license: see COPYING file. 190 | 191 | Further Information might be available at: 192 | https://github.com/autocracy/python-ipy 193 | -------------------------------------------------------------------------------- /thirdlib/IPy/example/confbuilder: -------------------------------------------------------------------------------- 1 | use Net::IP; 2 | 3 | @ns=(':ns.nerxs.com',':ns.dorsch.org',':ns.c0re.jp'); 4 | 5 | print "\n# domains\n"; 6 | 7 | open(IN, ') 10 | { 11 | if(!/^#/) 12 | { 13 | s/\n//; 14 | ($domain, $owner) = split(/:/); 15 | print ("'$domain:Contact for this domain is $owner\n"); 16 | foreach (@ns) 17 | { 18 | print (".".$domain.":$_\n"); 19 | } 20 | } 21 | } 22 | 23 | 24 | print "\n# networks\n"; 25 | 26 | open(IN, ') 29 | { 30 | 31 | if(!/^#/) 32 | { 33 | s/\n//; 34 | print "# $_\n"; 35 | @nets = split(/,/); 36 | $name = shift @nets; 37 | foreach(@nets) 38 | { 39 | my $ip = new Net::IP($_) or die (Net::IP::Error()); 40 | $net=$ip->ip(); 41 | $last=$ip->last_ip(); 42 | $net =~ s/://g; 43 | $last =~ s/://g; 44 | 45 | if($ip->size() > 32) 46 | { 47 | foreach (@ns) 48 | { 49 | print (".".$ip->reverse_ip().":$_\n"); 50 | } 51 | $ip6map{$ip->ip()} = $name; 52 | } 53 | else 54 | { 55 | for($i = int($ip->intip()) + 1; $i < int($ip->intip()) + int($ip->size()); $i++) 56 | { 57 | $nmap{sprintf("%d.%d.%d.%d", ($i >> 24) & 0xff, ($i >> 16) & 0xff, ($i>>8) & 0xff, $i & 0xff)} = "$name"; 58 | $rmap{sprintf("%d.%d.%d.%d", ($i >> 24) & 0xff, ($i >> 16) & 0xff, ($i>>8) & 0xff, $i & 0xff)} = $ip->binip(). ".$name"; 59 | } 60 | } 61 | print ("=net.$name:".$net."\n"); 62 | print ("=broadcast.$name:".$last."\n"); 63 | #print ("Bin : ".$ip->binip()."\n"); 64 | #print ("Mask: ".$ip->mask()."\n"); 65 | #print ("Size: ".$ip->size()."\n"); 66 | #print ("Type: ".$ip->iptype()."\n"); 67 | } 68 | } 69 | } 70 | 71 | close(IN); 72 | 73 | print "\n# hosts\n"; 74 | 75 | open(IN, ') 78 | { 79 | s/\n//; 80 | if(!/^#/) 81 | { 82 | if(!/^-/) 83 | { 84 | if(/^=/ || /^\+/) 85 | { 86 | @i = split(':'); 87 | $rmap{$i[1]} = ''; 88 | } 89 | print "$_\n"; 90 | } 91 | else 92 | { 93 | @fields = split(/\|/); 94 | $name = shift(@fields); 95 | $name =~ s/^.(.)/$1/; 96 | $_ = shift(@fields); 97 | @ips = split(/,/); 98 | $_ = shift(@fields); 99 | @aliases = split(/,/); 100 | $admin = shift(@fields); 101 | if(!$admin) 102 | { 103 | $admin = 'technik@c0re.23.nu'; 104 | } 105 | $_ = shift(@fields); 106 | @mxes = split(/,/); 107 | foreach(@ips) 108 | { 109 | my $ip = new Net::IP($_) or die (Net::IP::Error()); 110 | if(length($ip->binip()) == 32) 111 | { 112 | # IPv4 is easy 113 | if(!$nmap{$_}) 114 | { 115 | print STDERR "*** warning: no network for $_ ($name) - ignoring\n"; 116 | print "# no network for $_ ($name) - ignoring\n"; 117 | } 118 | else 119 | { 120 | print "=$name." . $nmap{$_} . ":$_\n"; 121 | $rmap{$_} = ''; 122 | print "'$name." . $nmap{$_} . ":Host contact is $admin\n"; 123 | } 124 | foreach(@aliases) 125 | { 126 | print "+$_:".$ip->ip()."\n"; 127 | print "'$_:Host contact is $admin\n"; 128 | } 129 | } 130 | else 131 | { 132 | #IPv6 here 133 | $net = $ip->ip(); 134 | $net =~ s/^(....:....:....:....).*/$1:0000:0000:0000:0000/; 135 | if(!$ip6map{$net}) 136 | { 137 | print STDERR "*** warning: no network for $_ ($name) - ignoring\n"; 138 | print "# no network for $_ ($name) - ignoring\n"; 139 | } 140 | else 141 | { 142 | $rip = $ip->ip(); 143 | $rip =~ s/://g; 144 | print "6$name." . $ip6map{$net} . ":".$rip."\n"; 145 | } 146 | foreach(@aliases) 147 | { 148 | $rip = $ip->ip(); 149 | $rip =~ s/://g; 150 | print "3$_:".$rip."\n"; 151 | } 152 | } 153 | 154 | } 155 | } 156 | } 157 | } 158 | 159 | close(IN); 160 | 161 | 162 | print "\n# reverse lookup\n"; 163 | foreach(sort(keys(%nmap))) 164 | { 165 | if($rmap{$_}) 166 | { 167 | print "=".$rmap{$_}.":$_\n"; 168 | } 169 | } 170 | -------------------------------------------------------------------------------- /thirdlib/IPy/example/confbuilder.py: -------------------------------------------------------------------------------- 1 | # This is a hack I use to generate my tinydns configuration 2 | # It serves as e test for converting from Perl Net::IP to 3 | # Python and IPy 4 | 5 | # Further Information might be available at http://c0re.jp/c0de/IPy/ 6 | # Hacked 2001 by drt@un.bewaff.net 7 | 8 | import sys 9 | sys.path.append('..') 10 | 11 | import IPy 12 | 13 | 14 | ns = {'ns.nerxs.com': '213.221.113.70', 15 | 'ns.dorsch.org': '195.143.234.25', 16 | 'ns.c0re.jp': '217.6.214.130'} 17 | 18 | print "# *** nameservers ***" 19 | for x in ns.keys(): 20 | print "=%s:%s" % (x, ns[x]) 21 | 22 | print "\n# *** domains ***" 23 | 24 | fd = open('domains') 25 | 26 | for x in fd.readlines(): 27 | if x[0] != '#': 28 | if x[-1] == '\n': 29 | x = x[:-1] 30 | (domain, owner) = x.split(':') 31 | print "'%s:Contact for this domain is %s" % (domain, owner) 32 | for y in ns.keys(): 33 | print ".%s::%s" % (domain, y) 34 | 35 | fd.close() 36 | 37 | print "\n# *** Networks ***" 38 | 39 | fd = open('networks') 40 | ip6map = {} 41 | rmap = {} 42 | nmap = {} 43 | 44 | for x in fd.readlines(): 45 | if x[-1] == '\n': 46 | x = x[:-1] 47 | if len(x) > 0 and x[0] != '#': 48 | nets = x.split(',') 49 | name = nets.pop(0) 50 | print "# Network: %s" % name 51 | for y in nets: 52 | ip = IPy.IP(y) 53 | print "# Address range: %s (%s), %d addresses" % (ip.strCompressed(), ip.iptype(), ip.len()) 54 | print "=net.%s:%s" % (name, ip.net()) 55 | print "=broadcast.%s:%s" % (name, ip.broadcast()) 56 | 57 | if ip.version() == 4: 58 | for z in ip: 59 | # TODO reverse? 60 | nmap[z.int()] = name 61 | rmap[z.int()] = z.strBin() + "." + name 62 | else: 63 | # IPv6 64 | for z in ns.keys(): 65 | for v in ip.reverseName(): 66 | print ".%s::%s" % (v, z) 67 | ip6map[ip.strFullsize(0)] = name 68 | 69 | fd.close() 70 | 71 | print "\n# *** hosts ***" 72 | 73 | fd = open('hosts') 74 | 75 | for x in fd.readlines(): 76 | if x[-1] == '\n': 77 | x = x[:-1] 78 | if x != '' and x[0] != '#': 79 | if "@Z'.".find(x[0]) >= 0: 80 | print x 81 | else: 82 | if "=+'".find(x[0]) >= 0: 83 | i = x.split(':') 84 | rmap[IPy.IP(i[1]).int()] = '' 85 | print x 86 | else: 87 | x = x[1:] 88 | x += '||||' 89 | fields = x.split('|') 90 | name = fields.pop(0) 91 | if name[0] == '.': 92 | name = name[1:] 93 | v = fields.pop(0) 94 | ips = v.split(',') 95 | v = fields.pop(0) 96 | aliases = v.split(',') 97 | if aliases == ['']: 98 | aliases = [] 99 | admin = fields.pop() 100 | if admin == '': 101 | admin = 'technik@c0re.23.nu' 102 | v = fields.pop() 103 | mxes = v.split(',') 104 | if mxes == ['']: 105 | mxes = [] 106 | for y in ips: 107 | ip = IPy.IP(y) 108 | if ip.version() == 4: 109 | # IPv4 is easy 110 | if not nmap.has_key(ip.int()): 111 | print >>sys.stderr, "*** warning: no network for %s (%s) - ignoring" % (y, name) 112 | print "# no network for %s (%s)" % (y, name) 113 | else: 114 | print "=%s.%s:%s" % (name, nmap[ip.int()], y) 115 | print "'%s.%s:Host contact is %s" % (name, nmap[ip.int()], admin) 116 | rmap[ip.int()] = '' 117 | for z in aliases: 118 | print "+%s:%s" % (z, ip) 119 | print "'%s:Host contact is %s" % (z, admin) 120 | else: 121 | #IPv6 here 122 | net = ip.strFullsize(0) 123 | net = net[:19] + ':0000:0000:0000:0000' 124 | if ip6map.has_key(net): 125 | print >>sys.stderr, "*** warning: no network for %s (%s) - ignoring" % (ip, name) 126 | print "# no network for %s (%s) - ignoring" % (ip, name) 127 | else: 128 | print "6%s.%s:%s"; (name, ip6map[net], ip.strHex()[2:]) 129 | for z in aliases: 130 | print "3%s:%s" % (name, ip.strHex()[2:]) 131 | print "'%s:Host contact is %s" % (name, admin) 132 | 133 | fd.close() 134 | 135 | print "\n# *** reverse lookup ***" 136 | k = nmap.keys() 137 | k.sort() 138 | for x in k: 139 | if rmap.has_key(x) and rmap[x] != '': 140 | print "=%s:%s" % (rmap[x], str(IPy.IP(x))) 141 | 142 | -------------------------------------------------------------------------------- /thirdlib/IPy/setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | # Release process: 4 | # 5 | # - set version in IPy.py 6 | # - set version in setup.py 7 | # - run unit test: make 8 | # - run unit test: make PYTHON=python3 9 | # - set release date in ChangeLog 10 | # - git commit -a 11 | # - git tag -a IPy-x.y -m "tag IPy x.y" 12 | # - git push 13 | # - git push --tags 14 | # - python setup.py register sdist upload 15 | # - update the website 16 | # 17 | # After the release: 18 | # - set version to n+1 (IPy.py and setup.py) 19 | # - add a new empty section in the changelog for version n+1 20 | # - git commit -a 21 | # - git push 22 | 23 | from __future__ import with_statement 24 | import sys 25 | from distutils.core import setup 26 | 27 | VERSION = '0.83' 28 | 29 | options = {} 30 | 31 | with open('README') as fp: 32 | README = fp.read().strip() + "\n\n" 33 | 34 | ChangeLog = ( 35 | "What's new\n" 36 | "==========\n" 37 | "\n") 38 | with open('ChangeLog') as fp: 39 | ChangeLog += fp.read().strip() 40 | 41 | LONG_DESCRIPTION = README + ChangeLog 42 | CLASSIFIERS = [ 43 | 'Development Status :: 5 - Production/Stable', 44 | 'Intended Audience :: Developers', 45 | 'Intended Audience :: System Administrators', 46 | 'Environment :: Plugins', 47 | 'Topic :: Software Development :: Libraries :: Python Modules', 48 | 'Topic :: Communications', 49 | 'Topic :: Internet', 50 | 'Topic :: System :: Networking', 51 | 'License :: OSI Approved :: BSD License', 52 | 'Operating System :: OS Independent', 53 | 'Natural Language :: English', 54 | 'Programming Language :: Python', 55 | 'Programming Language :: Python :: 3', 56 | ] 57 | URL = "https://github.com/autocracy/python-ipy" 58 | 59 | setup( 60 | name="IPy", 61 | version=VERSION, 62 | description="Class and tools for handling of IPv4 and IPv6 addresses and networks", 63 | long_description=LONG_DESCRIPTION, 64 | author="Maximillian Dornseif", 65 | maintainer="Jeff Ferland", 66 | maintainer_email="jeff AT storyinmemo.com", 67 | license="BSD License", 68 | keywords="ipv4 ipv6 netmask", 69 | url=URL, 70 | download_url=URL, 71 | classifiers= CLASSIFIERS, 72 | py_modules=["IPy"], 73 | **options 74 | ) 75 | 76 | -------------------------------------------------------------------------------- /thirdlib/IPy/test/test.rst: -------------------------------------------------------------------------------- 1 | Non regression tests 2 | ==================== 3 | 4 | >>> from IPy import IP 5 | >>> IP('::ffff:1.2.3.4').strCompressed() 6 | '::ffff:1.2.3.4' 7 | >>> IP('::ffff:192.168.10.0/120').strCompressed() 8 | '::ffff:192.168.10.0/120' 9 | >>> IP('::ffff:192.168.10.42/120', make_net=True).strCompressed() 10 | '::ffff:192.168.10.0/120' 11 | >>> IP('::/0', make_net=True).net() 12 | IP('::') 13 | >>> IP('0.0.0.0/0') + IP('0.0.0.0/0') 14 | Traceback (most recent call last): 15 | ... 16 | ValueError: Networks with a prefixlen longer than /1 can't be added. 17 | 18 | 19 | Compare 0.0.0.0/0 and ::/0 bug 20 | ============================== 21 | 22 | >>> IP('0.0.0.0/0') < IP('::/0') 23 | True 24 | >>> IP('0.0.0.0/0') > IP('::/0') 25 | False 26 | >>> IP('0.0.0.0/0') == IP('::/0') 27 | False 28 | 29 | >>> d={} 30 | >>> d[IP('0.0.0.0/0')] = 1 31 | >>> d[IP('::/0')] = 2 32 | >>> d 33 | {IP('::/0'): 2, IP('0.0.0.0/0'): 1} 34 | 35 | >>> addresses = [IP('0.0.0.0/16'), IP('::7'), IP('::3'), IP('::0'), 36 | ... IP('0.0.0.0'), IP('0.0.0.3'), IP('0.0.0.0/0'), IP('::/0')] 37 | ... 38 | >>> addresses.sort() 39 | >>> addresses 40 | [IP('0.0.0.0/0'), IP('0.0.0.0/16'), IP('0.0.0.0'), IP('0.0.0.3'), IP('::/0'), IP('::'), IP('::3'), IP('::7')] 41 | 42 | >>> IP('::/0').netmask() 43 | IP('::') 44 | 45 | 46 | IP types 47 | ======== 48 | 49 | >>> IP('10.8.3.0/24').iptype() 50 | 'PRIVATE' 51 | >>> IP('88.164.127.124').iptype() 52 | 'PUBLIC' 53 | >>> IP('223.0.0.0/8').iptype() 54 | 'PUBLIC' 55 | >>> IP('224.0.0.0/8').iptype() 56 | 'RESERVED' 57 | 58 | Reverse name 59 | ============ 60 | 61 | >>> IP('::ffff:193.0.1.208').reverseName() 62 | '208.1.0.193.in-addr.arpa.' 63 | >>> IP('::ffff:193.0.1.208').reverseNames() 64 | ['208.1.0.193.in-addr.arpa.'] 65 | >>> IP('128.0.0.0/7').reverseName() 66 | '128-255..in-addr.arpa.' 67 | >>> IP('128.0.0.0/7').reverseNames() 68 | ['128.in-addr.arpa.', '129.in-addr.arpa.'] 69 | >>> IP('::ffff:128.0.0.0/103').reverseName() == IP('128.0.0.0/7').reverseName() 70 | True 71 | >>> IP('::ffff:128.0.0.0/103').reverseNames() == IP('128.0.0.0/7').reverseNames() 72 | True 73 | 74 | Issue #2 and #9 75 | =============== 76 | 77 | >>> IP('1.2.3.4') == None 78 | False 79 | >>> IP('1.2.3.4') == object() 80 | False 81 | >>> IP('1.2.3.4') != None 82 | True 83 | >>> IP('1.2.3.4') != object() 84 | True 85 | 86 | 87 | get_mac() 88 | ========= 89 | 90 | >>> IP('fe80::f66d:04ff:fe47:2fae').get_mac() 91 | 'f4:6d:04:47:2f:ae' 92 | >>> IP('2001:DB8::212:7FFF:FEEB:6B40').get_mac() 93 | '00:12:7f:eb:6b:40' 94 | >>> IP('::1').get_mac() is None 95 | True 96 | >>> IP('1.2.3.4').get_mac() is None 97 | True 98 | 99 | Issue #12: IPv6[index] -> IPv4 100 | ============================== 101 | 102 | >>> IP('1.2.3.0/24')[4] 103 | IP('1.2.3.4') 104 | >>> IP('::/0')[1] 105 | IP('::1') 106 | 107 | -------------------------------------------------------------------------------- /thirdlib/IPy/test/test_fuzz.py: -------------------------------------------------------------------------------- 1 | """Fuzing for IPy.py""" 2 | 3 | # TODO: unify assert / FilIf usage 4 | 5 | import sys 6 | import functools 7 | import itertools 8 | sys.path.append('.') 9 | sys.path.append('..') 10 | 11 | import IPy 12 | import unittest 13 | import random 14 | 15 | if sys.version_info >= (3,): 16 | xrange = range 17 | 18 | # on Python-2.7 and higher, we use load_tests to multiply out the test cases so that unittest 19 | # represents each as an individual test case. 20 | def iterate_27(n): 21 | def wrap(func): 22 | func.iterations = n 23 | return func 24 | return wrap 25 | 26 | def load_tests(loader, tests, pattern): 27 | def expand(tests): 28 | if isinstance(tests, unittest.TestCase): 29 | method_name = tests._testMethodName 30 | meth = getattr(tests, method_name) 31 | if hasattr(meth, 'iterations'): 32 | tests = unittest.TestSuite(type(tests)(method_name) for i in xrange(meth.iterations)) 33 | else: 34 | tests = unittest.TestSuite(expand(t) for t in tests) 35 | return tests 36 | return expand(tests) 37 | 38 | # On older Pythons, we run the requisite iterations directly, in a single test case. 39 | def iterate_old(n): 40 | def wrap(func): 41 | @functools.wraps(func) 42 | def replacement(*args): 43 | for i in xrange(n): 44 | func(*args) 45 | return replacement 46 | return wrap 47 | 48 | if sys.version_info >= (2,7): 49 | iterate = iterate_27 50 | else: 51 | iterate = iterate_old 52 | 53 | # utilities 54 | 55 | def random_ipv4_prefix(): 56 | prefixlen = random.randrange(32) 57 | int_ip = random.randrange(IPy.MAX_IPV4_ADDRESS) 58 | int_ip &= 0xffffffff << (32-prefixlen) 59 | return IPy.IP('.'.join(map(str, (int_ip >> 24, 60 | (int_ip >> 16) & 0xff, 61 | (int_ip >> 8) & 0xff, 62 | int_ip & 0xff))) 63 | + '/%d' % prefixlen) 64 | 65 | # tests 66 | 67 | class ParseAndBack(unittest.TestCase): 68 | 69 | @iterate(500) 70 | def testRandomValuesv4(self): 71 | question = random.randrange(0xffffffff) 72 | self.assertEqual(IPy.parseAddress(IPy.intToIp(question, 4)), (question, 4), hex(question)) 73 | 74 | @iterate(500) 75 | def testRandomValuesv6(self): 76 | question = random.randrange(0xffffffffffffffffffffffffffffffff) 77 | self.assertEqual(IPy.parseAddress(IPy.intToIp(question, 6)), (question, 6), hex(question)) 78 | 79 | class TestIPSet(unittest.TestCase): 80 | 81 | @iterate(1000) 82 | def testRandomContains(self): 83 | prefixes = [random_ipv4_prefix() for i in xrange(random.randrange(50))] 84 | question = random_ipv4_prefix() 85 | answer = any(question in pfx for pfx in prefixes) 86 | ipset = IPy.IPSet(prefixes) 87 | self.assertEqual(question in ipset, answer, 88 | "%s in %s != %s (made from %s)" % (question, ipset, answer, prefixes)) 89 | 90 | 91 | @iterate(1000) 92 | def testRandomDisjoint(self): 93 | prefixes1 = [random_ipv4_prefix() for i in xrange(random.randrange(50))] 94 | prefixes2 = [random_ipv4_prefix() for i in xrange(random.randrange(50))] 95 | # test disjointnes the stupid way 96 | disjoint = True 97 | for p1, p2 in itertools.product(prefixes1, prefixes2): 98 | if p1 in p2 or p2 in p1: 99 | disjoint = False 100 | break 101 | ipset1 = IPy.IPSet(prefixes1) 102 | ipset2 = IPy.IPSet(prefixes2) 103 | self.assertEqual(ipset1.isdisjoint(ipset2), disjoint, 104 | "%s.isdisjoint(%s) != %s" % (ipset1, ipset2, disjoint)) 105 | self.assertEqual(ipset2.isdisjoint(ipset1), disjoint, 106 | "%s.isdisjoint(%s) != %s" % (ipset2, ipset1, disjoint)) 107 | 108 | if __name__ == "__main__": 109 | unittest.main() 110 | -------------------------------------------------------------------------------- /thirdlib/IPy/test_doc.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import doctest 3 | import sys 4 | if hasattr(doctest, "testfile"): 5 | total_failures, total_tests = (0, 0) 6 | 7 | print("=== Test file: README ===") 8 | failure, tests = doctest.testfile('README', optionflags=doctest.ELLIPSIS) 9 | total_failures += failure 10 | total_tests += tests 11 | 12 | print("=== Test file: test.rst ===") 13 | failure, tests = doctest.testfile('test/test.rst', optionflags=doctest.ELLIPSIS) 14 | total_failures += failure 15 | total_tests += tests 16 | 17 | print("=== Test IPy module ===") 18 | import IPy 19 | failure, tests = doctest.testmod(IPy) 20 | total_failures += failure 21 | total_tests += tests 22 | 23 | print("=== Overall Results ===") 24 | print("total tests %d, failures %d" % (total_tests, total_failures)) 25 | if total_failures: 26 | sys.exit(1) 27 | else: 28 | sys.stderr.write("WARNING: doctest has no function testfile (before Python 2.4), unable to check README\n") 29 | 30 | -------------------------------------------------------------------------------- /thirdlib/colorama/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright Jonathan Hartley 2013. BSD 3-Clause license, see LICENSE file. 2 | from .initialise import init, deinit, reinit, colorama_text 3 | from .ansi import Fore, Back, Style, Cursor 4 | from .ansitowin32 import AnsiToWin32 5 | 6 | __version__ = '0.4.1' 7 | -------------------------------------------------------------------------------- /thirdlib/colorama/ansi.py: -------------------------------------------------------------------------------- 1 | # Copyright Jonathan Hartley 2013. BSD 3-Clause license, see LICENSE file. 2 | ''' 3 | This module generates ANSI character codes to printing colors to terminals. 4 | See: http://en.wikipedia.org/wiki/ANSI_escape_code 5 | ''' 6 | 7 | CSI = '\033[' 8 | OSC = '\033]' 9 | BEL = '\007' 10 | 11 | 12 | def code_to_chars(code): 13 | return CSI + str(code) + 'm' 14 | 15 | def set_title(title): 16 | return OSC + '2;' + title + BEL 17 | 18 | def clear_screen(mode=2): 19 | return CSI + str(mode) + 'J' 20 | 21 | def clear_line(mode=2): 22 | return CSI + str(mode) + 'K' 23 | 24 | 25 | class AnsiCodes(object): 26 | def __init__(self): 27 | # the subclasses declare class attributes which are numbers. 28 | # Upon instantiation we define instance attributes, which are the same 29 | # as the class attributes but wrapped with the ANSI escape sequence 30 | for name in dir(self): 31 | if not name.startswith('_'): 32 | value = getattr(self, name) 33 | setattr(self, name, code_to_chars(value)) 34 | 35 | 36 | class AnsiCursor(object): 37 | def UP(self, n=1): 38 | return CSI + str(n) + 'A' 39 | def DOWN(self, n=1): 40 | return CSI + str(n) + 'B' 41 | def FORWARD(self, n=1): 42 | return CSI + str(n) + 'C' 43 | def BACK(self, n=1): 44 | return CSI + str(n) + 'D' 45 | def POS(self, x=1, y=1): 46 | return CSI + str(y) + ';' + str(x) + 'H' 47 | 48 | 49 | class AnsiFore(AnsiCodes): 50 | BLACK = 30 51 | RED = 31 52 | GREEN = 32 53 | YELLOW = 33 54 | BLUE = 34 55 | MAGENTA = 35 56 | CYAN = 36 57 | WHITE = 37 58 | RESET = 39 59 | 60 | # These are fairly well supported, but not part of the standard. 61 | LIGHTBLACK_EX = 90 62 | LIGHTRED_EX = 91 63 | LIGHTGREEN_EX = 92 64 | LIGHTYELLOW_EX = 93 65 | LIGHTBLUE_EX = 94 66 | LIGHTMAGENTA_EX = 95 67 | LIGHTCYAN_EX = 96 68 | LIGHTWHITE_EX = 97 69 | 70 | 71 | class AnsiBack(AnsiCodes): 72 | BLACK = 40 73 | RED = 41 74 | GREEN = 42 75 | YELLOW = 43 76 | BLUE = 44 77 | MAGENTA = 45 78 | CYAN = 46 79 | WHITE = 47 80 | RESET = 49 81 | 82 | # These are fairly well supported, but not part of the standard. 83 | LIGHTBLACK_EX = 100 84 | LIGHTRED_EX = 101 85 | LIGHTGREEN_EX = 102 86 | LIGHTYELLOW_EX = 103 87 | LIGHTBLUE_EX = 104 88 | LIGHTMAGENTA_EX = 105 89 | LIGHTCYAN_EX = 106 90 | LIGHTWHITE_EX = 107 91 | 92 | 93 | class AnsiStyle(AnsiCodes): 94 | BRIGHT = 1 95 | DIM = 2 96 | NORMAL = 22 97 | RESET_ALL = 0 98 | 99 | Fore = AnsiFore() 100 | Back = AnsiBack() 101 | Style = AnsiStyle() 102 | Cursor = AnsiCursor() 103 | -------------------------------------------------------------------------------- /thirdlib/colorama/ansitowin32.py: -------------------------------------------------------------------------------- 1 | # Copyright Jonathan Hartley 2013. BSD 3-Clause license, see LICENSE file. 2 | import re 3 | import sys 4 | import os 5 | 6 | from .ansi import AnsiFore, AnsiBack, AnsiStyle, Style 7 | from .winterm import WinTerm, WinColor, WinStyle 8 | from .win32 import windll, winapi_test 9 | 10 | 11 | winterm = None 12 | if windll is not None: 13 | winterm = WinTerm() 14 | 15 | 16 | class StreamWrapper(object): 17 | ''' 18 | Wraps a stream (such as stdout), acting as a transparent proxy for all 19 | attribute access apart from method 'write()', which is delegated to our 20 | Converter instance. 21 | ''' 22 | def __init__(self, wrapped, converter): 23 | # double-underscore everything to prevent clashes with names of 24 | # attributes on the wrapped stream object. 25 | self.__wrapped = wrapped 26 | self.__convertor = converter 27 | 28 | def __getattr__(self, name): 29 | return getattr(self.__wrapped, name) 30 | 31 | def __enter__(self, *args, **kwargs): 32 | # special method lookup bypasses __getattr__/__getattribute__, see 33 | # https://stackoverflow.com/questions/12632894/why-doesnt-getattr-work-with-exit 34 | # thus, contextlib magic methods are not proxied via __getattr__ 35 | return self.__wrapped.__enter__(*args, **kwargs) 36 | 37 | def __exit__(self, *args, **kwargs): 38 | return self.__wrapped.__exit__(*args, **kwargs) 39 | 40 | def write(self, text): 41 | self.__convertor.write(text) 42 | 43 | def isatty(self): 44 | stream = self.__wrapped 45 | if 'PYCHARM_HOSTED' in os.environ: 46 | if stream is not None and (stream is sys.__stdout__ or stream is sys.__stderr__): 47 | return True 48 | try: 49 | stream_isatty = stream.isatty 50 | except AttributeError: 51 | return False 52 | else: 53 | return stream_isatty() 54 | 55 | @property 56 | def closed(self): 57 | stream = self.__wrapped 58 | try: 59 | return stream.closed 60 | except AttributeError: 61 | return True 62 | 63 | 64 | class AnsiToWin32(object): 65 | ''' 66 | Implements a 'write()' method which, on Windows, will strip ANSI character 67 | sequences from the text, and if outputting to a tty, will convert them into 68 | win32 function calls. 69 | ''' 70 | ANSI_CSI_RE = re.compile('\001?\033\\[((?:\\d|;)*)([a-zA-Z])\002?') # Control Sequence Introducer 71 | ANSI_OSC_RE = re.compile('\001?\033\\]((?:.|;)*?)(\x07)\002?') # Operating System Command 72 | 73 | def __init__(self, wrapped, convert=None, strip=None, autoreset=False): 74 | # The wrapped stream (normally sys.stdout or sys.stderr) 75 | self.wrapped = wrapped 76 | 77 | # should we reset colors to defaults after every .write() 78 | self.autoreset = autoreset 79 | 80 | # create the proxy wrapping our output stream 81 | self.stream = StreamWrapper(wrapped, self) 82 | 83 | on_windows = os.name == 'nt' 84 | # We test if the WinAPI works, because even if we are on Windows 85 | # we may be using a terminal that doesn't support the WinAPI 86 | # (e.g. Cygwin Terminal). In this case it's up to the terminal 87 | # to support the ANSI codes. 88 | conversion_supported = on_windows and winapi_test() 89 | 90 | # should we strip ANSI sequences from our output? 91 | if strip is None: 92 | strip = conversion_supported or (not self.stream.closed and not self.stream.isatty()) 93 | self.strip = strip 94 | 95 | # should we should convert ANSI sequences into win32 calls? 96 | if convert is None: 97 | convert = conversion_supported and not self.stream.closed and self.stream.isatty() 98 | self.convert = convert 99 | 100 | # dict of ansi codes to win32 functions and parameters 101 | self.win32_calls = self.get_win32_calls() 102 | 103 | # are we wrapping stderr? 104 | self.on_stderr = self.wrapped is sys.stderr 105 | 106 | def should_wrap(self): 107 | ''' 108 | True if this class is actually needed. If false, then the output 109 | stream will not be affected, nor will win32 calls be issued, so 110 | wrapping stdout is not actually required. This will generally be 111 | False on non-Windows platforms, unless optional functionality like 112 | autoreset has been requested using kwargs to init() 113 | ''' 114 | return self.convert or self.strip or self.autoreset 115 | 116 | def get_win32_calls(self): 117 | if self.convert and winterm: 118 | return { 119 | AnsiStyle.RESET_ALL: (winterm.reset_all, ), 120 | AnsiStyle.BRIGHT: (winterm.style, WinStyle.BRIGHT), 121 | AnsiStyle.DIM: (winterm.style, WinStyle.NORMAL), 122 | AnsiStyle.NORMAL: (winterm.style, WinStyle.NORMAL), 123 | AnsiFore.BLACK: (winterm.fore, WinColor.BLACK), 124 | AnsiFore.RED: (winterm.fore, WinColor.RED), 125 | AnsiFore.GREEN: (winterm.fore, WinColor.GREEN), 126 | AnsiFore.YELLOW: (winterm.fore, WinColor.YELLOW), 127 | AnsiFore.BLUE: (winterm.fore, WinColor.BLUE), 128 | AnsiFore.MAGENTA: (winterm.fore, WinColor.MAGENTA), 129 | AnsiFore.CYAN: (winterm.fore, WinColor.CYAN), 130 | AnsiFore.WHITE: (winterm.fore, WinColor.GREY), 131 | AnsiFore.RESET: (winterm.fore, ), 132 | AnsiFore.LIGHTBLACK_EX: (winterm.fore, WinColor.BLACK, True), 133 | AnsiFore.LIGHTRED_EX: (winterm.fore, WinColor.RED, True), 134 | AnsiFore.LIGHTGREEN_EX: (winterm.fore, WinColor.GREEN, True), 135 | AnsiFore.LIGHTYELLOW_EX: (winterm.fore, WinColor.YELLOW, True), 136 | AnsiFore.LIGHTBLUE_EX: (winterm.fore, WinColor.BLUE, True), 137 | AnsiFore.LIGHTMAGENTA_EX: (winterm.fore, WinColor.MAGENTA, True), 138 | AnsiFore.LIGHTCYAN_EX: (winterm.fore, WinColor.CYAN, True), 139 | AnsiFore.LIGHTWHITE_EX: (winterm.fore, WinColor.GREY, True), 140 | AnsiBack.BLACK: (winterm.back, WinColor.BLACK), 141 | AnsiBack.RED: (winterm.back, WinColor.RED), 142 | AnsiBack.GREEN: (winterm.back, WinColor.GREEN), 143 | AnsiBack.YELLOW: (winterm.back, WinColor.YELLOW), 144 | AnsiBack.BLUE: (winterm.back, WinColor.BLUE), 145 | AnsiBack.MAGENTA: (winterm.back, WinColor.MAGENTA), 146 | AnsiBack.CYAN: (winterm.back, WinColor.CYAN), 147 | AnsiBack.WHITE: (winterm.back, WinColor.GREY), 148 | AnsiBack.RESET: (winterm.back, ), 149 | AnsiBack.LIGHTBLACK_EX: (winterm.back, WinColor.BLACK, True), 150 | AnsiBack.LIGHTRED_EX: (winterm.back, WinColor.RED, True), 151 | AnsiBack.LIGHTGREEN_EX: (winterm.back, WinColor.GREEN, True), 152 | AnsiBack.LIGHTYELLOW_EX: (winterm.back, WinColor.YELLOW, True), 153 | AnsiBack.LIGHTBLUE_EX: (winterm.back, WinColor.BLUE, True), 154 | AnsiBack.LIGHTMAGENTA_EX: (winterm.back, WinColor.MAGENTA, True), 155 | AnsiBack.LIGHTCYAN_EX: (winterm.back, WinColor.CYAN, True), 156 | AnsiBack.LIGHTWHITE_EX: (winterm.back, WinColor.GREY, True), 157 | } 158 | return dict() 159 | 160 | def write(self, text): 161 | if self.strip or self.convert: 162 | self.write_and_convert(text) 163 | else: 164 | self.wrapped.write(text) 165 | self.wrapped.flush() 166 | if self.autoreset: 167 | self.reset_all() 168 | 169 | 170 | def reset_all(self): 171 | if self.convert: 172 | self.call_win32('m', (0,)) 173 | elif not self.strip and not self.stream.closed: 174 | self.wrapped.write(Style.RESET_ALL) 175 | 176 | 177 | def write_and_convert(self, text): 178 | ''' 179 | Write the given text to our wrapped stream, stripping any ANSI 180 | sequences from the text, and optionally converting them into win32 181 | calls. 182 | ''' 183 | cursor = 0 184 | text = self.convert_osc(text) 185 | for match in self.ANSI_CSI_RE.finditer(text): 186 | start, end = match.span() 187 | self.write_plain_text(text, cursor, start) 188 | self.convert_ansi(*match.groups()) 189 | cursor = end 190 | self.write_plain_text(text, cursor, len(text)) 191 | 192 | 193 | def write_plain_text(self, text, start, end): 194 | if start < end: 195 | self.wrapped.write(text[start:end]) 196 | self.wrapped.flush() 197 | 198 | 199 | def convert_ansi(self, paramstring, command): 200 | if self.convert: 201 | params = self.extract_params(command, paramstring) 202 | self.call_win32(command, params) 203 | 204 | 205 | def extract_params(self, command, paramstring): 206 | if command in 'Hf': 207 | params = tuple(int(p) if len(p) != 0 else 1 for p in paramstring.split(';')) 208 | while len(params) < 2: 209 | # defaults: 210 | params = params + (1,) 211 | else: 212 | params = tuple(int(p) for p in paramstring.split(';') if len(p) != 0) 213 | if len(params) == 0: 214 | # defaults: 215 | if command in 'JKm': 216 | params = (0,) 217 | elif command in 'ABCD': 218 | params = (1,) 219 | 220 | return params 221 | 222 | 223 | def call_win32(self, command, params): 224 | if command == 'm': 225 | for param in params: 226 | if param in self.win32_calls: 227 | func_args = self.win32_calls[param] 228 | func = func_args[0] 229 | args = func_args[1:] 230 | kwargs = dict(on_stderr=self.on_stderr) 231 | func(*args, **kwargs) 232 | elif command in 'J': 233 | winterm.erase_screen(params[0], on_stderr=self.on_stderr) 234 | elif command in 'K': 235 | winterm.erase_line(params[0], on_stderr=self.on_stderr) 236 | elif command in 'Hf': # cursor position - absolute 237 | winterm.set_cursor_position(params, on_stderr=self.on_stderr) 238 | elif command in 'ABCD': # cursor position - relative 239 | n = params[0] 240 | # A - up, B - down, C - forward, D - back 241 | x, y = {'A': (0, -n), 'B': (0, n), 'C': (n, 0), 'D': (-n, 0)}[command] 242 | winterm.cursor_adjust(x, y, on_stderr=self.on_stderr) 243 | 244 | 245 | def convert_osc(self, text): 246 | for match in self.ANSI_OSC_RE.finditer(text): 247 | start, end = match.span() 248 | text = text[:start] + text[end:] 249 | paramstring, command = match.groups() 250 | if command in '\x07': # \x07 = BEL 251 | params = paramstring.split(";") 252 | # 0 - change title and icon (we will only change title) 253 | # 1 - change icon (we don't support this) 254 | # 2 - change title 255 | if params[0] in '02': 256 | winterm.set_title(params[1]) 257 | return text 258 | -------------------------------------------------------------------------------- /thirdlib/colorama/initialise.py: -------------------------------------------------------------------------------- 1 | # Copyright Jonathan Hartley 2013. BSD 3-Clause license, see LICENSE file. 2 | import atexit 3 | import contextlib 4 | import sys 5 | 6 | from .ansitowin32 import AnsiToWin32 7 | 8 | 9 | orig_stdout = None 10 | orig_stderr = None 11 | 12 | wrapped_stdout = None 13 | wrapped_stderr = None 14 | 15 | atexit_done = False 16 | 17 | 18 | def reset_all(): 19 | if AnsiToWin32 is not None: # Issue #74: objects might become None at exit 20 | AnsiToWin32(orig_stdout).reset_all() 21 | 22 | 23 | def init(autoreset=False, convert=None, strip=None, wrap=True): 24 | 25 | if not wrap and any([autoreset, convert, strip]): 26 | raise ValueError('wrap=False conflicts with any other arg=True') 27 | 28 | global wrapped_stdout, wrapped_stderr 29 | global orig_stdout, orig_stderr 30 | 31 | orig_stdout = sys.stdout 32 | orig_stderr = sys.stderr 33 | 34 | if sys.stdout is None: 35 | wrapped_stdout = None 36 | else: 37 | sys.stdout = wrapped_stdout = \ 38 | wrap_stream(orig_stdout, convert, strip, autoreset, wrap) 39 | if sys.stderr is None: 40 | wrapped_stderr = None 41 | else: 42 | sys.stderr = wrapped_stderr = \ 43 | wrap_stream(orig_stderr, convert, strip, autoreset, wrap) 44 | 45 | global atexit_done 46 | if not atexit_done: 47 | atexit.register(reset_all) 48 | atexit_done = True 49 | 50 | 51 | def deinit(): 52 | if orig_stdout is not None: 53 | sys.stdout = orig_stdout 54 | if orig_stderr is not None: 55 | sys.stderr = orig_stderr 56 | 57 | 58 | @contextlib.contextmanager 59 | def colorama_text(*args, **kwargs): 60 | init(*args, **kwargs) 61 | try: 62 | yield 63 | finally: 64 | deinit() 65 | 66 | 67 | def reinit(): 68 | if wrapped_stdout is not None: 69 | sys.stdout = wrapped_stdout 70 | if wrapped_stderr is not None: 71 | sys.stderr = wrapped_stderr 72 | 73 | 74 | def wrap_stream(stream, convert, strip, autoreset, wrap): 75 | if wrap: 76 | wrapper = AnsiToWin32(stream, 77 | convert=convert, strip=strip, autoreset=autoreset) 78 | if wrapper.should_wrap(): 79 | stream = wrapper.stream 80 | return stream 81 | -------------------------------------------------------------------------------- /thirdlib/colorama/win32.py: -------------------------------------------------------------------------------- 1 | # Copyright Jonathan Hartley 2013. BSD 3-Clause license, see LICENSE file. 2 | 3 | # from winbase.h 4 | STDOUT = -11 5 | STDERR = -12 6 | 7 | try: 8 | import ctypes 9 | from ctypes import LibraryLoader 10 | windll = LibraryLoader(ctypes.WinDLL) 11 | from ctypes import wintypes 12 | except (AttributeError, ImportError): 13 | windll = None 14 | SetConsoleTextAttribute = lambda *_: None 15 | winapi_test = lambda *_: None 16 | else: 17 | from ctypes import byref, Structure, c_char, POINTER 18 | 19 | COORD = wintypes._COORD 20 | 21 | class CONSOLE_SCREEN_BUFFER_INFO(Structure): 22 | """struct in wincon.h.""" 23 | _fields_ = [ 24 | ("dwSize", COORD), 25 | ("dwCursorPosition", COORD), 26 | ("wAttributes", wintypes.WORD), 27 | ("srWindow", wintypes.SMALL_RECT), 28 | ("dwMaximumWindowSize", COORD), 29 | ] 30 | def __str__(self): 31 | return '(%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d)' % ( 32 | self.dwSize.Y, self.dwSize.X 33 | , self.dwCursorPosition.Y, self.dwCursorPosition.X 34 | , self.wAttributes 35 | , self.srWindow.Top, self.srWindow.Left, self.srWindow.Bottom, self.srWindow.Right 36 | , self.dwMaximumWindowSize.Y, self.dwMaximumWindowSize.X 37 | ) 38 | 39 | _GetStdHandle = windll.kernel32.GetStdHandle 40 | _GetStdHandle.argtypes = [ 41 | wintypes.DWORD, 42 | ] 43 | _GetStdHandle.restype = wintypes.HANDLE 44 | 45 | _GetConsoleScreenBufferInfo = windll.kernel32.GetConsoleScreenBufferInfo 46 | _GetConsoleScreenBufferInfo.argtypes = [ 47 | wintypes.HANDLE, 48 | POINTER(CONSOLE_SCREEN_BUFFER_INFO), 49 | ] 50 | _GetConsoleScreenBufferInfo.restype = wintypes.BOOL 51 | 52 | _SetConsoleTextAttribute = windll.kernel32.SetConsoleTextAttribute 53 | _SetConsoleTextAttribute.argtypes = [ 54 | wintypes.HANDLE, 55 | wintypes.WORD, 56 | ] 57 | _SetConsoleTextAttribute.restype = wintypes.BOOL 58 | 59 | _SetConsoleCursorPosition = windll.kernel32.SetConsoleCursorPosition 60 | _SetConsoleCursorPosition.argtypes = [ 61 | wintypes.HANDLE, 62 | COORD, 63 | ] 64 | _SetConsoleCursorPosition.restype = wintypes.BOOL 65 | 66 | _FillConsoleOutputCharacterA = windll.kernel32.FillConsoleOutputCharacterA 67 | _FillConsoleOutputCharacterA.argtypes = [ 68 | wintypes.HANDLE, 69 | c_char, 70 | wintypes.DWORD, 71 | COORD, 72 | POINTER(wintypes.DWORD), 73 | ] 74 | _FillConsoleOutputCharacterA.restype = wintypes.BOOL 75 | 76 | _FillConsoleOutputAttribute = windll.kernel32.FillConsoleOutputAttribute 77 | _FillConsoleOutputAttribute.argtypes = [ 78 | wintypes.HANDLE, 79 | wintypes.WORD, 80 | wintypes.DWORD, 81 | COORD, 82 | POINTER(wintypes.DWORD), 83 | ] 84 | _FillConsoleOutputAttribute.restype = wintypes.BOOL 85 | 86 | _SetConsoleTitleW = windll.kernel32.SetConsoleTitleW 87 | _SetConsoleTitleW.argtypes = [ 88 | wintypes.LPCWSTR 89 | ] 90 | _SetConsoleTitleW.restype = wintypes.BOOL 91 | 92 | def _winapi_test(handle): 93 | csbi = CONSOLE_SCREEN_BUFFER_INFO() 94 | success = _GetConsoleScreenBufferInfo( 95 | handle, byref(csbi)) 96 | return bool(success) 97 | 98 | def winapi_test(): 99 | return any(_winapi_test(h) for h in 100 | (_GetStdHandle(STDOUT), _GetStdHandle(STDERR))) 101 | 102 | def GetConsoleScreenBufferInfo(stream_id=STDOUT): 103 | handle = _GetStdHandle(stream_id) 104 | csbi = CONSOLE_SCREEN_BUFFER_INFO() 105 | success = _GetConsoleScreenBufferInfo( 106 | handle, byref(csbi)) 107 | return csbi 108 | 109 | def SetConsoleTextAttribute(stream_id, attrs): 110 | handle = _GetStdHandle(stream_id) 111 | return _SetConsoleTextAttribute(handle, attrs) 112 | 113 | def SetConsoleCursorPosition(stream_id, position, adjust=True): 114 | position = COORD(*position) 115 | # If the position is out of range, do nothing. 116 | if position.Y <= 0 or position.X <= 0: 117 | return 118 | # Adjust for Windows' SetConsoleCursorPosition: 119 | # 1. being 0-based, while ANSI is 1-based. 120 | # 2. expecting (x,y), while ANSI uses (y,x). 121 | adjusted_position = COORD(position.Y - 1, position.X - 1) 122 | if adjust: 123 | # Adjust for viewport's scroll position 124 | sr = GetConsoleScreenBufferInfo(STDOUT).srWindow 125 | adjusted_position.Y += sr.Top 126 | adjusted_position.X += sr.Left 127 | # Resume normal processing 128 | handle = _GetStdHandle(stream_id) 129 | return _SetConsoleCursorPosition(handle, adjusted_position) 130 | 131 | def FillConsoleOutputCharacter(stream_id, char, length, start): 132 | handle = _GetStdHandle(stream_id) 133 | char = c_char(char.encode()) 134 | length = wintypes.DWORD(length) 135 | num_written = wintypes.DWORD(0) 136 | # Note that this is hard-coded for ANSI (vs wide) bytes. 137 | success = _FillConsoleOutputCharacterA( 138 | handle, char, length, start, byref(num_written)) 139 | return num_written.value 140 | 141 | def FillConsoleOutputAttribute(stream_id, attr, length, start): 142 | ''' FillConsoleOutputAttribute( hConsole, csbi.wAttributes, dwConSize, coordScreen, &cCharsWritten )''' 143 | handle = _GetStdHandle(stream_id) 144 | attribute = wintypes.WORD(attr) 145 | length = wintypes.DWORD(length) 146 | num_written = wintypes.DWORD(0) 147 | # Note that this is hard-coded for ANSI (vs wide) bytes. 148 | return _FillConsoleOutputAttribute( 149 | handle, attribute, length, start, byref(num_written)) 150 | 151 | def SetConsoleTitle(title): 152 | return _SetConsoleTitleW(title) 153 | -------------------------------------------------------------------------------- /thirdlib/colorama/winterm.py: -------------------------------------------------------------------------------- 1 | # Copyright Jonathan Hartley 2013. BSD 3-Clause license, see LICENSE file. 2 | from . import win32 3 | 4 | 5 | # from wincon.h 6 | class WinColor(object): 7 | BLACK = 0 8 | BLUE = 1 9 | GREEN = 2 10 | CYAN = 3 11 | RED = 4 12 | MAGENTA = 5 13 | YELLOW = 6 14 | GREY = 7 15 | 16 | # from wincon.h 17 | class WinStyle(object): 18 | NORMAL = 0x00 # dim text, dim background 19 | BRIGHT = 0x08 # bright text, dim background 20 | BRIGHT_BACKGROUND = 0x80 # dim text, bright background 21 | 22 | class WinTerm(object): 23 | 24 | def __init__(self): 25 | self._default = win32.GetConsoleScreenBufferInfo(win32.STDOUT).wAttributes 26 | self.set_attrs(self._default) 27 | self._default_fore = self._fore 28 | self._default_back = self._back 29 | self._default_style = self._style 30 | # In order to emulate LIGHT_EX in windows, we borrow the BRIGHT style. 31 | # So that LIGHT_EX colors and BRIGHT style do not clobber each other, 32 | # we track them separately, since LIGHT_EX is overwritten by Fore/Back 33 | # and BRIGHT is overwritten by Style codes. 34 | self._light = 0 35 | 36 | def get_attrs(self): 37 | return self._fore + self._back * 16 + (self._style | self._light) 38 | 39 | def set_attrs(self, value): 40 | self._fore = value & 7 41 | self._back = (value >> 4) & 7 42 | self._style = value & (WinStyle.BRIGHT | WinStyle.BRIGHT_BACKGROUND) 43 | 44 | def reset_all(self, on_stderr=None): 45 | self.set_attrs(self._default) 46 | self.set_console(attrs=self._default) 47 | self._light = 0 48 | 49 | def fore(self, fore=None, light=False, on_stderr=False): 50 | if fore is None: 51 | fore = self._default_fore 52 | self._fore = fore 53 | # Emulate LIGHT_EX with BRIGHT Style 54 | if light: 55 | self._light |= WinStyle.BRIGHT 56 | else: 57 | self._light &= ~WinStyle.BRIGHT 58 | self.set_console(on_stderr=on_stderr) 59 | 60 | def back(self, back=None, light=False, on_stderr=False): 61 | if back is None: 62 | back = self._default_back 63 | self._back = back 64 | # Emulate LIGHT_EX with BRIGHT_BACKGROUND Style 65 | if light: 66 | self._light |= WinStyle.BRIGHT_BACKGROUND 67 | else: 68 | self._light &= ~WinStyle.BRIGHT_BACKGROUND 69 | self.set_console(on_stderr=on_stderr) 70 | 71 | def style(self, style=None, on_stderr=False): 72 | if style is None: 73 | style = self._default_style 74 | self._style = style 75 | self.set_console(on_stderr=on_stderr) 76 | 77 | def set_console(self, attrs=None, on_stderr=False): 78 | if attrs is None: 79 | attrs = self.get_attrs() 80 | handle = win32.STDOUT 81 | if on_stderr: 82 | handle = win32.STDERR 83 | win32.SetConsoleTextAttribute(handle, attrs) 84 | 85 | def get_position(self, handle): 86 | position = win32.GetConsoleScreenBufferInfo(handle).dwCursorPosition 87 | # Because Windows coordinates are 0-based, 88 | # and win32.SetConsoleCursorPosition expects 1-based. 89 | position.X += 1 90 | position.Y += 1 91 | return position 92 | 93 | def set_cursor_position(self, position=None, on_stderr=False): 94 | if position is None: 95 | # I'm not currently tracking the position, so there is no default. 96 | # position = self.get_position() 97 | return 98 | handle = win32.STDOUT 99 | if on_stderr: 100 | handle = win32.STDERR 101 | win32.SetConsoleCursorPosition(handle, position) 102 | 103 | def cursor_adjust(self, x, y, on_stderr=False): 104 | handle = win32.STDOUT 105 | if on_stderr: 106 | handle = win32.STDERR 107 | position = self.get_position(handle) 108 | adjusted_position = (position.Y + y, position.X + x) 109 | win32.SetConsoleCursorPosition(handle, adjusted_position, adjust=False) 110 | 111 | def erase_screen(self, mode=0, on_stderr=False): 112 | # 0 should clear from the cursor to the end of the screen. 113 | # 1 should clear from the cursor to the beginning of the screen. 114 | # 2 should clear the entire screen, and move cursor to (1,1) 115 | handle = win32.STDOUT 116 | if on_stderr: 117 | handle = win32.STDERR 118 | csbi = win32.GetConsoleScreenBufferInfo(handle) 119 | # get the number of character cells in the current buffer 120 | cells_in_screen = csbi.dwSize.X * csbi.dwSize.Y 121 | # get number of character cells before current cursor position 122 | cells_before_cursor = csbi.dwSize.X * csbi.dwCursorPosition.Y + csbi.dwCursorPosition.X 123 | if mode == 0: 124 | from_coord = csbi.dwCursorPosition 125 | cells_to_erase = cells_in_screen - cells_before_cursor 126 | elif mode == 1: 127 | from_coord = win32.COORD(0, 0) 128 | cells_to_erase = cells_before_cursor 129 | elif mode == 2: 130 | from_coord = win32.COORD(0, 0) 131 | cells_to_erase = cells_in_screen 132 | else: 133 | # invalid mode 134 | return 135 | # fill the entire screen with blanks 136 | win32.FillConsoleOutputCharacter(handle, ' ', cells_to_erase, from_coord) 137 | # now set the buffer's attributes accordingly 138 | win32.FillConsoleOutputAttribute(handle, self.get_attrs(), cells_to_erase, from_coord) 139 | if mode == 2: 140 | # put the cursor where needed 141 | win32.SetConsoleCursorPosition(handle, (1, 1)) 142 | 143 | def erase_line(self, mode=0, on_stderr=False): 144 | # 0 should clear from the cursor to the end of the line. 145 | # 1 should clear from the cursor to the beginning of the line. 146 | # 2 should clear the entire line. 147 | handle = win32.STDOUT 148 | if on_stderr: 149 | handle = win32.STDERR 150 | csbi = win32.GetConsoleScreenBufferInfo(handle) 151 | if mode == 0: 152 | from_coord = csbi.dwCursorPosition 153 | cells_to_erase = csbi.dwSize.X - csbi.dwCursorPosition.X 154 | elif mode == 1: 155 | from_coord = win32.COORD(0, csbi.dwCursorPosition.Y) 156 | cells_to_erase = csbi.dwCursorPosition.X 157 | elif mode == 2: 158 | from_coord = win32.COORD(0, csbi.dwCursorPosition.Y) 159 | cells_to_erase = csbi.dwSize.X 160 | else: 161 | # invalid mode 162 | return 163 | # fill the entire screen with blanks 164 | win32.FillConsoleOutputCharacter(handle, ' ', cells_to_erase, from_coord) 165 | # now set the buffer's attributes accordingly 166 | win32.FillConsoleOutputAttribute(handle, self.get_attrs(), cells_to_erase, from_coord) 167 | 168 | def set_title(self, title): 169 | win32.SetConsoleTitle(title) 170 | --------------------------------------------------------------------------------