├── .appveyor.yml ├── .gitignore ├── .perltidyrc ├── .proverc ├── .travis.yml ├── Build.PL ├── Changes ├── LICENSE ├── META.json ├── README.md ├── cpanfile ├── lib └── CPAN │ ├── Audit.pm │ └── Audit │ ├── DB.pm │ ├── Discover.pm │ ├── Discover │ ├── Cpanfile.pm │ └── CpanfileSnapshot.pm │ ├── Installed.pm │ ├── Query.pm │ └── Version.pm ├── minil.toml ├── script └── cpan-audit ├── t ├── cli.t ├── cli │ ├── deps.t │ ├── installed.t │ ├── module.t │ ├── release.t │ └── show.t ├── data │ ├── carton │ │ └── cpanfile.snapshot │ ├── cpanfile │ │ └── cpanfile │ └── installed │ │ └── perl5 │ │ └── lib │ │ └── perl5 │ │ └── Catalyst.pm ├── discover │ ├── cpanfile.t │ └── cpanfile_snapshot.t ├── installed.t ├── lib │ └── TestCommand.pm ├── query.t └── version.t └── util └── generate /.appveyor.yml: -------------------------------------------------------------------------------- 1 | version: '{build}' 2 | install: 3 | - cmd: >- 4 | choco install -y --allow-empty-checksums strawberryperl -version 5.28.0.1 5 | 6 | SET PATH=C:\strawberry\c\bin;C:\strawberry\perl\site\bin;C:\strawberry\perl\bin;%PATH% 7 | 8 | cpanm --notest --quiet --installdeps . 9 | build: off 10 | cache: 11 | - C:\strawberry -> .appveyor.yml 12 | - C:\ProgramData\chocolatey\bin -> .appveyor.yml 13 | - C:\ProgramData\chocolatey\lib -> .appveyor.yml 14 | - C:\Users\appveyor\AppData\Local\Temp\1\chocolatey\ -> .appveyor.yml 15 | test_script: 16 | - cmd: prove -l -r t/ 17 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /.build/ 2 | /_build/ 3 | /Build 4 | /Build.bat 5 | /blib 6 | /Makefile 7 | /pm_to_blib 8 | 9 | /carton.lock 10 | /.carton/ 11 | /local/ 12 | 13 | nytprof.out 14 | nytprof/ 15 | 16 | cover_db/ 17 | 18 | *.bak 19 | *.old 20 | *~ 21 | *.swp 22 | *.o 23 | *.obj 24 | 25 | !LICENSE 26 | 27 | /_build_params 28 | 29 | MYMETA.* 30 | 31 | /CPAN-Audit-* 32 | -------------------------------------------------------------------------------- /.perltidyrc: -------------------------------------------------------------------------------- 1 | -l=120 2 | -------------------------------------------------------------------------------- /.proverc: -------------------------------------------------------------------------------- 1 | -Ilib -It/lib -r 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | language: perl 3 | perl: 4 | - "5.24" 5 | - "5.26" 6 | - "5.28" 7 | before_install: 8 | - cpanm -n https://github.com/kritikaio/devel-cover-report-kritika-perl/archive/master.tar.gz 9 | install: 10 | - cpanm -n -q --with-recommends --skip-satisfied --installdeps . 11 | script: 12 | - perl Build.PL && ./Build build && ./Build test 13 | - perl -v | grep '5.28' && cover -test -report kritika || exit 0 14 | -------------------------------------------------------------------------------- /Build.PL: -------------------------------------------------------------------------------- 1 | # ========================================================================= 2 | # THIS FILE IS AUTOMATICALLY GENERATED BY MINILLA. 3 | # DO NOT EDIT DIRECTLY. 4 | # ========================================================================= 5 | 6 | use 5.008_001; 7 | use strict; 8 | 9 | use Module::Build::Tiny 0.035; 10 | 11 | Build_PL(); 12 | 13 | -------------------------------------------------------------------------------- /Changes: -------------------------------------------------------------------------------- 1 | Revision history for Perl extension CPAN-Audit 2 | 3 | {{$NEXT}} 4 | 5 | 0.15 2019-03-09T09:47:36Z 6 | 7 | - regenerate database fixing Plack-Middleware-Session distribution name 8 | 9 | 0.14 2019-01-26T10:23:21Z 10 | 11 | [ADVISORIES] 12 | CPANSA-Dancer2 13 | CPANSA-HTTP-Session2 14 | CPANSA-Plack-Middleware-Session-Cookie 15 | 16 | 0.13 2018-11-22T20:38:09Z 17 | 18 | - --no-corelist option by MCRayRay 19 | - test fixes 20 | 21 | 0.12 2018-11-11T19:43:25Z 22 | 23 | - require Module::CoreList latest version 24 | 25 | 0.11 2018-11-11T18:57:53Z 26 | 27 | - check core modules by James Raspass 28 | 29 | 0.10 2018-11-07T20:17:30Z 30 | 31 | - --quiet option 32 | - small refactoring 33 | - require the latest version of Pod::Usage 34 | 35 | 0.09 2018-11-05T21:17:35Z 36 | 37 | - do not hide db from pause (#7) 38 | 39 | 0.08 2018-10-17T18:10:41Z 40 | 41 | [ADVISORIES] 42 | - CPANSA-Net-DNS 43 | - CPANSA-PAR 44 | - CPANSA-PAR-Packer 45 | - CPANSA-RT-Authen-ExternalAuth 46 | - CPANSA-Tk 47 | - CPANSA-UI-Dialog (updated) 48 | - CPANSA-XML-LibXML 49 | 50 | 0.07 2018-10-16T21:37:20Z 51 | 52 | - test fixes 53 | 54 | 0.06 2018-10-16T19:19:22Z 55 | 56 | - use name instead of fullname 57 | - fix installed modules discovery 58 | 59 | 0.05 2018-10-15T19:36:39Z 60 | 61 | [ADVISORIES] 62 | - CPANSA-MHonArc 63 | - CPANSA-Module-Signature 64 | - CPANSA-libapreq2 65 | - CPANSA-mod_perl 66 | - CPANSA-Compress-Raw-Bzip2 67 | - CPANSA-Compress-Raw-Zlib 68 | 69 | [IMPROVEMENTS] 70 | - kritika.io and metacpan badges 71 | 72 | 0.04 2018-10-14T10:56:27Z 73 | 74 | [FEATURES] 75 | - install command accepts path to installations 76 | 77 | [IMPROVEMENTS] 78 | - get rid of Carton dependency 79 | - more test coverage 80 | - CI integrations 81 | - perl 5.8 compat 82 | 83 | 0.03 2018-10-13T12:59:36Z 84 | 85 | [ADVISORIES] 86 | - CPANSA-App-Github-Email 87 | - CPANSA-Crypt-OpenSSL-DSA 88 | - CPANSA-Crypt-Passwd-XS 89 | - CPANSA-DBD-MariaDB 90 | - CPANSA-Dancer 91 | - CPANSA-Data-Dumper 92 | - CPANSA-Email-Address 93 | - CPANSA-Encode 94 | - CPANSA-ExtUtils-MakeMaker 95 | - CPANSA-FCGI 96 | - CPANSA-Fake-Encode 97 | - CPANSA-Fake-Our 98 | - CPANSA-File-DataClass 99 | - CPANSA-File-Path 100 | - CPANSA-HTTP-Tiny 101 | - CPANSA-Imager 102 | - CPANSA-PathTools 103 | 104 | [FEATURES] 105 | - new installed command to audit all installed modules 106 | - cpan.snapshot support by Takumi Akiyama (github.com/akiym) 107 | 108 | 0.02 2018-10-09T08:24:36Z 109 | 110 | - support perl 5.8 111 | 112 | 0.01 2018-10-08T06:39:07Z 113 | 114 | - original version 115 | 116 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | This software is copyright (c) 2018 by vti . 2 | 3 | This is free software; you can redistribute it and/or modify it under 4 | the same terms as the Perl 5 programming language system itself. 5 | 6 | Terms of the Perl programming language system itself 7 | 8 | a) the GNU General Public License as published by the Free 9 | Software Foundation; either version 1, or (at your option) any 10 | later version, or 11 | b) the "Artistic License" 12 | 13 | --- The GNU General Public License, Version 1, February 1989 --- 14 | 15 | This software is Copyright (c) 2018 by vti . 16 | 17 | This is free software, licensed under: 18 | 19 | The GNU General Public License, Version 1, February 1989 20 | 21 | GNU GENERAL PUBLIC LICENSE 22 | Version 1, February 1989 23 | 24 | Copyright (C) 1989 Free Software Foundation, Inc. 25 | 51 Franklin St, Suite 500, Boston, MA 02110-1335 USA 26 | 27 | Everyone is permitted to copy and distribute verbatim copies 28 | of this license document, but changing it is not allowed. 29 | 30 | Preamble 31 | 32 | The license agreements of most software companies try to keep users 33 | at the mercy of those companies. By contrast, our General Public 34 | License is intended to guarantee your freedom to share and change free 35 | software--to make sure the software is free for all its users. The 36 | General Public License applies to the Free Software Foundation's 37 | software and to any other program whose authors commit to using it. 38 | You can use it for your programs, too. 39 | 40 | When we speak of free software, we are referring to freedom, not 41 | price. Specifically, the General Public License is designed to make 42 | sure that you have the freedom to give away or sell copies of free 43 | software, that you receive source code or can get it if you want it, 44 | that you can change the software or use pieces of it in new free 45 | programs; and that you know you can do these things. 46 | 47 | To protect your rights, we need to make restrictions that forbid 48 | anyone to deny you these rights or to ask you to surrender the rights. 49 | These restrictions translate to certain responsibilities for you if you 50 | distribute copies of the software, or if you modify it. 51 | 52 | For example, if you distribute copies of a such a program, whether 53 | gratis or for a fee, you must give the recipients all the rights that 54 | you have. You must make sure that they, too, receive or can get the 55 | source code. And you must tell them their rights. 56 | 57 | We protect your rights with two steps: (1) copyright the software, and 58 | (2) offer you this license which gives you legal permission to copy, 59 | distribute and/or modify the software. 60 | 61 | Also, for each author's protection and ours, we want to make certain 62 | that everyone understands that there is no warranty for this free 63 | software. If the software is modified by someone else and passed on, we 64 | want its recipients to know that what they have is not the original, so 65 | that any problems introduced by others will not reflect on the original 66 | authors' reputations. 67 | 68 | The precise terms and conditions for copying, distribution and 69 | modification follow. 70 | 71 | GNU GENERAL PUBLIC LICENSE 72 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 73 | 74 | 0. This License Agreement applies to any program or other work which 75 | contains a notice placed by the copyright holder saying it may be 76 | distributed under the terms of this General Public License. The 77 | "Program", below, refers to any such program or work, and a "work based 78 | on the Program" means either the Program or any work containing the 79 | Program or a portion of it, either verbatim or with modifications. Each 80 | licensee is addressed as "you". 81 | 82 | 1. You may copy and distribute verbatim copies of the Program's source 83 | code as you receive it, in any medium, provided that you conspicuously and 84 | appropriately publish on each copy an appropriate copyright notice and 85 | disclaimer of warranty; keep intact all the notices that refer to this 86 | General Public License and to the absence of any warranty; and give any 87 | other recipients of the Program a copy of this General Public License 88 | along with the Program. You may charge a fee for the physical act of 89 | transferring a copy. 90 | 91 | 2. You may modify your copy or copies of the Program or any portion of 92 | it, and copy and distribute such modifications under the terms of Paragraph 93 | 1 above, provided that you also do the following: 94 | 95 | a) cause the modified files to carry prominent notices stating that 96 | you changed the files and the date of any change; and 97 | 98 | b) cause the whole of any work that you distribute or publish, that 99 | in whole or in part contains the Program or any part thereof, either 100 | with or without modifications, to be licensed at no charge to all 101 | third parties under the terms of this General Public License (except 102 | that you may choose to grant warranty protection to some or all 103 | third parties, at your option). 104 | 105 | c) If the modified program normally reads commands interactively when 106 | run, you must cause it, when started running for such interactive use 107 | in the simplest and most usual way, to print or display an 108 | announcement including an appropriate copyright notice and a notice 109 | that there is no warranty (or else, saying that you provide a 110 | warranty) and that users may redistribute the program under these 111 | conditions, and telling the user how to view a copy of this General 112 | Public License. 113 | 114 | d) You may charge a fee for the physical act of transferring a 115 | copy, and you may at your option offer warranty protection in 116 | exchange for a fee. 117 | 118 | Mere aggregation of another independent work with the Program (or its 119 | derivative) on a volume of a storage or distribution medium does not bring 120 | the other work under the scope of these terms. 121 | 122 | 3. You may copy and distribute the Program (or a portion or derivative of 123 | it, under Paragraph 2) in object code or executable form under the terms of 124 | Paragraphs 1 and 2 above provided that you also do one of the following: 125 | 126 | a) accompany it with the complete corresponding machine-readable 127 | source code, which must be distributed under the terms of 128 | Paragraphs 1 and 2 above; or, 129 | 130 | b) accompany it with a written offer, valid for at least three 131 | years, to give any third party free (except for a nominal charge 132 | for the cost of distribution) a complete machine-readable copy of the 133 | corresponding source code, to be distributed under the terms of 134 | Paragraphs 1 and 2 above; or, 135 | 136 | c) accompany it with the information you received as to where the 137 | corresponding source code may be obtained. (This alternative is 138 | allowed only for noncommercial distribution and only if you 139 | received the program in object code or executable form alone.) 140 | 141 | Source code for a work means the preferred form of the work for making 142 | modifications to it. For an executable file, complete source code means 143 | all the source code for all modules it contains; but, as a special 144 | exception, it need not include source code for modules which are standard 145 | libraries that accompany the operating system on which the executable 146 | file runs, or for standard header files or definitions files that 147 | accompany that operating system. 148 | 149 | 4. You may not copy, modify, sublicense, distribute or transfer the 150 | Program except as expressly provided under this General Public License. 151 | Any attempt otherwise to copy, modify, sublicense, distribute or transfer 152 | the Program is void, and will automatically terminate your rights to use 153 | the Program under this License. However, parties who have received 154 | copies, or rights to use copies, from you under this General Public 155 | License will not have their licenses terminated so long as such parties 156 | remain in full compliance. 157 | 158 | 5. By copying, distributing or modifying the Program (or any work based 159 | on the Program) you indicate your acceptance of this license to do so, 160 | and all its terms and conditions. 161 | 162 | 6. Each time you redistribute the Program (or any work based on the 163 | Program), the recipient automatically receives a license from the original 164 | licensor to copy, distribute or modify the Program subject to these 165 | terms and conditions. You may not impose any further restrictions on the 166 | recipients' exercise of the rights granted herein. 167 | 168 | 7. The Free Software Foundation may publish revised and/or new versions 169 | of the General Public License from time to time. Such new versions will 170 | be similar in spirit to the present version, but may differ in detail to 171 | address new problems or concerns. 172 | 173 | Each version is given a distinguishing version number. If the Program 174 | specifies a version number of the license which applies to it and "any 175 | later version", you have the option of following the terms and conditions 176 | either of that version or of any later version published by the Free 177 | Software Foundation. If the Program does not specify a version number of 178 | the license, you may choose any version ever published by the Free Software 179 | Foundation. 180 | 181 | 8. If you wish to incorporate parts of the Program into other free 182 | programs whose distribution conditions are different, write to the author 183 | to ask for permission. For software which is copyrighted by the Free 184 | Software Foundation, write to the Free Software Foundation; we sometimes 185 | make exceptions for this. Our decision will be guided by the two goals 186 | of preserving the free status of all derivatives of our free software and 187 | of promoting the sharing and reuse of software generally. 188 | 189 | NO WARRANTY 190 | 191 | 9. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY 192 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN 193 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 194 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED 195 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 196 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS 197 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE 198 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, 199 | REPAIR OR CORRECTION. 200 | 201 | 10. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 202 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR 203 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, 204 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING 205 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED 206 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY 207 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER 208 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 209 | POSSIBILITY OF SUCH DAMAGES. 210 | 211 | END OF TERMS AND CONDITIONS 212 | 213 | Appendix: How to Apply These Terms to Your New Programs 214 | 215 | If you develop a new program, and you want it to be of the greatest 216 | possible use to humanity, the best way to achieve this is to make it 217 | free software which everyone can redistribute and change under these 218 | terms. 219 | 220 | To do so, attach the following notices to the program. It is safest to 221 | attach them to the start of each source file to most effectively convey 222 | the exclusion of warranty; and each file should have at least the 223 | "copyright" line and a pointer to where the full notice is found. 224 | 225 | 226 | Copyright (C) 19yy 227 | 228 | This program is free software; you can redistribute it and/or modify 229 | it under the terms of the GNU General Public License as published by 230 | the Free Software Foundation; either version 1, or (at your option) 231 | any later version. 232 | 233 | This program is distributed in the hope that it will be useful, 234 | but WITHOUT ANY WARRANTY; without even the implied warranty of 235 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 236 | GNU General Public License for more details. 237 | 238 | You should have received a copy of the GNU General Public License 239 | along with this program; if not, write to the Free Software 240 | Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301 USA 241 | 242 | 243 | Also add information on how to contact you by electronic and paper mail. 244 | 245 | If the program is interactive, make it output a short notice like this 246 | when it starts in an interactive mode: 247 | 248 | Gnomovision version 69, Copyright (C) 19xx name of author 249 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 250 | This is free software, and you are welcome to redistribute it 251 | under certain conditions; type `show c' for details. 252 | 253 | The hypothetical commands `show w' and `show c' should show the 254 | appropriate parts of the General Public License. Of course, the 255 | commands you use may be called something other than `show w' and `show 256 | c'; they could even be mouse-clicks or menu items--whatever suits your 257 | program. 258 | 259 | You should also get your employer (if you work as a programmer) or your 260 | school, if any, to sign a "copyright disclaimer" for the program, if 261 | necessary. Here a sample; alter the names: 262 | 263 | Yoyodyne, Inc., hereby disclaims all copyright interest in the 264 | program `Gnomovision' (a program to direct compilers to make passes 265 | at assemblers) written by James Hacker. 266 | 267 | , 1 April 1989 268 | Ty Coon, President of Vice 269 | 270 | That's all there is to it! 271 | 272 | 273 | --- The Artistic License 1.0 --- 274 | 275 | This software is Copyright (c) 2018 by vti . 276 | 277 | This is free software, licensed under: 278 | 279 | The Artistic License 1.0 280 | 281 | The Artistic License 282 | 283 | Preamble 284 | 285 | The intent of this document is to state the conditions under which a Package 286 | may be copied, such that the Copyright Holder maintains some semblance of 287 | artistic control over the development of the package, while giving the users of 288 | the package the right to use and distribute the Package in a more-or-less 289 | customary fashion, plus the right to make reasonable modifications. 290 | 291 | Definitions: 292 | 293 | - "Package" refers to the collection of files distributed by the Copyright 294 | Holder, and derivatives of that collection of files created through 295 | textual modification. 296 | - "Standard Version" refers to such a Package if it has not been modified, 297 | or has been modified in accordance with the wishes of the Copyright 298 | Holder. 299 | - "Copyright Holder" is whoever is named in the copyright or copyrights for 300 | the package. 301 | - "You" is you, if you're thinking about copying or distributing this Package. 302 | - "Reasonable copying fee" is whatever you can justify on the basis of media 303 | cost, duplication charges, time of people involved, and so on. (You will 304 | not be required to justify it to the Copyright Holder, but only to the 305 | computing community at large as a market that must bear the fee.) 306 | - "Freely Available" means that no fee is charged for the item itself, though 307 | there may be fees involved in handling the item. It also means that 308 | recipients of the item may redistribute it under the same conditions they 309 | received it. 310 | 311 | 1. You may make and give away verbatim copies of the source form of the 312 | Standard Version of this Package without restriction, provided that you 313 | duplicate all of the original copyright notices and associated disclaimers. 314 | 315 | 2. You may apply bug fixes, portability fixes and other modifications derived 316 | from the Public Domain or from the Copyright Holder. A Package modified in such 317 | a way shall still be considered the Standard Version. 318 | 319 | 3. You may otherwise modify your copy of this Package in any way, provided that 320 | you insert a prominent notice in each changed file stating how and when you 321 | changed that file, and provided that you do at least ONE of the following: 322 | 323 | a) place your modifications in the Public Domain or otherwise make them 324 | Freely Available, such as by posting said modifications to Usenet or an 325 | equivalent medium, or placing the modifications on a major archive site 326 | such as ftp.uu.net, or by allowing the Copyright Holder to include your 327 | modifications in the Standard Version of the Package. 328 | 329 | b) use the modified Package only within your corporation or organization. 330 | 331 | c) rename any non-standard executables so the names do not conflict with 332 | standard executables, which must also be provided, and provide a separate 333 | manual page for each non-standard executable that clearly documents how it 334 | differs from the Standard Version. 335 | 336 | d) make other distribution arrangements with the Copyright Holder. 337 | 338 | 4. You may distribute the programs of this Package in object code or executable 339 | form, provided that you do at least ONE of the following: 340 | 341 | a) distribute a Standard Version of the executables and library files, 342 | together with instructions (in the manual page or equivalent) on where to 343 | get the Standard Version. 344 | 345 | b) accompany the distribution with the machine-readable source of the Package 346 | with your modifications. 347 | 348 | c) accompany any non-standard executables with their corresponding Standard 349 | Version executables, giving the non-standard executables non-standard 350 | names, and clearly documenting the differences in manual pages (or 351 | equivalent), together with instructions on where to get the Standard 352 | Version. 353 | 354 | d) make other distribution arrangements with the Copyright Holder. 355 | 356 | 5. You may charge a reasonable copying fee for any distribution of this 357 | Package. You may charge any fee you choose for support of this Package. You 358 | may not charge a fee for this Package itself. However, you may distribute this 359 | Package in aggregate with other (possibly commercial) programs as part of a 360 | larger (possibly commercial) software distribution provided that you do not 361 | advertise this Package as a product of your own. 362 | 363 | 6. The scripts and library files supplied as input to or produced as output 364 | from the programs of this Package do not automatically fall under the copyright 365 | of this Package, but belong to whomever generated them, and may be sold 366 | commercially, and may be aggregated with this Package. 367 | 368 | 7. C or perl subroutines supplied by you and linked into this Package shall not 369 | be considered part of this Package. 370 | 371 | 8. The name of the Copyright Holder may not be used to endorse or promote 372 | products derived from this software without specific prior written permission. 373 | 374 | 9. THIS PACKAGE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR IMPLIED 375 | WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF 376 | MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE. 377 | 378 | The End 379 | -------------------------------------------------------------------------------- /META.json: -------------------------------------------------------------------------------- 1 | { 2 | "abstract" : "Audit CPAN distributions for known vulnerabilities", 3 | "author" : [ 4 | "Viacheslav Tykhanovskyi " 5 | ], 6 | "dynamic_config" : 0, 7 | "generated_by" : "Minilla/v3.1.3, CPAN::Meta::Converter version 2.150010", 8 | "license" : [ 9 | "perl_5" 10 | ], 11 | "meta-spec" : { 12 | "url" : "http://search.cpan.org/perldoc?CPAN::Meta::Spec", 13 | "version" : 2 14 | }, 15 | "name" : "CPAN-Audit", 16 | "no_index" : { 17 | "directory" : [ 18 | "t", 19 | "xt", 20 | "inc", 21 | "share", 22 | "eg", 23 | "examples", 24 | "author", 25 | "builder" 26 | ] 27 | }, 28 | "prereqs" : { 29 | "configure" : { 30 | "requires" : { 31 | "Module::Build::Tiny" : "0.035" 32 | } 33 | }, 34 | "develop" : { 35 | "requires" : { 36 | "Test::CPAN::Meta" : "0", 37 | "Test::MinimumVersion::Fast" : "0.04", 38 | "Test::PAUSE::Permissions" : "0.04", 39 | "Test::Pod" : "1.41", 40 | "Test::Spellunker" : "v0.2.7" 41 | } 42 | }, 43 | "runtime" : { 44 | "requires" : { 45 | "CPAN::DistnameInfo" : "0", 46 | "Module::CPANfile" : "0", 47 | "Module::CoreList" : "5.20181020", 48 | "Pod::Usage" : "1.69", 49 | "perl" : "5.008001", 50 | "version" : "0" 51 | } 52 | }, 53 | "test" : { 54 | "requires" : { 55 | "Capture::Tiny" : "0", 56 | "Test::More" : "0.98" 57 | } 58 | } 59 | }, 60 | "release_status" : "unstable", 61 | "resources" : { 62 | "bugtracker" : { 63 | "web" : "https://github.com/vti/cpan-audit/issues" 64 | }, 65 | "homepage" : "https://github.com/vti/cpan-audit", 66 | "repository" : { 67 | "type" : "git", 68 | "url" : "git://github.com/vti/cpan-audit.git", 69 | "web" : "https://github.com/vti/cpan-audit" 70 | } 71 | }, 72 | "version" : "0.15", 73 | "x_authority" : "cpan:VTI", 74 | "x_contributors" : [ 75 | "James Raspass ", 76 | "Mark Raymond ", 77 | "Takumi Akiyama " 78 | ], 79 | "x_serialization_backend" : "JSON::PP version 2.97001", 80 | "x_static_install" : 1 81 | } 82 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Build Status](https://travis-ci.org/vti/cpan-audit.svg?branch=master)](https://travis-ci.org/vti/cpan-audit) [![Build Status](https://img.shields.io/appveyor/ci/vti/cpan-audit/master.svg?logo=appveyor)](https://ci.appveyor.com/project/vti/cpan-audit/branch/master) [![MetaCPAN Release](https://badge.fury.io/pl/CPAN-Audit.svg)](https://metacpan.org/release/CPAN-Audit) [![Kritika Status](https://kritika.io/users/vti/repos/vti+cpan-audit/heads/master/status.svg)](https://kritika.io/users/vti/repos/vti+cpan-audit) [![Kritika Status](https://kritika.io/users/vti/repos/vti+cpan-audit/heads/master/status.svg?type=coverage)](https://kritika.io/users/vti/repos/vti+cpan-audit) [![Kritika Status](https://kritika.io/users/vti/repos/vti+cpan-audit/heads/master/status.svg?type=deps)](https://kritika.io/users/vti/repos/vti+cpan-audit) 2 | # NAME 3 | 4 | *DEPRECATED*: Take a look at . 5 | 6 | cpan-audit - Audit CPAN modules 7 | 8 | # SYNOPSIS 9 | 10 | cpan-audit \[command\] \[options...\] 11 | 12 | Commands: 13 | 14 | module [version range] audit module with optional version range (all by default) 15 | dist|release [version range] audit distribution with optional version range (all by default) 16 | deps [directory] audit dependencies from the directory (. by default) 17 | installed audit all installed modules 18 | show [advisory id] show information about specific advisory 19 | 20 | Options: 21 | 22 | --no-color switch off colors 23 | --no-corelist ignore modules bundled with perl version 24 | --ascii use ascii output 25 | --quiet be quiet 26 | --verbose be verbose 27 | --help|h help message 28 | 29 | Examples: 30 | 31 | cpan-audit dist Catalyst-Runtime 32 | cpan-audit dist Catalyst-Runtime 7.0 33 | cpan-audit dist Catalyst-Runtime >5.48 34 | 35 | cpan-audit module Catalyst 7.0 36 | 37 | cpan-audit deps . 38 | cpan-audit deps /path/to/distribution 39 | 40 | cpan-audit installed 41 | cpan-audit installed local/ 42 | 43 | cpan-audit show CPANSA-Mojolicious-2018-03 44 | 45 | # DESCRIPTION 46 | 47 | `cpan-audit` is a command line application that checks the modules or distributions for known vulnerabilities. It is using 48 | its internal database that is automatically generated from a hand-picked database 49 | [https://github.com/vti/cpan-security-advisory](https://github.com/vti/cpan-security-advisory). 50 | 51 | `cpan-audit` does not connect to anything, that is why it is important to keep it up to date. Every update of the internal 52 | database is released as a new version. 53 | 54 | `cpan-audit` can automatically detect dependencies from the following sources: 55 | 56 | - `Carton` 57 | 58 | Parses `cpanfile.snapshot` file and checks the distribution versions. 59 | 60 | - `cpanfile` 61 | 62 | Parses `cpanfile` taking into account the required versions. 63 | 64 | It is assumed that if the required version of the module is less than a version of a release with a known vulnerability 65 | fix, then the module is considered affected. 66 | 67 | # LICENSE 68 | 69 | Copyright (C) Viacheslav Tykhanovskyi. 70 | 71 | This library is free software; you can redistribute it and/or modify 72 | it under the same terms as Perl itself. 73 | -------------------------------------------------------------------------------- /cpanfile: -------------------------------------------------------------------------------- 1 | requires 'perl', '5.008001'; 2 | 3 | requires 'version'; 4 | requires 'CPAN::DistnameInfo'; 5 | requires 'Module::CPANfile'; 6 | requires 'Pod::Usage', '1.69'; 7 | requires 'Module::CoreList', '5.20181020'; 8 | 9 | on 'test' => sub { 10 | requires 'Test::More', '0.98'; 11 | requires 'Capture::Tiny'; 12 | }; 13 | -------------------------------------------------------------------------------- /lib/CPAN/Audit.pm: -------------------------------------------------------------------------------- 1 | package CPAN::Audit; 2 | use 5.008001; 3 | use strict; 4 | use warnings; 5 | use version; 6 | use CPAN::Audit::Installed; 7 | use CPAN::Audit::Discover; 8 | use CPAN::Audit::Version; 9 | use CPAN::Audit::Query; 10 | use CPAN::Audit::DB; 11 | use Module::CoreList; 12 | 13 | our $VERSION = "0.15"; 14 | 15 | sub new { 16 | my $class = shift; 17 | my (%params) = @_; 18 | 19 | my $self = {}; 20 | bless $self, $class; 21 | 22 | $self->{ascii} = $params{ascii}; 23 | $self->{verbose} = $params{verbose}; 24 | $self->{quiet} = $params{quiet}; 25 | $self->{no_color} = $params{no_color}; 26 | $self->{no_corelist} = $params{no_corelist}; 27 | $self->{interactive} = $params{interactive}; 28 | 29 | if ( !$self->{interactive} ) { 30 | $self->{ascii} = 1; 31 | $self->{no_color} = 1; 32 | } 33 | 34 | $self->{db} = CPAN::Audit::DB->db; 35 | $self->{query} = CPAN::Audit::Query->new( db => $self->{db} ); 36 | $self->{discover} = CPAN::Audit::Discover->new( db => $self->{db} ); 37 | 38 | return $self; 39 | } 40 | 41 | sub command { 42 | my $self = shift; 43 | my ( $command, @args ) = @_; 44 | 45 | my %dists; 46 | 47 | if (!$self->{no_corelist} 48 | && ( $command eq 'dependencies' 49 | || $command eq 'deps' 50 | || $command eq 'installed' ) 51 | ) 52 | { 53 | # Find core modules for this perl version first. 54 | # This way explictly installed versions will overwrite. 55 | if ( my $core = $Module::CoreList::version{$]} ) { 56 | while ( my ( $mod, $ver ) = each %$core ) { 57 | my $dist = $self->{db}{module2dist}{$mod} or next; 58 | 59 | $dists{$dist} = $ver if version->parse($ver) > $dists{$dist}; 60 | } 61 | } 62 | } 63 | 64 | if ( $command eq 'module' ) { 65 | my ( $module, $version_range ) = @args; 66 | $self->fatal("Usage: module [version-range]") unless $module; 67 | 68 | my $distname = $self->{db}->{module2dist}->{$module}; 69 | 70 | if ( !$distname ) { 71 | $self->message("__GREEN__Module '$module' is not in database"); 72 | return 0; 73 | } 74 | 75 | $dists{$distname} = $version_range || ''; 76 | } 77 | elsif ( $command eq 'release' || $command eq 'dist' ) { 78 | my ( $distname, $version_range ) = @args; 79 | $self->fatal("Usage: dist|release [version-range]") 80 | unless $distname; 81 | 82 | if ( !$self->{db}->{dists}->{$distname} ) { 83 | $self->message("__GREEN__Distribution '$distname' is not in database"); 84 | return 0; 85 | } 86 | 87 | $dists{$distname} = $version_range || ''; 88 | } 89 | elsif ( $command eq 'show' ) { 90 | my ($advisory_id) = @args; 91 | $self->fatal("Usage: show ") unless $advisory_id; 92 | 93 | my ($release) = $advisory_id =~ m/^CPANSA-(.*?)-(\d+)-(\d+)$/; 94 | $self->fatal("Invalid advisory id") unless $release; 95 | 96 | my $dist = $self->{db}->{dists}->{$release}; 97 | $self->fatal("Unknown advisory id") unless $dist; 98 | 99 | my ($advisory) = 100 | grep { $_->{id} eq $advisory_id } @{ $dist->{advisories} }; 101 | $self->fatal("Unknown advisory id") unless $advisory; 102 | 103 | $self->print_advisory($advisory); 104 | 105 | return 0; 106 | } 107 | elsif ( $command eq 'dependencies' || $command eq 'deps' ) { 108 | my ($path) = @args; 109 | $path = '.' unless defined $path; 110 | 111 | $self->fatal("Usage: deps ") unless -d $path; 112 | 113 | my @deps = $self->{discover}->discover($path); 114 | 115 | $self->message( 'Discovered %d dependencies', scalar(@deps) ); 116 | 117 | foreach my $dep (@deps) { 118 | my $dist = $dep->{dist} 119 | || $self->{db}->{module2dist}->{ $dep->{module} }; 120 | next unless $dist; 121 | 122 | $dists{$dist} = $dep->{version}; 123 | } 124 | } 125 | elsif ( $command eq 'installed' ) { 126 | $self->message_info('Collecting all installed modules. This can take a while...'); 127 | 128 | my @deps = CPAN::Audit::Installed->new( 129 | db => $self->{db}, 130 | $self->{verbose} 131 | ? ( 132 | cb => sub { 133 | my ($info) = @_; 134 | 135 | $self->message( '%s: %s-%s', $info->{path}, $info->{distname}, $info->{version} ); 136 | } 137 | ) 138 | : () 139 | )->find(@ARGV); 140 | 141 | foreach my $dep (@deps) { 142 | my $dist = $dep->{dist} 143 | || $self->{db}->{module2dist}->{ $dep->{module} }; 144 | next unless $dist; 145 | 146 | $dists{ $dep->{dist} } = $dep->{version}; 147 | } 148 | } 149 | else { 150 | $self->fatal("Error: unknown command: $command. See -h"); 151 | } 152 | 153 | my $total_advisories = 0; 154 | 155 | if (%dists) { 156 | my $query = $self->{query}; 157 | 158 | foreach my $distname ( sort keys %dists ) { 159 | my $version_range = $dists{$distname}; 160 | 161 | my @advisories = $query->advisories_for( $distname, $version_range ); 162 | 163 | $version_range = 'Any' 164 | if $version_range eq '' || $version_range eq '0'; 165 | 166 | if (@advisories) { 167 | $self->message( '__RED__%s (requires %s) has %d advisories__RESET__', 168 | $distname, $version_range, scalar(@advisories) ); 169 | 170 | foreach my $advisory (@advisories) { 171 | $self->print_advisory($advisory); 172 | } 173 | } 174 | 175 | $total_advisories += @advisories; 176 | } 177 | } 178 | 179 | if ($total_advisories) { 180 | $self->message( '__RED__Total advisories found: %d__RESET__', $total_advisories ); 181 | 182 | return $total_advisories; 183 | } 184 | else { 185 | $self->message_info('__GREEN__No advisories found__RESET__'); 186 | 187 | return 0; 188 | } 189 | } 190 | 191 | sub message_info { 192 | my $self = shift; 193 | 194 | return if $self->{quiet}; 195 | 196 | $self->message(@_); 197 | } 198 | 199 | sub message { 200 | my $self = shift; 201 | 202 | $self->_print( *STDOUT, @_ ); 203 | } 204 | 205 | sub fatal { 206 | my $self = shift; 207 | my ( $msg, @args ) = @_; 208 | 209 | $self->_print( *STDERR, "Error: $msg", @args ); 210 | exit 255; 211 | } 212 | 213 | sub print_advisory { 214 | my $self = shift; 215 | my ($advisory) = @_; 216 | 217 | $self->message(" __BOLD__* $advisory->{id}"); 218 | 219 | print " $advisory->{description}\n"; 220 | 221 | if ( $advisory->{affected_versions} ) { 222 | print " Affected range: $advisory->{affected_versions}\n"; 223 | } 224 | 225 | if ( $advisory->{fixed_versions} ) { 226 | print " Fixed range: $advisory->{fixed_versions}\n"; 227 | } 228 | 229 | if ( $advisory->{cves} ) { 230 | print "\n CVEs: "; 231 | print join ', ', @{ $advisory->{cves} }; 232 | print "\n"; 233 | } 234 | 235 | if ( $advisory->{references} ) { 236 | print "\n References:\n"; 237 | foreach my $reference ( @{ $advisory->{references} || [] } ) { 238 | print " $reference\n"; 239 | } 240 | } 241 | 242 | print "\n"; 243 | } 244 | 245 | sub _print { 246 | my $self = shift; 247 | my ( $fh, $format, @params ) = @_; 248 | 249 | my $msg = @params ? ( sprintf( $format, @params ) ) : ($format); 250 | 251 | if ( $self->{no_color} ) { 252 | $msg =~ s{__BOLD__}{}g; 253 | $msg =~ s{__GREEN__}{}g; 254 | $msg =~ s{__RED__}{}g; 255 | $msg =~ s{__RESET__}{}g; 256 | } 257 | else { 258 | $msg =~ s{__BOLD__}{\e[39;1m}g; 259 | $msg =~ s{__GREEN__}{\e[32m}g; 260 | $msg =~ s{__RED__}{\e[31m}g; 261 | $msg =~ s{__RESET__}{\e[0m}g; 262 | 263 | $msg .= "\e[0m"; 264 | } 265 | 266 | print $fh "$msg\n"; 267 | } 268 | 269 | 1; 270 | __END__ 271 | 272 | =encoding utf-8 273 | 274 | =head1 NAME 275 | 276 | CPAN::Audit - Audit CPAN distributions for known vulnerabilities 277 | 278 | =head1 SYNOPSIS 279 | 280 | use CPAN::Audit; 281 | 282 | =head1 DESCRIPTION 283 | 284 | CPAN::Audit is a module and a database at the same time. It is used by L command line application to query 285 | for vulnerabilities. 286 | 287 | =head1 LICENSE 288 | 289 | Copyright (C) Viacheslav Tykhanovskyi. 290 | 291 | This library is free software; you can redistribute it and/or modify 292 | it under the same terms as Perl itself. 293 | 294 | =head1 AUTHOR 295 | 296 | Viacheslav Tykhanovskyi Eviacheslav.t@gmail.comE 297 | 298 | =head1 CREDITS 299 | 300 | Takumi Akiyama (github.com/akiym) 301 | 302 | James Raspass (github.com/JRaspass) 303 | 304 | MCRayRay (github.com/MCRayRay) 305 | 306 | =cut 307 | -------------------------------------------------------------------------------- /lib/CPAN/Audit/Discover.pm: -------------------------------------------------------------------------------- 1 | package CPAN::Audit::Discover; 2 | use strict; 3 | use warnings; 4 | use CPAN::Audit::Discover::Cpanfile; 5 | use CPAN::Audit::Discover::CpanfileSnapshot; 6 | 7 | sub new { 8 | my $class = shift; 9 | 10 | my $self = {}; 11 | bless $self, $class; 12 | 13 | return $self; 14 | } 15 | 16 | sub discover { 17 | my $self = shift; 18 | my ($path) = @_; 19 | 20 | if ( -f "$path/cpanfile.snapshot" ) { 21 | return CPAN::Audit::Discover::CpanfileSnapshot->new->discover("$path/cpanfile.snapshot"); 22 | } 23 | elsif ( -f "$path/cpanfile" ) { 24 | return CPAN::Audit::Discover::Cpanfile->new->discover("$path/cpanfile"); 25 | } 26 | else { 27 | } 28 | 29 | return; 30 | } 31 | 32 | 1; 33 | -------------------------------------------------------------------------------- /lib/CPAN/Audit/Discover/Cpanfile.pm: -------------------------------------------------------------------------------- 1 | package CPAN::Audit::Discover::Cpanfile; 2 | use strict; 3 | use warnings; 4 | use Module::CPANfile; 5 | 6 | sub new { 7 | my $class = shift; 8 | 9 | my $self = {}; 10 | bless $self, $class; 11 | 12 | return $self; 13 | } 14 | 15 | sub discover { 16 | my $self = shift; 17 | my ($cpanfile_path) = @_; 18 | 19 | my $cpanfile = Module::CPANfile->load($cpanfile_path); 20 | 21 | my $prereqs = $cpanfile->prereqs->as_string_hash; 22 | 23 | my @deps; 24 | foreach my $phase ( keys %$prereqs ) { 25 | foreach my $type ( keys %{ $prereqs->{$phase} } ) { 26 | foreach my $module ( keys %{ $prereqs->{$phase}->{$type} } ) { 27 | my $version = $prereqs->{$phase}->{$type}->{$module}; 28 | 29 | next if $module eq 'perl'; 30 | 31 | push @deps, 32 | { 33 | module => $module, 34 | version => $version, 35 | }; 36 | } 37 | } 38 | } 39 | 40 | return @deps; 41 | } 42 | 43 | 1; 44 | -------------------------------------------------------------------------------- /lib/CPAN/Audit/Discover/CpanfileSnapshot.pm: -------------------------------------------------------------------------------- 1 | package CPAN::Audit::Discover::CpanfileSnapshot; 2 | use strict; 3 | use warnings; 4 | use CPAN::DistnameInfo; 5 | 6 | sub new { 7 | my $class = shift; 8 | 9 | my $self = {}; 10 | bless $self, $class; 11 | 12 | return $self; 13 | } 14 | 15 | sub discover { 16 | my $self = shift; 17 | my ($cpanfile_snapshot_path) = @_; 18 | 19 | open my $fh, '<', $cpanfile_snapshot_path or die $!; 20 | 21 | my @deps; 22 | while ( defined( my $line = <$fh> ) ) { 23 | if ( $line =~ m/pathname: ([^\s]+)/ ) { 24 | next unless my $d = CPAN::DistnameInfo->new($1); 25 | 26 | next unless $d->dist && $d->version; 27 | 28 | push @deps, 29 | { 30 | dist => $d->dist, 31 | version => $d->version, 32 | }; 33 | } 34 | } 35 | 36 | close $fh; 37 | 38 | return @deps; 39 | } 40 | 41 | 1; 42 | -------------------------------------------------------------------------------- /lib/CPAN/Audit/Installed.pm: -------------------------------------------------------------------------------- 1 | package CPAN::Audit::Installed; 2 | use strict; 3 | use warnings; 4 | use File::Find (); 5 | use Cwd (); 6 | 7 | sub new { 8 | my $class = shift; 9 | my (%params) = @_; 10 | 11 | my $self = {}; 12 | bless $self, $class; 13 | 14 | $self->{db} = $params{db}; 15 | $self->{cb} = $params{cb}; 16 | 17 | return $self; 18 | } 19 | 20 | sub find { 21 | my $self = shift; 22 | my (@inc) = @_; 23 | 24 | @inc = @INC unless @inc; 25 | @inc = grep { defined && -d $_ } map { Cwd::realpath($_) } @inc; 26 | 27 | my %seen; 28 | my @deps; 29 | 30 | File::Find::find( 31 | { 32 | wanted => sub { 33 | my $path = $File::Find::name; 34 | 35 | if ( $path && -f $path && m/\.pm$/ ) { 36 | return unless my $module = module_from_file($path); 37 | 38 | return unless my $distname = $self->{db}->{module2dist}->{$module}; 39 | 40 | my $dist = $self->{db}->{dists}->{$distname}; 41 | if ( $dist->{main_module} eq $module ) { 42 | return if $seen{$module}++; 43 | 44 | return unless my $version = module_version($path); 45 | 46 | push @deps, { dist => $distname, version => $version }; 47 | 48 | if ( $self->{cb} ) { 49 | $self->{cb}->( 50 | { 51 | path => $path, 52 | distname => $distname, 53 | version => $version 54 | } 55 | ); 56 | } 57 | } 58 | } 59 | }, 60 | follow => 1, 61 | follow_skip => 2, 62 | }, 63 | @inc 64 | ); 65 | 66 | return @deps; 67 | } 68 | 69 | # https://metacpan.org/source/ABELTJE/V-0.13/V.pm 70 | sub module_version { 71 | my ($parsefile) = @_; 72 | 73 | open my $mod, '<', $parsefile or die $!; 74 | 75 | my $inpod = 0; 76 | my $result; 77 | local $_; 78 | while (<$mod>) { 79 | $inpod = /^=(?!cut)/ ? 1 : /^=cut/ ? 0 : $inpod; 80 | next if $inpod || /^\s*#/; 81 | 82 | chomp; 83 | next unless m/([\$*])(([\w\:\']*)\bVERSION)\b.*\=/; 84 | my $eval = qq{ 85 | package CPAN::Audit::_version; 86 | no strict; 87 | 88 | local $1$2; 89 | \$$2=undef; do { 90 | $_ 91 | }; \$$2 92 | }; 93 | local $^W = 0; 94 | $result = eval($eval); 95 | warn "Could not eval '$eval' in $parsefile: $@" if $@; 96 | $result = "undef" unless defined $result; 97 | last; 98 | } 99 | close $mod; 100 | return $result; 101 | } 102 | 103 | sub module_from_file { 104 | my ($path) = @_; 105 | 106 | my $module; 107 | 108 | open my $fh, '<', $path or return; 109 | while ( my $line = <$fh> ) { 110 | if ( $line =~ m/package\s+(.*?)\s*;/ms ) { 111 | $module = $1; 112 | last; 113 | } 114 | } 115 | close $fh; 116 | 117 | return unless $module; 118 | } 119 | 120 | 1; 121 | -------------------------------------------------------------------------------- /lib/CPAN/Audit/Query.pm: -------------------------------------------------------------------------------- 1 | package CPAN::Audit::Query; 2 | use strict; 3 | use warnings; 4 | use CPAN::Audit::Version; 5 | 6 | sub new { 7 | my $class = shift; 8 | my (%params) = @_; 9 | 10 | my $self = {}; 11 | bless $self, $class; 12 | 13 | $self->{db} = $params{db} || {}; 14 | 15 | return $self; 16 | } 17 | 18 | sub advisories_for { 19 | my $self = shift; 20 | my ( $distname, $version_range ) = @_; 21 | 22 | my $dist = $self->{db}->{dists}->{$distname}; 23 | return unless $dist; 24 | 25 | my @advisories = @{ $dist->{advisories} }; 26 | my @versions = @{ $dist->{versions} }; 27 | 28 | if ( !$version_range ) { 29 | return @advisories; 30 | } 31 | 32 | my $version_checker = CPAN::Audit::Version->new; 33 | 34 | my @all_versions = map { $_->{version} } @versions; 35 | my @selected_versions; 36 | 37 | foreach my $version (@all_versions) { 38 | if ( $version_checker->in_range( $version, $version_range ) ) { 39 | push @selected_versions, $version; 40 | } 41 | } 42 | 43 | if ( !@selected_versions ) { 44 | return; 45 | } 46 | 47 | my @matched_advisories; 48 | foreach my $advisory (@advisories) { 49 | my @affected_versions = $version_checker->affected_versions( \@all_versions, $advisory->{affected_versions} ); 50 | next unless @affected_versions; 51 | 52 | foreach my $affected_version ( reverse @affected_versions ) { 53 | if ( $version_checker->in_range( $affected_version, $version_range ) ) { 54 | push @matched_advisories, $advisory; 55 | last; 56 | } 57 | } 58 | } 59 | 60 | if ( !@matched_advisories ) { 61 | return; 62 | } 63 | 64 | return @matched_advisories; 65 | } 66 | 67 | 1; 68 | -------------------------------------------------------------------------------- /lib/CPAN/Audit/Version.pm: -------------------------------------------------------------------------------- 1 | package CPAN::Audit::Version; 2 | use strict; 3 | use warnings; 4 | use version; 5 | 6 | sub new { 7 | my $class = shift; 8 | 9 | my $self = {}; 10 | bless $self, $class; 11 | 12 | return $self; 13 | } 14 | 15 | sub in_range { 16 | my $self = shift; 17 | my ( $version, $range ) = @_; 18 | 19 | return unless defined $version && defined $range; 20 | 21 | my @ands = split /\s*,\s*/, $range; 22 | 23 | return unless defined( $version = eval { version->parse($version) } ); 24 | 25 | foreach my $and (@ands) { 26 | my ( $op, $range_version ) = $and =~ m/^(<=|<|>=|>|==|!=)?\s*([^\s]+)$/; 27 | 28 | return 29 | unless defined( $range_version = eval { version->parse($range_version) } ); 30 | 31 | $op = '>=' unless defined $op; 32 | 33 | if ( $op eq '<' ) { 34 | return unless $version < $range_version; 35 | } 36 | elsif ( $op eq '<=' ) { 37 | return unless $version <= $range_version; 38 | } 39 | elsif ( $op eq '>' ) { 40 | return unless $version > $range_version; 41 | } 42 | elsif ( $op eq '>=' ) { 43 | return unless $version >= $range_version; 44 | } 45 | elsif ( $op eq '==' ) { 46 | return unless $version == $range_version; 47 | } 48 | elsif ( $op eq '!=' ) { 49 | return unless $version != $range_version; 50 | } 51 | else { 52 | return 0; 53 | } 54 | } 55 | 56 | return 1; 57 | } 58 | 59 | sub affected_versions { 60 | my $self = shift; 61 | my ( $available_versions, $range ) = @_; 62 | 63 | my @affected_versions; 64 | foreach my $version (@$available_versions) { 65 | if ( $self->in_range( $version, $range ) ) { 66 | push @affected_versions, $version; 67 | } 68 | } 69 | 70 | return @affected_versions; 71 | } 72 | 73 | 1; 74 | -------------------------------------------------------------------------------- /minil.toml: -------------------------------------------------------------------------------- 1 | name = "CPAN-Audit" 2 | badges = ["travis", "appveyor", "metacpan", "kritika", "kritika?type=coverage", "kritika?type=deps"] 3 | authority="cpan:VTI" 4 | 5 | module_maker="ModuleBuildTiny" 6 | 7 | readme_from="script/cpan-audit" 8 | -------------------------------------------------------------------------------- /script/cpan-audit: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env perl 2 | use strict; 3 | use warnings; 4 | use FindBin; 5 | use Pod::Usage; 6 | use Getopt::Long; 7 | use CPAN::Audit; 8 | 9 | my $opt_no_color; 10 | my $opt_no_corelist; 11 | my $opt_ascii; 12 | my $opt_verbose; 13 | my $opt_quiet; 14 | my $opt_help; 15 | GetOptions( 16 | 'no-color' => \$opt_no_color, 17 | 'no-corelist' => \$opt_no_corelist, 18 | 'ascii' => \$opt_ascii, 19 | 'verbose|v' => \$opt_verbose, 20 | 'quiet|q' => \$opt_quiet, 21 | 'help|h' => \$opt_help, 22 | ) or die("Error in command line arguments\n"); 23 | 24 | pod2usage( -input => $FindBin::Bin . "/" . $FindBin::Script ) if $opt_help; 25 | 26 | my ($command) = shift @ARGV; 27 | 28 | if ( !$command ) { 29 | pod2usage( -input => $FindBin::Bin . "/" . $FindBin::Script ); 30 | } 31 | 32 | my $audit = CPAN::Audit->new( 33 | no_color => $opt_no_color, 34 | no_corelist => $opt_no_corelist, 35 | ascii => $opt_ascii, 36 | verbose => $opt_verbose, 37 | quiet => $opt_quiet, 38 | interactive => ( -t STDIN && -t STDOUT ), 39 | ); 40 | 41 | exit $audit->command( $command, @ARGV ); 42 | 43 | __END__ 44 | 45 | =head1 NAME 46 | 47 | cpan-audit - Audit CPAN modules 48 | 49 | =head1 SYNOPSIS 50 | 51 | cpan-audit [command] [options...] 52 | 53 | Commands: 54 | 55 | module [version range] audit module with optional version range (all by default) 56 | dist|release [version range] audit distribution with optional version range (all by default) 57 | deps [directory] audit dependencies from the directory (. by default) 58 | installed audit all installed modules 59 | show [advisory id] show information about specific advisory 60 | 61 | Options: 62 | 63 | --no-color switch off colors 64 | --no-corelist ignore modules bundled with perl version 65 | --ascii use ascii output 66 | --quiet be quiet 67 | --verbose be verbose 68 | --help|h help message 69 | 70 | Examples: 71 | 72 | cpan-audit dist Catalyst-Runtime 73 | cpan-audit dist Catalyst-Runtime 7.0 74 | cpan-audit dist Catalyst-Runtime >5.48 75 | 76 | cpan-audit module Catalyst 7.0 77 | 78 | cpan-audit deps . 79 | cpan-audit deps /path/to/distribution 80 | 81 | cpan-audit installed 82 | cpan-audit installed local/ 83 | 84 | cpan-audit show CPANSA-Mojolicious-2018-03 85 | 86 | =head1 DESCRIPTION 87 | 88 | C is a command line application that checks the modules or distributions for known vulnerabilities. It is using 89 | its internal database that is automatically generated from a hand-picked database 90 | L. 91 | 92 | C does not connect to anything, that is why it is important to keep it up to date. Every update of the internal 93 | database is released as a new version. 94 | 95 | C can automatically detect dependencies from the following sources: 96 | 97 | =over 98 | 99 | =item C 100 | 101 | Parses C file and checks the distribution versions. 102 | 103 | =item C 104 | 105 | Parses C taking into account the required versions. 106 | 107 | =back 108 | 109 | It is assumed that if the required version of the module is less than a version of a release with a known vulnerability 110 | fix, then the module is considered affected. 111 | 112 | =head1 LICENSE 113 | 114 | Copyright (C) Viacheslav Tykhanovskyi. 115 | 116 | This library is free software; you can redistribute it and/or modify 117 | it under the same terms as Perl itself. 118 | 119 | =cut 120 | -------------------------------------------------------------------------------- /t/cli.t: -------------------------------------------------------------------------------- 1 | use strict; 2 | use warnings; 3 | use lib 't/lib'; 4 | use Test::More; 5 | use TestCommand; 6 | 7 | subtest 'help is printed' => sub { 8 | my ( $stdout, $stderr, $exit ) = TestCommand->command(); 9 | 10 | is $stdout, ''; 11 | like $stderr, qr/Usage:.*cpan-audit/ms; 12 | isnt $exit, 0; 13 | }; 14 | 15 | done_testing; 16 | -------------------------------------------------------------------------------- /t/cli/deps.t: -------------------------------------------------------------------------------- 1 | use strict; 2 | use warnings; 3 | use lib 't/lib'; 4 | use Test::More; 5 | use TestCommand; 6 | 7 | subtest 'command: deps' => sub { 8 | my ( $stdout, $stderr, $exit ) = TestCommand->command('deps'); 9 | 10 | like $stdout, qr/Discovered \d+ dependencies/; 11 | is $stderr, ''; 12 | is $exit, 0; 13 | }; 14 | 15 | done_testing; 16 | -------------------------------------------------------------------------------- /t/cli/installed.t: -------------------------------------------------------------------------------- 1 | use strict; 2 | use warnings; 3 | use lib 't/lib'; 4 | use Test::More; 5 | use TestCommand; 6 | 7 | subtest 'command: installed' => sub { 8 | my ( $stdout, $stderr, $exit ) = TestCommand->command( 'installed', 'lib' ); 9 | 10 | like $stdout, qr/Collecting all installed modules/; 11 | is $stderr, ''; 12 | is $exit, 0; 13 | }; 14 | 15 | done_testing; 16 | -------------------------------------------------------------------------------- /t/cli/module.t: -------------------------------------------------------------------------------- 1 | use strict; 2 | use warnings; 3 | use lib 't/lib'; 4 | use Test::More; 5 | use TestCommand; 6 | 7 | subtest 'command: module' => sub { 8 | my ( $stdout, $stderr, $exit ) = TestCommand->command( 'module', 'Catalyst' ); 9 | 10 | like $stdout, qr/CPANSA-Catalyst-Runtime-2013-01/; 11 | is $stderr, ''; 12 | isnt $exit, 0; 13 | }; 14 | 15 | subtest 'command: unknown module' => sub { 16 | my ( $stdout, $stderr, $exit ) = TestCommand->command( 'module', 'Unknown' ); 17 | 18 | like $stdout, qr/Module 'Unknown' is not in database/; 19 | is $stderr, ''; 20 | is $exit, 0; 21 | }; 22 | 23 | subtest 'command: invalid invocation' => sub { 24 | my ( $stdout, $stderr, $exit ) = TestCommand->command( 'module' ); 25 | 26 | is $stdout, ''; 27 | like $stderr, qr/Error: Usage: /; 28 | isnt $exit, 0; 29 | }; 30 | 31 | done_testing; 32 | -------------------------------------------------------------------------------- /t/cli/release.t: -------------------------------------------------------------------------------- 1 | use strict; 2 | use warnings; 3 | use lib 't/lib'; 4 | use Test::More; 5 | use TestCommand; 6 | 7 | subtest 'command: release' => sub { 8 | my ( $stdout, $stderr, $exit ) = TestCommand->command( 'release', 'Catalyst-Runtime' ); 9 | 10 | like $stdout, qr/CPANSA-Catalyst-Runtime-2013-01/; 11 | is $stderr, ''; 12 | isnt $exit, 0; 13 | }; 14 | 15 | subtest 'command: unknown release' => sub { 16 | my ( $stdout, $stderr, $exit ) = TestCommand->command( 'release', 'Unknown' ); 17 | 18 | like $stdout, qr/Distribution 'Unknown' is not in database/; 19 | is $stderr, ''; 20 | is $exit, 0; 21 | }; 22 | 23 | subtest 'command: invalid invocation' => sub { 24 | my ( $stdout, $stderr, $exit ) = TestCommand->command( 'release' ); 25 | 26 | is $stdout, ''; 27 | like $stderr, qr/Error: Usage: /; 28 | isnt $exit, 0; 29 | }; 30 | 31 | done_testing; 32 | -------------------------------------------------------------------------------- /t/cli/show.t: -------------------------------------------------------------------------------- 1 | use strict; 2 | use warnings; 3 | use lib 't/lib'; 4 | use Test::More; 5 | use TestCommand; 6 | 7 | subtest 'command: show' => sub { 8 | my ( $stdout, $stderr, $exit ) = TestCommand->command( 'show', 'CPANSA-Catalyst-Runtime-2013-01' ); 9 | 10 | like $stdout, qr/CPANSA-Catalyst-Runtime-2013-01/; 11 | is $stderr, ''; 12 | is $exit, 0; 13 | }; 14 | 15 | subtest 'command: show unknown advisory' => sub { 16 | my ( $stdout, $stderr, $exit ) = TestCommand->command( 'show', 'CPANSA-UNKNOWN' ); 17 | 18 | is $stdout, ''; 19 | like $stderr, qr/Invalid advisory id/; 20 | isnt $exit, 0; 21 | }; 22 | 23 | subtest 'command: show invalid invocation' => sub { 24 | my ( $stdout, $stderr, $exit ) = TestCommand->command('show'); 25 | 26 | is $stdout, ''; 27 | like $stderr, qr/Error: Usage:/; 28 | isnt $exit, 0; 29 | }; 30 | 31 | done_testing; 32 | -------------------------------------------------------------------------------- /t/data/carton/cpanfile.snapshot: -------------------------------------------------------------------------------- 1 | # carton snapshot format: version 1.0 2 | DISTRIBUTIONS 3 | Apache-LogFormat-Compiler-0.35 4 | pathname: K/KA/KAZEBURO/Apache-LogFormat-Compiler-0.35.tar.gz 5 | provides: 6 | Apache::LogFormat::Compiler 0.35 7 | requirements: 8 | Module::Build::Tiny 0.035 9 | POSIX 0 10 | POSIX::strftime::Compiler 0.30 11 | Time::Local 0 12 | perl 5.008001 13 | Class-Inspector-1.32 14 | pathname: P/PL/PLICEASE/Class-Inspector-1.32.tar.gz 15 | provides: 16 | Class::Inspector 1.32 17 | Class::Inspector::Functions 1.32 18 | requirements: 19 | ExtUtils::MakeMaker 0 20 | File::Spec 0.80 21 | perl 5.006 22 | Invalid-1.0 23 | pathname: Invalid 24 | -------------------------------------------------------------------------------- /t/data/cpanfile/cpanfile: -------------------------------------------------------------------------------- 1 | requires 'perl', '5.8'; 2 | requires 'Catalyst', '5'; 3 | -------------------------------------------------------------------------------- /t/data/installed/perl5/lib/perl5/Catalyst.pm: -------------------------------------------------------------------------------- 1 | =pod 2 | 3 | =head1 NAME 4 | 5 | Catalyst 6 | 7 | =cut 8 | 9 | package Catalyst ; 10 | 11 | our $VERSION = '5.0'; # No BumpVersion 12 | -------------------------------------------------------------------------------- /t/discover/cpanfile.t: -------------------------------------------------------------------------------- 1 | use strict; 2 | use warnings; 3 | use Test::More; 4 | use CPAN::Audit::Discover::Cpanfile; 5 | 6 | subtest 'discover' => sub { 7 | my @deps = _build()->discover('t/data/cpanfile/cpanfile'); 8 | 9 | is_deeply \@deps, 10 | [ 11 | { 12 | 'module' => 'Catalyst', 13 | 'version' => '5' 14 | }, 15 | ]; 16 | }; 17 | 18 | done_testing; 19 | 20 | sub _build { CPAN::Audit::Discover::Cpanfile->new(@_) } 21 | -------------------------------------------------------------------------------- /t/discover/cpanfile_snapshot.t: -------------------------------------------------------------------------------- 1 | use strict; 2 | use warnings; 3 | use Test::More; 4 | use CPAN::Audit::Discover::CpanfileSnapshot; 5 | 6 | subtest 'discover' => sub { 7 | my @deps = _build()->discover('t/data/carton/cpanfile.snapshot'); 8 | 9 | is_deeply \@deps, 10 | [ 11 | { 12 | 'dist' => 'Apache-LogFormat-Compiler', 13 | 'version' => '0.35' 14 | }, 15 | { 16 | 'version' => '1.32', 17 | 'dist' => 'Class-Inspector' 18 | } 19 | ]; 20 | }; 21 | 22 | done_testing; 23 | 24 | sub _build { CPAN::Audit::Discover::CpanfileSnapshot->new(@_) } 25 | -------------------------------------------------------------------------------- /t/installed.t: -------------------------------------------------------------------------------- 1 | use strict; 2 | use warnings; 3 | use Test::More; 4 | use CPAN::Audit::Installed; 5 | 6 | subtest 'installed' => sub { 7 | my @deps = _build( 8 | db => { 9 | module2dist => { Catalyst => 'Catalyst-Runtime' }, 10 | dists => { 11 | 'Catalyst-Runtime' => { 12 | main_module => 'Catalyst', 13 | advisories => [ 14 | { 15 | id => 'CPANSA-Catalyst-2018-01' 16 | } 17 | ] 18 | } 19 | } 20 | } 21 | )->find('t/data/installed'); 22 | 23 | is_deeply \@deps, 24 | [ 25 | { 26 | 'dist' => 'Catalyst-Runtime', 27 | 'version' => '5.0' 28 | }, 29 | ]; 30 | }; 31 | 32 | done_testing; 33 | 34 | sub _build { CPAN::Audit::Installed->new(@_) } 35 | -------------------------------------------------------------------------------- /t/lib/TestCommand.pm: -------------------------------------------------------------------------------- 1 | package TestCommand; 2 | use strict; 3 | use warnings; 4 | use Capture::Tiny qw(capture); 5 | 6 | sub command { 7 | shift; 8 | my @args = @_; 9 | 10 | my ( $stdout, $stderr, $exit ) = capture { 11 | system $^X, 'script/cpan-audit', '--no-corelist', @args; 12 | }; 13 | 14 | return ( $stdout, $stderr, $exit ); 15 | } 16 | 17 | 1; 18 | -------------------------------------------------------------------------------- /t/query.t: -------------------------------------------------------------------------------- 1 | use strict; 2 | use warnings; 3 | 4 | use Test::More; 5 | use CPAN::Audit::Query; 6 | 7 | subtest 'advisories_for' => sub { 8 | my $query = _build( 9 | db => { 10 | dists => { 11 | Foo => { 12 | advisories => [ 13 | { 14 | id => 'SA-1', 15 | package => 'Foo', 16 | affected_versions => '<1.1' 17 | }, 18 | { 19 | id => 'SA-2', 20 | package => 'Foo', 21 | affected_versions => '<1.2' 22 | }, 23 | ], 24 | versions => [ 25 | { version => '0.9' }, 26 | { version => '1.1' }, 27 | { version => '1.2' }, 28 | { version => '1.3' } 29 | ] 30 | }, 31 | } 32 | } 33 | ); 34 | 35 | is_deeply [ $query->advisories_for('Unknown') ], []; 36 | 37 | is scalar $query->advisories_for('Foo'), 2; 38 | is scalar $query->advisories_for( 'Foo', '1.1' ), 1; 39 | 40 | is_deeply [ $query->advisories_for( 'Foo', '1.3' ) ], []; 41 | 42 | is_deeply [ $query->advisories_for( 'Foo', '5' ) ], []; 43 | }; 44 | 45 | done_testing; 46 | 47 | sub _build { CPAN::Audit::Query->new(@_) } 48 | -------------------------------------------------------------------------------- /t/version.t: -------------------------------------------------------------------------------- 1 | use strict; 2 | use warnings; 3 | 4 | use Test::More; 5 | use CPAN::Audit::Version; 6 | 7 | subtest 'in_range' => sub { 8 | my $checker = _build(); 9 | 10 | ok( !$checker->in_range() ); 11 | ok( !$checker->in_range('1.2') ); 12 | 13 | ok( !$checker->in_range( 'abc', 'def' ) ); 14 | ok( !$checker->in_range( 'abc', '1.2' ) ); 15 | ok( !$checker->in_range( '1.2', 'def' ) ); 16 | 17 | ok( !$checker->in_range( '1.2', '^1.2' ) ); 18 | 19 | ok( $checker->in_range( '1.2', '' ) ); 20 | ok( $checker->in_range( '1.2', '0' ) ); 21 | 22 | ok( $checker->in_range( '1.2', '1.1' ) ); 23 | ok( $checker->in_range( '1.2', '1.2' ) ); 24 | ok( !$checker->in_range( '1.2', '1.5' ) ); 25 | 26 | ok( $checker->in_range( '1.0', '<=1.1' ) ); 27 | ok( $checker->in_range( '1.1', '<=1.1' ) ); 28 | ok( !$checker->in_range( '1.2', '<=1.1' ) ); 29 | 30 | ok( $checker->in_range( '1.0', '<1.1' ) ); 31 | ok( !$checker->in_range( '1.1', '<1.1' ) ); 32 | ok( !$checker->in_range( '1.2', '<1.1' ) ); 33 | 34 | ok( !$checker->in_range( '1.0', '>=1.1' ) ); 35 | ok( $checker->in_range( '1.1', '>=1.1' ) ); 36 | ok( $checker->in_range( '1.2', '>=1.1' ) ); 37 | 38 | ok( $checker->in_range( '1.2', '>1.1' ) ); 39 | ok( !$checker->in_range( '1.1', '>1.1' ) ); 40 | ok( !$checker->in_range( '1.0', '>1.1' ) ); 41 | 42 | ok( $checker->in_range( '1.0', '==1.0' ) ); 43 | ok( !$checker->in_range( '1.0', '==1.1' ) ); 44 | 45 | ok( $checker->in_range( '1.0', '!=1.1' ) ); 46 | ok( !$checker->in_range( '1.0', '!=1.0' ) ); 47 | 48 | ok( $checker->in_range( '5', '>= 1.1, < 6' ) ); 49 | ok( !$checker->in_range( '5', '>= 1.1, < 4' ) ); 50 | }; 51 | 52 | subtest 'affected_versions' => sub { 53 | my $checker = _build(); 54 | 55 | is_deeply( 56 | [ 57 | $checker->affected_versions( 58 | [ '1.2', '1.3', '2.0' ], 59 | '>= 1.2, <= 1.5' 60 | ) 61 | ], 62 | [ '1.2', '1.3' ] 63 | ); 64 | }; 65 | 66 | done_testing; 67 | 68 | sub _build { CPAN::Audit::Version->new } 69 | -------------------------------------------------------------------------------- /util/generate: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env perl 2 | 3 | use strict; 4 | use warnings; 5 | 6 | use JSON (); 7 | use File::Basename qw(basename); 8 | use YAML::Tiny; 9 | use HTTP::Tiny; 10 | use Data::Dumper; 11 | use CPAN::DistnameInfo; 12 | 13 | local $Data::Dumper::Sortkeys = 1; 14 | 15 | my @files = @ARGV; 16 | die "Usage: \n" unless @files; 17 | 18 | my %db; 19 | foreach my $file (@files) { 20 | my $yaml = YAML::Tiny->read($file)->[0]; 21 | 22 | my $dist = basename $file; 23 | $dist =~ s{^CPANSA-}{}; 24 | $dist =~ s{\.yml$}{}; 25 | 26 | $db{dists}->{$dist}->{advisories} = $yaml; 27 | } 28 | 29 | provides( \%db ); 30 | 31 | foreach my $dist ( keys %{ $db{dists} } ) { 32 | $db{dists}->{$dist}->{versions} = [ all_releases($dist) ]; 33 | $db{dists}->{$dist}->{main_module} = release($dist)->{main_module}; 34 | } 35 | 36 | my $dump = Dumper( \%db ); 37 | $dump =~ s{^\$VAR1\s*=\s*}{}; 38 | $dump =~ s{}{}; 39 | 40 | print <<"EOF"; 41 | package CPAN::Audit::DB; 42 | 43 | use strict; 44 | use warnings; 45 | 46 | sub db { 47 | $dump 48 | } 49 | 50 | 1; 51 | EOF 52 | 53 | sub provides { 54 | my ($db) = @_; 55 | 56 | my $ua = HTTP::Tiny->new; 57 | 58 | my $details_file = '/tmp/02packages.details.txt.gz'; 59 | $ua->mirror( 'http://www.cpan.org//modules/02packages.details.txt.gz', 60 | $details_file ); 61 | 62 | open my $fh, '<:gzip', $details_file 63 | or die "Can't open '$details_file': $!"; 64 | 65 | while ( defined( my $line = <$fh> ) ) { 66 | chomp $line; 67 | 68 | last if $line eq ''; 69 | } 70 | 71 | while ( defined( my $line = <$fh> ) ) { 72 | my ( $module, $version, $pathname ) = split /\s+/, $line; 73 | next unless $module && $pathname; 74 | 75 | my $dist_info = CPAN::DistnameInfo->new($pathname); 76 | next unless $dist_info; 77 | 78 | my $author = $dist_info->cpanid; 79 | my $dist = $dist_info->dist; 80 | my $name = $dist_info->distvname; 81 | 82 | next unless $dist; 83 | 84 | next unless $db->{dists}->{$dist}; 85 | 86 | $db->{module2dist}->{$module} = $dist; 87 | } 88 | 89 | close $fh; 90 | } 91 | 92 | sub release { 93 | my ($distribution) = @_; 94 | 95 | my $response = HTTP::Tiny->new->get( 96 | 'http://fastapi.metacpan.org/v1/release/' . $distribution ); 97 | 98 | my $content_json = JSON::decode_json( $response->{content} ); 99 | 100 | return $content_json; 101 | } 102 | 103 | sub all_releases { 104 | my ($distribution) = @_; 105 | 106 | my $response = HTTP::Tiny->new->post( 107 | 'http://fastapi.metacpan.org/v1/release/_search', 108 | { 109 | headers => { 'Content-Type' => 'application/json' }, 110 | content => JSON::encode_json( 111 | { 112 | size => 5000, 113 | fields => [ 'date', 'version' ], 114 | filter => { 115 | term => { distribution => $distribution } 116 | } 117 | } 118 | ) 119 | } 120 | ); 121 | 122 | my $content_json = JSON::decode_json( $response->{content} ); 123 | 124 | my @results = 125 | sort { $a->{date} cmp $b->{date} } 126 | map { $_->{fields} } @{ $content_json->{hits}->{hits} }; 127 | return unless @results; 128 | 129 | return @results; 130 | } 131 | --------------------------------------------------------------------------------