├── .gitignore ├── README.md ├── app ├── .htaccess ├── AppCache.php ├── AppKernel.php ├── Resources │ └── views │ │ ├── base.html.twig │ │ └── default │ │ └── index.html.twig ├── autoload.php └── config │ ├── config.yml │ ├── config_dev.yml │ ├── config_prod.yml │ ├── config_test.yml │ ├── parameters.yml.dist │ ├── routing.yml │ ├── routing_dev.yml │ ├── security.yml │ └── services.yml ├── bin ├── console └── symfony_requirements ├── composer.json ├── composer.lock ├── phpunit.xml.dist ├── process-questions.sh ├── src ├── .htaccess └── AppBundle │ ├── AppBundle.php │ ├── BaseService.php │ ├── CacheService.php │ ├── Command │ ├── CrawlQuestionsCommand.php │ ├── FindUsersCommand.php │ ├── FindWinnersCommand.php │ ├── GenerateStaticWebsiteCommand.php │ ├── PopulatePlaceTablesCommand.php │ ├── RefreshQuestionsCommand.php │ └── RefreshUsersCommand.php │ ├── Controller │ └── DefaultController.php │ ├── CountryUtils.php │ ├── Eloquent.php.sample │ ├── InjectionFinder.php │ ├── Model │ ├── City.php │ ├── Country.php │ ├── Place.php │ ├── Question.php │ └── User.php │ ├── QuestionCrawler.php │ ├── ReportService.php │ ├── StackExchangeApi.php │ └── UserFinder.php ├── structure.sql ├── testimport.sublime-project ├── tests └── AppBundle │ └── Controller │ └── DefaultControllerTest.php ├── var ├── SymfonyRequirements.php ├── cache │ └── .gitkeep ├── logs │ └── .gitkeep ├── places │ └── README.md └── sessions │ └── .gitkeep └── web ├── .htaccess ├── app.php ├── app_dev.php ├── apple-touch-icon.png ├── config.php ├── favicon.ico ├── index_template.html └── robots.txt /.gitignore: -------------------------------------------------------------------------------- 1 | /app/config/parameters.yml 2 | /build/ 3 | /phpunit.xml 4 | /var/* 5 | !/var/cache 6 | /var/cache/* 7 | !/var/places 8 | /var/places/* 9 | !/var/places/README.md 10 | !var/cache/.gitkeep 11 | !/var/logs 12 | /var/logs/* 13 | !var/logs/.gitkeep 14 | !/var/sessions 15 | /var/sessions/* 16 | !var/sessions/.gitkeep 17 | !var/SymfonyRequirements.php 18 | /vendor/ 19 | /web/bundles/ 20 | /src/AppBundle/Eloquent.php 21 | INFO.md 22 | crawl-question*.log 23 | get-question-count.php 24 | web/index.html 25 | TODO.md 26 | *.sublime-workspace -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # so-sql-injections 2 | 3 | SQL injection vulnerabilities in Stack Overflow PHP questions 4 | 5 | https://laurent22.github.io/so-injections/ 6 | 7 | License: MIT 8 | -------------------------------------------------------------------------------- /app/.htaccess: -------------------------------------------------------------------------------- 1 | 2 | Require all denied 3 | 4 | 5 | Order deny,allow 6 | Deny from all 7 | 8 | -------------------------------------------------------------------------------- /app/AppCache.php: -------------------------------------------------------------------------------- 1 | getEnvironment(), ['dev', 'test'], true)) { 22 | $bundles[] = new Symfony\Bundle\DebugBundle\DebugBundle(); 23 | $bundles[] = new Symfony\Bundle\WebProfilerBundle\WebProfilerBundle(); 24 | $bundles[] = new Sensio\Bundle\DistributionBundle\SensioDistributionBundle(); 25 | $bundles[] = new Sensio\Bundle\GeneratorBundle\SensioGeneratorBundle(); 26 | } 27 | 28 | return $bundles; 29 | } 30 | 31 | public function getRootDir() 32 | { 33 | return __DIR__; 34 | } 35 | 36 | public function getCacheDir() 37 | { 38 | return dirname(__DIR__).'/var/cache/'.$this->getEnvironment(); 39 | } 40 | 41 | public function getLogDir() 42 | { 43 | return dirname(__DIR__).'/var/logs'; 44 | } 45 | 46 | public function registerContainerConfiguration(LoaderInterface $loader) 47 | { 48 | $loader->load($this->getRootDir().'/config/config_'.$this->getEnvironment().'.yml'); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /app/Resources/views/base.html.twig: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | {% block title %}Welcome!{% endblock %} 6 | {% block stylesheets %}{% endblock %} 7 | 8 | 9 | 10 | {% block body %}{% endblock %} 11 | {% block javascripts %}{% endblock %} 12 | 13 | 14 | -------------------------------------------------------------------------------- /app/Resources/views/default/index.html.twig: -------------------------------------------------------------------------------- 1 | {% extends 'base.html.twig' %} 2 | 3 | {% block body %} 4 |
5 |
6 |
7 |

Welcome to Symfony {{ constant('Symfony\\Component\\HttpKernel\\Kernel::VERSION') }}

8 |
9 | 10 |
11 |

12 | 13 | 14 | Your application is now ready. You can start working on it at: 15 | {{ base_dir }}/ 16 |

17 |
18 | 19 | 44 | 45 |
46 |
47 | {% endblock %} 48 | 49 | {% block stylesheets %} 50 | 76 | {% endblock %} 77 | -------------------------------------------------------------------------------- /app/autoload.php: -------------------------------------------------------------------------------- 1 | getParameterOption(['--env', '-e'], getenv('SYMFONY_ENV') ?: 'dev'); 21 | $debug = getenv('SYMFONY_DEBUG') !== '0' && !$input->hasParameterOption(['--no-debug', '']) && $env !== 'prod'; 22 | 23 | if ($debug) { 24 | Debug::enable(); 25 | } 26 | 27 | $kernel = new AppKernel($env, $debug); 28 | $application = new Application($kernel); 29 | $application->run($input); 30 | -------------------------------------------------------------------------------- /bin/symfony_requirements: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env php 2 | getPhpIniConfigPath(); 9 | 10 | echo_title('Symfony Requirements Checker'); 11 | 12 | echo '> PHP is using the following php.ini file:'.PHP_EOL; 13 | if ($iniPath) { 14 | echo_style('green', ' '.$iniPath); 15 | } else { 16 | echo_style('yellow', ' WARNING: No configuration file (php.ini) used by PHP!'); 17 | } 18 | 19 | echo PHP_EOL.PHP_EOL; 20 | 21 | echo '> Checking Symfony requirements:'.PHP_EOL.' '; 22 | 23 | $messages = array(); 24 | foreach ($symfonyRequirements->getRequirements() as $req) { 25 | if ($helpText = get_error_message($req, $lineSize)) { 26 | echo_style('red', 'E'); 27 | $messages['error'][] = $helpText; 28 | } else { 29 | echo_style('green', '.'); 30 | } 31 | } 32 | 33 | $checkPassed = empty($messages['error']); 34 | 35 | foreach ($symfonyRequirements->getRecommendations() as $req) { 36 | if ($helpText = get_error_message($req, $lineSize)) { 37 | echo_style('yellow', 'W'); 38 | $messages['warning'][] = $helpText; 39 | } else { 40 | echo_style('green', '.'); 41 | } 42 | } 43 | 44 | if ($checkPassed) { 45 | echo_block('success', 'OK', 'Your system is ready to run Symfony projects'); 46 | } else { 47 | echo_block('error', 'ERROR', 'Your system is not ready to run Symfony projects'); 48 | 49 | echo_title('Fix the following mandatory requirements', 'red'); 50 | 51 | foreach ($messages['error'] as $helpText) { 52 | echo ' * '.$helpText.PHP_EOL; 53 | } 54 | } 55 | 56 | if (!empty($messages['warning'])) { 57 | echo_title('Optional recommendations to improve your setup', 'yellow'); 58 | 59 | foreach ($messages['warning'] as $helpText) { 60 | echo ' * '.$helpText.PHP_EOL; 61 | } 62 | } 63 | 64 | echo PHP_EOL; 65 | echo_style('title', 'Note'); 66 | echo ' The command console could use a different php.ini file'.PHP_EOL; 67 | echo_style('title', '~~~~'); 68 | echo ' than the one used with your web server. To be on the'.PHP_EOL; 69 | echo ' safe side, please check the requirements from your web'.PHP_EOL; 70 | echo ' server using the '; 71 | echo_style('yellow', 'web/config.php'); 72 | echo ' script.'.PHP_EOL; 73 | echo PHP_EOL; 74 | 75 | exit($checkPassed ? 0 : 1); 76 | 77 | function get_error_message(Requirement $requirement, $lineSize) 78 | { 79 | if ($requirement->isFulfilled()) { 80 | return; 81 | } 82 | 83 | $errorMessage = wordwrap($requirement->getTestMessage(), $lineSize - 3, PHP_EOL.' ').PHP_EOL; 84 | $errorMessage .= ' > '.wordwrap($requirement->getHelpText(), $lineSize - 5, PHP_EOL.' > ').PHP_EOL; 85 | 86 | return $errorMessage; 87 | } 88 | 89 | function echo_title($title, $style = null) 90 | { 91 | $style = $style ?: 'title'; 92 | 93 | echo PHP_EOL; 94 | echo_style($style, $title.PHP_EOL); 95 | echo_style($style, str_repeat('~', strlen($title)).PHP_EOL); 96 | echo PHP_EOL; 97 | } 98 | 99 | function echo_style($style, $message) 100 | { 101 | // ANSI color codes 102 | $styles = array( 103 | 'reset' => "\033[0m", 104 | 'red' => "\033[31m", 105 | 'green' => "\033[32m", 106 | 'yellow' => "\033[33m", 107 | 'error' => "\033[37;41m", 108 | 'success' => "\033[37;42m", 109 | 'title' => "\033[34m", 110 | ); 111 | $supports = has_color_support(); 112 | 113 | echo($supports ? $styles[$style] : '').$message.($supports ? $styles['reset'] : ''); 114 | } 115 | 116 | function echo_block($style, $title, $message) 117 | { 118 | $message = ' '.trim($message).' '; 119 | $width = strlen($message); 120 | 121 | echo PHP_EOL.PHP_EOL; 122 | 123 | echo_style($style, str_repeat(' ', $width)); 124 | echo PHP_EOL; 125 | echo_style($style, str_pad(' ['.$title.']', $width, ' ', STR_PAD_RIGHT)); 126 | echo PHP_EOL; 127 | echo_style($style, $message); 128 | echo PHP_EOL; 129 | echo_style($style, str_repeat(' ', $width)); 130 | echo PHP_EOL; 131 | } 132 | 133 | function has_color_support() 134 | { 135 | static $support; 136 | 137 | if (null === $support) { 138 | if (DIRECTORY_SEPARATOR == '\\') { 139 | $support = false !== getenv('ANSICON') || 'ON' === getenv('ConEmuANSI'); 140 | } else { 141 | $support = function_exists('posix_isatty') && @posix_isatty(STDOUT); 142 | } 143 | } 144 | 145 | return $support; 146 | } 147 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "laurent/testimport2", 3 | "license": "proprietary", 4 | "type": "project", 5 | "autoload": { 6 | "psr-4": { 7 | "": "src/" 8 | }, 9 | "classmap": [ 10 | "app/AppKernel.php", 11 | "app/AppCache.php" 12 | ] 13 | }, 14 | "autoload-dev": { 15 | "psr-4": { 16 | "Tests\\": "tests/" 17 | } 18 | }, 19 | "require": { 20 | "php": ">=5.5.9", 21 | "symfony/symfony": "3.1.*", 22 | "doctrine/orm": "^2.5", 23 | "doctrine/doctrine-bundle": "^1.6", 24 | "doctrine/doctrine-cache-bundle": "^1.2", 25 | "symfony/swiftmailer-bundle": "^2.3", 26 | "symfony/monolog-bundle": "^2.8", 27 | "symfony/polyfill-apcu": "^1.0", 28 | "sensio/distribution-bundle": "^5.0", 29 | "sensio/framework-extra-bundle": "^3.0.2", 30 | "incenteev/composer-parameter-handler": "^2.0", 31 | "illuminate/database": "*" 32 | }, 33 | "require-dev": { 34 | "sensio/generator-bundle": "^3.0", 35 | "symfony/phpunit-bridge": "^3.0" 36 | }, 37 | "scripts": { 38 | "symfony-scripts": [ 39 | "Incenteev\\ParameterHandler\\ScriptHandler::buildParameters", 40 | "Sensio\\Bundle\\DistributionBundle\\Composer\\ScriptHandler::buildBootstrap", 41 | "Sensio\\Bundle\\DistributionBundle\\Composer\\ScriptHandler::clearCache", 42 | "Sensio\\Bundle\\DistributionBundle\\Composer\\ScriptHandler::installAssets", 43 | "Sensio\\Bundle\\DistributionBundle\\Composer\\ScriptHandler::installRequirementsFile", 44 | "Sensio\\Bundle\\DistributionBundle\\Composer\\ScriptHandler::prepareDeploymentTarget" 45 | ], 46 | "post-install-cmd": [ 47 | "@symfony-scripts" 48 | ], 49 | "post-update-cmd": [ 50 | "@symfony-scripts" 51 | ] 52 | }, 53 | "extra": { 54 | "symfony-app-dir": "app", 55 | "symfony-bin-dir": "bin", 56 | "symfony-var-dir": "var", 57 | "symfony-web-dir": "web", 58 | "symfony-tests-dir": "tests", 59 | "symfony-assets-install": "relative", 60 | "incenteev-parameters": { 61 | "file": "app/config/parameters.yml" 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /phpunit.xml.dist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | tests 18 | 19 | 20 | 21 | 22 | 23 | src 24 | 25 | src/*Bundle/Resources 26 | src/*/*Bundle/Resources 27 | src/*/Bundle/*Bundle/Resources 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /process-questions.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | PHP_CMD=/usr/bin/php7.1 5 | 6 | SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" 7 | 8 | echo "app:crawl-questions" 9 | $PHP_CMD $SCRIPT_DIR/bin/console -vvv app:crawl-questions >> "$SCRIPT_DIR/crawl-question-3.log" 2>&1 10 | echo "app:find-winners" 11 | $PHP_CMD $SCRIPT_DIR/bin/console -vvv app:find-winners 12 | echo "app:find-users" 13 | $PHP_CMD $SCRIPT_DIR/bin/console -vvv app:find-users 14 | 15 | $PHP_CMD $SCRIPT_DIR/bin/console -vvv app:generate-static-website 16 | cp "$SCRIPT_DIR/web/index.html" /var/www/laurent22.github.io/so-injections/ 17 | cd /var/www/laurent22.github.io/so-injections/ 18 | git add . 19 | git commit -m "script auto-update" || true 20 | git push 21 | cd - -------------------------------------------------------------------------------- /src/.htaccess: -------------------------------------------------------------------------------- 1 | 2 | Require all denied 3 | 4 | 5 | Order deny,allow 6 | Deny from all 7 | 8 | -------------------------------------------------------------------------------- /src/AppBundle/AppBundle.php: -------------------------------------------------------------------------------- 1 | output_ = $output; 11 | } 12 | 13 | protected function writeln($s) { 14 | if (!$this->output_) return; 15 | $this->output_->writeln($this->formatLine($s)); 16 | } 17 | 18 | protected function write($s) { 19 | if (!$this->output_) return; 20 | $this->output_->write($this->formatLine($s)); 21 | } 22 | 23 | private function formatLine($s) { 24 | return date('Y-m-d H:i:s', time()) . ': ' . $s; 25 | } 26 | 27 | } -------------------------------------------------------------------------------- /src/AppBundle/CacheService.php: -------------------------------------------------------------------------------- 1 | adapter_) $this->adapter_ = new FilesystemAdapter(); 14 | return $this->adapter_; 15 | } 16 | 17 | public function get($k) { 18 | $entry = $this->adapter()->getItem(md5($k)); 19 | return $entry->isHit() ? json_decode($entry->get(), true) : null; 20 | } 21 | 22 | public function set($k, $v, $expiryTime = null) { 23 | $entry = $this->adapter()->getItem(md5($k)); 24 | $entry->set(json_encode($v)); 25 | if ($expiryTime) $entry->expiresAfter($expiryTime); 26 | $this->adapter()->save($entry); 27 | } 28 | 29 | public function getOrSet($k, $func, $expiryTime = null) { 30 | $v = $this->get($k); 31 | if ($v === null) { 32 | $v = $func(); 33 | $this->set($k, $v, $expiryTime); 34 | } 35 | return $v; 36 | } 37 | 38 | } -------------------------------------------------------------------------------- /src/AppBundle/Command/CrawlQuestionsCommand.php: -------------------------------------------------------------------------------- 1 | setName('app:crawl-questions'); 14 | $this->setDescription('Crawl PHP questions from Stack Overflow and save them to the database.'); 15 | } 16 | 17 | protected function execute(InputInterface $input, OutputInterface $output) { 18 | $crawler = $this->getContainer()->get('app.question_crawler'); 19 | $crawler->setOutput($output); 20 | $crawler->execute(); 21 | } 22 | 23 | } -------------------------------------------------------------------------------- /src/AppBundle/Command/FindUsersCommand.php: -------------------------------------------------------------------------------- 1 | setName('app:find-users'); 14 | $this->setDescription('Extract users from the questions stored in the database.'); 15 | } 16 | 17 | protected function execute(InputInterface $input, OutputInterface $output) { 18 | $crawler = $this->getContainer()->get('app.user_finder'); 19 | $crawler->setOutput($output); 20 | $crawler->execute(); 21 | } 22 | 23 | } -------------------------------------------------------------------------------- /src/AppBundle/Command/FindWinnersCommand.php: -------------------------------------------------------------------------------- 1 | setName('app:find-winners'); 14 | $this->setDescription('Searches in the database for questions that contain SQL injections.'); 15 | } 16 | 17 | protected function execute(InputInterface $input, OutputInterface $output) { 18 | $service = $this->getContainer()->get('app.injection_finder'); 19 | $service->setOutput($output); 20 | $service->execute(); 21 | } 22 | 23 | } -------------------------------------------------------------------------------- /src/AppBundle/Command/GenerateStaticWebsiteCommand.php: -------------------------------------------------------------------------------- 1 | setName('app:generate-static-website'); 14 | $this->setDescription('Generate static website.'); 15 | } 16 | 17 | protected function execute(InputInterface $input, OutputInterface $output) { 18 | $service = $this->getContainer()->get('app.report_service'); 19 | $service->setOutput($output); 20 | 21 | $rootDir = $this->getContainer()->get('kernel')->getRootDir(); 22 | $content = file_get_contents(dirname($rootDir) . '/web/index_template.html'); 23 | $outputFile = dirname($rootDir) . '/web/index.html'; 24 | 25 | $content = str_replace('[MONTHLY_REPORT]', json_encode($service->monthlyInjections()), $content); 26 | $content = str_replace('[COUNTRY_REPORT]', json_encode($service->sqlInjectionsPerCountry()), $content); 27 | $content = str_replace('[LATEST_INJECTIONS]', json_encode($service->latestInjections()), $content); 28 | $content = str_replace('[GENERATION_TIMESTAMP]', json_encode(time()), $content); 29 | 30 | file_put_contents($outputFile, $content); 31 | } 32 | 33 | } -------------------------------------------------------------------------------- /src/AppBundle/Command/PopulatePlaceTablesCommand.php: -------------------------------------------------------------------------------- 1 | setName('app:populate-place-tables'); 17 | $this->setDescription('Populate place tables with data.'); 18 | } 19 | 20 | protected function execute(InputInterface $input, OutputInterface $output) { 21 | $e = $this->getContainer()->get('app.eloquent'); 22 | $con = $e->connection(); 23 | 24 | $rootDir = $this->getContainer()->get('kernel')->getRootDir(); 25 | $placesDir = dirname($rootDir) . '/var/places'; 26 | 27 | $con->getPdo()->query('TRUNCATE countries'); 28 | $con->getPdo()->query('TRUNCATE cities'); 29 | $con->getPdo()->query('TRUNCATE places'); 30 | 31 | // --------------------------------------------------------------------------- 32 | // Countries 33 | // --------------------------------------------------------------------------- 34 | 35 | $con->beginTransaction(); 36 | 37 | $countryGeonameIds = array(); 38 | $countryCodeToIds = array(); 39 | $file = fopen($placesDir . '/countryInfo.txt', 'r'); 40 | if (!$file) throw new \Exception('Cannot open file'); 41 | while (($line = fgets($file)) !== false) { 42 | $line = trim($line); 43 | if ($line == '' || $line[0] == '#') continue; 44 | $items = explode("\t", trim($line)); 45 | $isoCode = $items[0]; 46 | $geonameId = $items[16]; 47 | $c = new Country(); 48 | $c->code = $isoCode; 49 | $c->geoname_id = $geonameId; 50 | $c->save(); 51 | 52 | $countryGeonameIds[] = $geonameId; 53 | $countryCodeToIds[$isoCode] = $geonameId; 54 | } 55 | fclose($file); 56 | 57 | $con->commit(); 58 | 59 | // $s = ''; 60 | // foreach ($countryGeonameIds as $id) { 61 | // if ($s != '') $s .= '|'; 62 | // $s .= $id; 63 | // } 64 | // $s = sprintf('grep -P \'^(%s)\t\' allCountries.txt > countries_only.txt', $s); 65 | // echo $s . "\n"; die(); 66 | 67 | // --------------------------------------------------------------------------- 68 | // Country names 69 | // --------------------------------------------------------------------------- 70 | 71 | $con->beginTransaction(); 72 | 73 | $file = fopen($placesDir . '/countries_only.txt', 'r'); 74 | if (!$file) throw new \Exception('Cannot open file'); 75 | while (($line = fgets($file)) !== false) { 76 | $line = trim($line); 77 | if ($line == '' || $line[0] == '#') continue; 78 | $items = explode("\t", trim($line)); 79 | 80 | $geonameId = trim($items[0]); 81 | $names = trim($items[3]); 82 | if (!empty($names)) { 83 | $names = explode(',', $names); 84 | $names = array_merge(array($items[1]), $names); 85 | } else { 86 | $names = array($items[1]); 87 | } 88 | 89 | $names = array_unique($names); 90 | 91 | foreach ($names as $name) { 92 | $name = trim($name); 93 | if (empty($name)) continue; 94 | $place = new Place(); 95 | $place->city_id = null; 96 | $place->country_id = $geonameId; 97 | $place->name = $name; 98 | $place->save(); 99 | } 100 | } 101 | fclose($file); 102 | 103 | $con->commit(); 104 | 105 | // --------------------------------------------------------------------------- 106 | // Cities 107 | // --------------------------------------------------------------------------- 108 | 109 | $con->beginTransaction(); 110 | 111 | $addCount = 0; 112 | $commitCount = 0; 113 | $file = fopen($placesDir . '/cities1000.txt', 'r'); 114 | if (!$file) throw new \Exception('Cannot open file'); 115 | while (($line = fgets($file)) !== false) { 116 | $line = trim($line); 117 | if ($line == '' || $line[0] == '#') continue; 118 | $items = explode("\t", trim($line)); 119 | 120 | $geonameId = trim($items[0]); 121 | $names = trim($items[3]); 122 | if (!empty($names)) { 123 | $names = explode(',', $names); 124 | $names = array_merge(array($items[1]), $names); 125 | } else { 126 | $names = array($items[1]); 127 | } 128 | 129 | $names = array_unique($names); 130 | 131 | $countryCode = $items[8]; 132 | if (!isset($countryCodeToIds[$countryCode])) throw new \Exception('Cannot find country code: ' . $countryCode); 133 | $countryId = $countryCodeToIds[$countryCode]; 134 | 135 | $city = new City(); 136 | $city->geoname_id = $geonameId; 137 | $city->country_id = $countryId; 138 | $city->save(); 139 | 140 | foreach ($names as $name) { 141 | $place = new Place(); 142 | $place->city_id = $geonameId; 143 | $place->country_id = null; 144 | $place->name = $name; 145 | $place->save(); 146 | } 147 | 148 | $addCount++; 149 | if ($addCount >= 10000) { 150 | $addCount = 0; 151 | $commitCount++; 152 | echo $commitCount . "\n"; 153 | $con->commit(); 154 | $con->beginTransaction(); 155 | } 156 | } 157 | fclose($file); 158 | 159 | $con->commit(); 160 | } 161 | 162 | } -------------------------------------------------------------------------------- /src/AppBundle/Command/RefreshQuestionsCommand.php: -------------------------------------------------------------------------------- 1 | setName('app:refresh-questions'); 14 | $this->setDescription('Refresh local questions based on stored JSON object.'); 15 | } 16 | 17 | protected function execute(InputInterface $input, OutputInterface $output) { 18 | $crawler = $this->getContainer()->get('app.question_crawler'); 19 | $crawler->setOutput($output); 20 | $crawler->refreshQuestions(); 21 | } 22 | 23 | } -------------------------------------------------------------------------------- /src/AppBundle/Command/RefreshUsersCommand.php: -------------------------------------------------------------------------------- 1 | setName('app:refresh-users'); 14 | $this->setDescription('Refresh local users based on stored JSON object.'); 15 | } 16 | 17 | protected function execute(InputInterface $input, OutputInterface $output) { 18 | $crawler = $this->getContainer()->get('app.user_finder'); 19 | $crawler->setOutput($output); 20 | $crawler->refreshUsers(); 21 | } 22 | 23 | } -------------------------------------------------------------------------------- /src/AppBundle/Controller/DefaultController.php: -------------------------------------------------------------------------------- 1 | get('app.eloquent')->connection(); 17 | $users = $db->table('questions')->where('question_id', '=', 61784)->get(); 18 | 19 | var_dump($users);die(); 20 | 21 | // replace this example code with whatever you need 22 | return $this->render('default/index.html.twig', [ 23 | 'base_dir' => realpath($this->getParameter('kernel.root_dir').'/..'), 24 | ]); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/AppBundle/CountryUtils.php: -------------------------------------------------------------------------------- 1 | array('Afghanistan'), 9 | 'AL' => array('Albania'), 10 | 'DZ' => array('Algeria'), 11 | 'AS' => array('American Samoa'), 12 | 'AD' => array('Andorra'), 13 | 'AO' => array('Angola'), 14 | 'AI' => array('Anguilla'), 15 | 'AQ' => array('Antarctica'), 16 | 'AG' => array('Antigua and Barbuda'), 17 | 'AR' => array('Argentina'), 18 | 'AM' => array('Armenia'), 19 | 'AW' => array('Aruba'), 20 | 'AU' => array('Australia'), 21 | 'AT' => array('Austria'), 22 | 'AZ' => array('Azerbaijan'), 23 | 'BS' => array('Bahamas'), 24 | 'BH' => array('Bahrain'), 25 | 'BD' => array('Bangladesh'), 26 | 'BB' => array('Barbados'), 27 | 'BY' => array('Belarus'), 28 | 'BE' => array('Belgium'), 29 | 'BZ' => array('Belize'), 30 | 'BJ' => array('Benin'), 31 | 'BM' => array('Bermuda'), 32 | 'BT' => array('Bhutan'), 33 | 'BO' => array('Bolivia'), 34 | 'BA' => array('Bosnia and Herzegovina'), 35 | 'BW' => array('Botswana'), 36 | 'BV' => array('Bouvet Island'), 37 | 'BR' => array('Brazil'), 38 | 'IO' => array('British Indian Ocean Territory'), 39 | 'BN' => array('Brunei Darussalam'), 40 | 'BG' => array('Bulgaria'), 41 | 'BF' => array('Burkina Faso'), 42 | 'BI' => array('Burundi'), 43 | 'KH' => array('Cambodia'), 44 | 'CM' => array('Cameroon'), 45 | 'CA' => array('Canada'), 46 | 'CV' => array('Cape Verde'), 47 | 'KY' => array('Cayman Islands'), 48 | 'CF' => array('Central African Republic'), 49 | 'TD' => array('Chad'), 50 | 'CL' => array('Chile'), 51 | 'CN' => array('China'), 52 | 'CX' => array('Christmas Island'), 53 | 'CC' => array('Cocos (Keeling) Islands'), 54 | 'CO' => array('Colombia'), 55 | 'KM' => array('Comoros'), 56 | 'CG' => array('Congo'), 57 | 'CD' => array('Congo, the Democratic Republic of the'), 58 | 'CK' => array('Cook Islands'), 59 | 'CR' => array('Costa Rica'), 60 | 'CI' => array('Cote d\'Ivoire'), 61 | 'HR' => array('Croatia'), 62 | 'CU' => array('Cuba'), 63 | 'CY' => array('Cyprus'), 64 | 'CZ' => array('Czech Republic'), 65 | 'DK' => array('Denmark'), 66 | 'DJ' => array('Djibouti'), 67 | 'DM' => array('Dominica'), 68 | 'DO' => array('Dominican Republic'), 69 | 'EC' => array('Ecuador'), 70 | 'EG' => array('Egypt'), 71 | 'SV' => array('El Salvador'), 72 | 'GQ' => array('Equatorial Guinea'), 73 | 'ER' => array('Eritrea'), 74 | 'EE' => array('Estonia'), 75 | 'ET' => array('Ethiopia'), 76 | 'FK' => array('Falkland Islands (Malvinas)'), 77 | 'FO' => array('Faroe Islands'), 78 | 'FJ' => array('Fiji'), 79 | 'FI' => array('Finland'), 80 | 'FR' => array('France'), 81 | 'GF' => array('French Guiana'), 82 | 'PF' => array('French Polynesia'), 83 | 'TF' => array('French Southern Territories'), 84 | 'GA' => array('Gabon'), 85 | 'GM' => array('Gambia'), 86 | 'GE' => array('Georgia'), 87 | 'DE' => array('Germany', 'Deutschland'), 88 | 'GH' => array('Ghana'), 89 | 'GI' => array('Gibraltar'), 90 | 'GR' => array('Greece'), 91 | 'GL' => array('Greenland'), 92 | 'GD' => array('Grenada'), 93 | 'GP' => array('Guadeloupe'), 94 | 'GU' => array('Guam'), 95 | 'GT' => array('Guatemala'), 96 | 'GN' => array('Guinea'), 97 | 'GW' => array('Guinea-Bissau'), 98 | 'GY' => array('Guyana'), 99 | 'HT' => array('Haiti'), 100 | 'HM' => array('Heard Island and Mcdonald Islands'), 101 | 'VA' => array('Holy See (Vatican City State)'), 102 | 'HN' => array('Honduras'), 103 | 'HK' => array('Hong Kong'), 104 | 'HU' => array('Hungary'), 105 | 'IS' => array('Iceland'), 106 | 'IN' => array('India'), 107 | 'ID' => array('Indonesia'), 108 | 'IR' => array('Iran, Islamic Republic of'), 109 | 'IQ' => array('Iraq'), 110 | 'IE' => array('Ireland'), 111 | 'IL' => array('Israel'), 112 | 'IT' => array('Italy'), 113 | 'JM' => array('Jamaica'), 114 | 'JP' => array('Japan'), 115 | 'JO' => array('Jordan'), 116 | 'KZ' => array('Kazakhstan'), 117 | 'KE' => array('Kenya'), 118 | 'KI' => array('Kiribati'), 119 | 'KP' => array('Korea, Democratic People\'s Republic of'), 120 | 'KR' => array('Korea, Republic of'), 121 | 'KW' => array('Kuwait'), 122 | 'KG' => array('Kyrgyzstan'), 123 | 'LA' => array('Lao People\'s Democratic Republic'), 124 | 'LV' => array('Latvia', 'Republic of Latvia'), 125 | 'LB' => array('Lebanon'), 126 | 'LS' => array('Lesotho'), 127 | 'LR' => array('Liberia'), 128 | 'LY' => array('Libyan Arab Jamahiriya'), 129 | 'LI' => array('Liechtenstein'), 130 | 'LT' => array('Lithuania'), 131 | 'LU' => array('Luxembourg'), 132 | 'MO' => array('Macao', 'Macau'), 133 | 'MK' => array('Macedonia, the Former Yugoslav Republic of', 'Macedonia'), 134 | 'MG' => array('Madagascar'), 135 | 'MW' => array('Malawi'), 136 | 'MY' => array('Malaysia'), 137 | 'MV' => array('Maldives'), 138 | 'ML' => array('Mali'), 139 | 'MT' => array('Malta'), 140 | 'MH' => array('Marshall Islands'), 141 | 'MQ' => array('Martinique'), 142 | 'MR' => array('Mauritania'), 143 | 'MU' => array('Mauritius'), 144 | 'YT' => array('Mayotte'), 145 | 'MX' => array('Mexico'), 146 | 'FM' => array('Micronesia, Federated States of'), 147 | 'MD' => array('Moldova, Republic of'), 148 | 'MC' => array('Monaco'), 149 | 'MN' => array('Mongolia'), 150 | 'MS' => array('Montserrat'), 151 | 'MA' => array('Morocco'), 152 | 'MZ' => array('Mozambique'), 153 | 'MM' => array('Myanmar'), 154 | 'NA' => array('Namibia'), 155 | 'NR' => array('Nauru'), 156 | 'NP' => array('Nepal'), 157 | 'NL' => array('Netherlands', 'The Netherlands'), 158 | 'AN' => array('Netherlands Antilles'), 159 | 'NC' => array('New Caledonia'), 160 | 'NZ' => array('New Zealand'), 161 | 'NI' => array('Nicaragua'), 162 | 'NE' => array('Niger'), 163 | 'NG' => array('Nigeria'), 164 | 'NU' => array('Niue'), 165 | 'NF' => array('Norfolk Island'), 166 | 'MP' => array('Northern Mariana Islands'), 167 | 'NO' => array('Norway'), 168 | 'OM' => array('Oman'), 169 | 'PK' => array('Pakistan'), 170 | 'PW' => array('Palau'), 171 | 'PS' => array('Palestinian Territory, Occupied'), 172 | 'PA' => array('Panama'), 173 | 'PG' => array('Papua New Guinea'), 174 | 'PY' => array('Paraguay'), 175 | 'PE' => array('Peru'), 176 | 'PH' => array('Philippines'), 177 | 'PN' => array('Pitcairn'), 178 | 'PL' => array('Poland'), 179 | 'PT' => array('Portugal'), 180 | 'PR' => array('Puerto Rico'), 181 | 'QA' => array('Qatar'), 182 | 'RE' => array('Reunion'), 183 | 'RO' => array('Romania'), 184 | 'RU' => array('Russian Federation', 'Russia'), 185 | 'RW' => array('Rwanda'), 186 | 'SH' => array('Saint Helena'), 187 | 'KN' => array('Saint Kitts and Nevis'), 188 | 'LC' => array('Saint Lucia'), 189 | 'PM' => array('Saint Pierre and Miquelon'), 190 | 'VC' => array('Saint Vincent and the Grenadines'), 191 | 'WS' => array('Samoa'), 192 | 'SM' => array('San Marino'), 193 | 'ST' => array('Sao Tome and Principe'), 194 | 'SA' => array('Saudi Arabia'), 195 | 'SN' => array('Senegal'), 196 | 'CS' => array('Serbia and Montenegro', 'Serbia'), 197 | 'SC' => array('Seychelles'), 198 | 'SL' => array('Sierra Leone'), 199 | 'SG' => array('Singapore'), 200 | 'SK' => array('Slovakia'), 201 | 'SI' => array('Slovenia'), 202 | 'SB' => array('Solomon Islands'), 203 | 'SO' => array('Somalia'), 204 | 'ZA' => array('South Africa'), 205 | 'GS' => array('South Georgia and the South Sandwich Islands'), 206 | 'ES' => array('Spain'), 207 | 'LK' => array('Sri Lanka'), 208 | 'SD' => array('Sudan'), 209 | 'SR' => array('Suriname'), 210 | 'SJ' => array('Svalbard and Jan Mayen'), 211 | 'SZ' => array('Swaziland'), 212 | 'SE' => array('Sweden'), 213 | 'CH' => array('Switzerland'), 214 | 'SY' => array('Syrian Arab Republic'), 215 | 'TW' => array('Taiwan'), 216 | 'TJ' => array('Tajikistan'), 217 | 'TZ' => array('Tanzania, United Republic of'), 218 | 'TH' => array('Thailand'), 219 | 'TL' => array('Timor-Leste'), 220 | 'TG' => array('Togo'), 221 | 'TK' => array('Tokelau'), 222 | 'TO' => array('Tonga'), 223 | 'TT' => array('Trinidad and Tobago'), 224 | 'TN' => array('Tunisia'), 225 | 'TR' => array('Turkey'), 226 | 'TM' => array('Turkmenistan'), 227 | 'TC' => array('Turks and Caicos Islands'), 228 | 'TV' => array('Tuvalu'), 229 | 'UG' => array('Uganda'), 230 | 'UA' => array('Ukraine'), 231 | 'AE' => array('United Arab Emirates'), 232 | 'GB' => array('United Kingdom', 'UK', 'England'), 233 | 'US' => array('United States', 'US', 'USA'), 234 | 'UM' => array('United States Minor Outlying Islands'), 235 | 'UY' => array('Uruguay'), 236 | 'UZ' => array('Uzbekistan'), 237 | 'VU' => array('Vanuatu'), 238 | 'VE' => array('Venezuela'), 239 | 'VN' => array('Viet Nam', 'Vietnam'), 240 | 'VG' => array('Virgin Islands, British'), 241 | 'VI' => array('Virgin Islands, U.s.'), 242 | 'WF' => array('Wallis and Futuna'), 243 | 'EH' => array('Western Sahara'), 244 | 'YE' => array('Yemen'), 245 | 'ZM' => array('Zambia'), 246 | 'ZW' => array('Zimbabwe'), 247 | ); 248 | 249 | static private $usStates_ = array( 250 | 'AL'=>"Alabama", 251 | 'AK'=>"Alaska", 252 | 'AZ'=>"Arizona", 253 | 'AR'=>"Arkansas", 254 | 'CA'=>"California", 255 | 'CO'=>"Colorado", 256 | 'CT'=>"Connecticut", 257 | 'DE'=>"Delaware", 258 | 'DC'=>"District Of Columbia", 259 | 'FL'=>"Florida", 260 | 'GA'=>"Georgia", 261 | 'HI'=>"Hawaii", 262 | 'ID'=>"Idaho", 263 | 'IL'=>"Illinois", 264 | 'IN'=>"Indiana", 265 | 'IA'=>"Iowa", 266 | 'KS'=>"Kansas", 267 | 'KY'=>"Kentucky", 268 | 'LA'=>"Louisiana", 269 | 'ME'=>"Maine", 270 | 'MD'=>"Maryland", 271 | 'MA'=>"Massachusetts", 272 | 'MI'=>"Michigan", 273 | 'MN'=>"Minnesota", 274 | 'MS'=>"Mississippi", 275 | 'MO'=>"Missouri", 276 | 'MT'=>"Montana", 277 | 'NE'=>"Nebraska", 278 | 'NV'=>"Nevada", 279 | 'NH'=>"New Hampshire", 280 | 'NJ'=>"New Jersey", 281 | 'NM'=>"New Mexico", 282 | 'NY'=>"New York", 283 | 'NC'=>"North Carolina", 284 | 'ND'=>"North Dakota", 285 | 'OH'=>"Ohio", 286 | 'OK'=>"Oklahoma", 287 | 'OR'=>"Oregon", 288 | 'PA'=>"Pennsylvania", 289 | 'RI'=>"Rhode Island", 290 | 'SC'=>"South Carolina", 291 | 'SD'=>"South Dakota", 292 | 'TN'=>"Tennessee", 293 | 'TX'=>"Texas", 294 | 'UT'=>"Utah", 295 | 'VT'=>"Vermont", 296 | 'VA'=>"Virginia", 297 | 'WA'=>"Washington", 298 | 'WV'=>"West Virginia", 299 | 'WI'=>"Wisconsin", 300 | 'WY'=>"Wyoming" 301 | ); 302 | 303 | static public function guessCountry($location) { 304 | $s = explode(',', $location); 305 | $s = strtolower(trim($s[count($s) - 1])); 306 | 307 | foreach (self::$countries_ as $code => $countries) { 308 | foreach ($countries as $c) { 309 | if ($s == strtolower($c)) { 310 | return $code; 311 | } 312 | } 313 | } 314 | 315 | foreach (self::$usStates_ as $code => $state) { 316 | if ($code == strtoupper($s)) { 317 | return 'US'; 318 | } 319 | if (strtolower($state) == strtolower($s)) { 320 | return 'US'; 321 | } 322 | } 323 | 324 | // Check country code 325 | 326 | return null; 327 | 328 | //throw new Exception('Cannot find for country for location: ' : $location); 329 | } 330 | 331 | } -------------------------------------------------------------------------------- /src/AppBundle/Eloquent.php.sample: -------------------------------------------------------------------------------- 1 | capsule_ = new \Illuminate\Database\Capsule\Manager(); 11 | 12 | $this->capsule_->addConnection([ 13 | 'driver' => 'mysql', 14 | 'host' => '127.0.0.1', 15 | 'database' => 'sql_injections', 16 | 'username' => '', 17 | 'password' => '', 18 | 'charset' => 'utf8', 19 | 'collation' => 'utf8_unicode_ci', 20 | 'prefix' => '', 21 | ]); 22 | 23 | $this->capsule_->bootEloquent(); 24 | } 25 | 26 | public function connection() { 27 | return $this->capsule_->getConnection('default'); 28 | } 29 | 30 | } -------------------------------------------------------------------------------- /src/AppBundle/InjectionFinder.php: -------------------------------------------------------------------------------- 1 | db_ = $eloquent->connection(); 14 | } 15 | 16 | public function execute() { 17 | $limit = 1000; 18 | 19 | while (true) { 20 | $questions = Question::where('is_processed', '=', '0') 21 | ->orderBy('question_id', 'asc') 22 | ->limit($limit) 23 | ->get(); 24 | 25 | $this->writeln(sprintf('Processing %s questions', count($questions))); 26 | 27 | if (!count($questions)) break; 28 | 29 | $this->db_->beginTransaction(); 30 | try { 31 | foreach ($questions as $question) { 32 | $result = $this->findSqlInjection($question->body_markdown); 33 | $question->sql_injection_line = $result['lineNum']; 34 | $question->has_sql_injection = $result['found']; 35 | $question->is_processed = 1; 36 | $question->has_sql = $result['hasSql']; 37 | $question->save(); 38 | } 39 | } catch (\Exception $e) { 40 | $this->db_->rollback(); 41 | throw $e; 42 | } 43 | $this->db_->commit(); 44 | } 45 | } 46 | 47 | public function findSqlInjection($body) { 48 | // $body = ' INSERT INTO `table_name` (`field1`, `field2`) VALUES (\'a\', \'b\'), (\'c\', \'d\', $post)'; 49 | // $body = ' SELECT * FROM `table_name` WHERE username = $username'; 50 | // $body = ' select * FROM `table_name` WHERE username = $username'; 51 | // $body = ' UPDATE table_name SET username = $username WHERE ...'; 52 | 53 | $lines = Question::bodyToLines($body); 54 | 55 | $sqlRegexes = array( 56 | '/INSERT\s+INTO.*/i', 57 | '/SELECT\s+.*?\sFROM.*/i', 58 | '/UPDATE\s+.*?\sSET.*/i', 59 | '/DELETE\s+FROM.*/i', 60 | ); 61 | 62 | $sqlInjectionRegexes = array( 63 | '/SELECT\s+.*?\sFROM\s.*?\sWHERE\s.*?\$[a-zA-Z_].*?/i', // SELECT ... FROM ... WHERE email = "$email" 64 | '/SELECT\s+.*?\$[a-zA-Z_].*?\sFROM.*?/i', // SELECT ... $email ... FROM... 65 | '/INSERT\s+INTO\s.*?\$[a-zA-Z_].*?/i', // INSERT INTO ... $email ... 66 | '/UPDATE\s+.*?\sSET\s.*?\$[a-zA-Z_].*?/i', // UPDATE ... SET ... email = $email ... 67 | // '/UPDATE\s+.*?\$[a-zA-Z_].*?\sSET.*?/i', // UPDATE $table SET ... 68 | '/DELETE\s+FROM\s+.*?\$[a-zA-Z_].*?/i', // DELETE FROM ... $somevar ... 69 | ); 70 | 71 | $injectionLine = -1; 72 | $hasSql = false; 73 | for ($lineIndex = 0; $lineIndex < count($lines); $lineIndex++) { 74 | $line = $lines[$lineIndex]; 75 | if (strpos($line, "\t") !== 0 && strpos($line, " ") !== 0) continue; // Skip non-code lines 76 | $line = trim($line); 77 | if (strpos($line, '//') === 0) continue; // Skip comments 78 | 79 | foreach ($sqlRegexes as $regex) { 80 | $ok = preg_match($regex, $line); 81 | if ($ok === 1) { 82 | $hasSql = true; 83 | break; 84 | } 85 | } 86 | 87 | foreach ($sqlInjectionRegexes as $regex) { 88 | $ok = preg_match($regex, $line); 89 | if ($ok === 1) { 90 | $injectionLine = $lineIndex; 91 | break; 92 | } 93 | } 94 | 95 | if ($injectionLine >= 0) break; 96 | } 97 | 98 | return array( 99 | 'found' => $injectionLine >= 0, 100 | 'lineNum' => $injectionLine, 101 | 'line' => $injectionLine >= 0 ? trim($lines[$injectionLine]) : null, 102 | 'hasSql' => $hasSql, 103 | ); 104 | } 105 | 106 | } -------------------------------------------------------------------------------- /src/AppBundle/Model/City.php: -------------------------------------------------------------------------------- 1 | geoname_id)->first(); 15 | if (!$place) return ''; 16 | return $place->name; 17 | } 18 | 19 | static public function byName($name) { 20 | $place = Place::where('country_id', '>', 0)->where('name', '=', $name)->first(); 21 | if (!$place) { 22 | // Handles cases like "Korea, Republic of" / "Republic of Korea" 23 | $s = explode(',', $name); 24 | if (count($s) == 2) $place = Place::where('country_id', '>', 0)->where('name', '=', trim($s[1]) . ' ' . trim($s[0]))->first(); 25 | } 26 | if (!$place) throw new \Exception('Invalid name: ' . $name); 27 | return $place->country(); 28 | } 29 | 30 | static public function byCode($code) { 31 | return Country::where('code', '=', $code)->first(); 32 | } 33 | 34 | static public function countryName($code) { 35 | $code = strtoupper($code); 36 | if (isset(self::$countryCodeToName_[$code])) return self::$countryCodeToName_[$code]; 37 | $country = self::byCode($code); 38 | return $country->name(); 39 | } 40 | 41 | static public function topCountries() { 42 | $sql = 'SELECT country, count(*) total FROM users WHERE country IS NOT NULL GROUP BY country ORDER BY total DESC LIMIT 40'; 43 | $con = (new self())->getConnection(); 44 | $results = $con->getPdo()->query($sql); 45 | return $results->fetchAll(\PDO::FETCH_ASSOC); 46 | } 47 | 48 | static private $countryCodeToName_ = array( 49 | 'AF' => 'Afghanistan', 50 | 'AX' => 'Aland Islands', 51 | 'AL' => 'Albania', 52 | 'DZ' => 'Algeria', 53 | 'AS' => 'American Samoa', 54 | 'AD' => 'Andorra', 55 | 'AO' => 'Angola', 56 | 'AI' => 'Anguilla', 57 | 'AQ' => 'Antarctica', 58 | 'AG' => 'Antigua And Barbuda', 59 | 'AR' => 'Argentina', 60 | 'AM' => 'Armenia', 61 | 'AW' => 'Aruba', 62 | 'AU' => 'Australia', 63 | 'AT' => 'Austria', 64 | 'AZ' => 'Azerbaijan', 65 | 'BS' => 'Bahamas', 66 | 'BH' => 'Bahrain', 67 | 'BD' => 'Bangladesh', 68 | 'BB' => 'Barbados', 69 | 'BY' => 'Belarus', 70 | 'BE' => 'Belgium', 71 | 'BZ' => 'Belize', 72 | 'BJ' => 'Benin', 73 | 'BM' => 'Bermuda', 74 | 'BT' => 'Bhutan', 75 | 'BO' => 'Bolivia', 76 | 'BA' => 'Bosnia And Herzegovina', 77 | 'BW' => 'Botswana', 78 | 'BV' => 'Bouvet Island', 79 | 'BR' => 'Brazil', 80 | 'IO' => 'British Indian Ocean Territory', 81 | 'BN' => 'Brunei Darussalam', 82 | 'BG' => 'Bulgaria', 83 | 'BF' => 'Burkina Faso', 84 | 'BI' => 'Burundi', 85 | 'KH' => 'Cambodia', 86 | 'CM' => 'Cameroon', 87 | 'CA' => 'Canada', 88 | 'CV' => 'Cape Verde', 89 | 'KY' => 'Cayman Islands', 90 | 'CF' => 'Central African Republic', 91 | 'TD' => 'Chad', 92 | 'CL' => 'Chile', 93 | 'CN' => 'China', 94 | 'CX' => 'Christmas Island', 95 | 'CC' => 'Cocos (Keeling) Islands', 96 | 'CO' => 'Colombia', 97 | 'KM' => 'Comoros', 98 | 'CG' => 'Congo', 99 | 'CD' => 'Congo, Democratic Republic', 100 | 'CK' => 'Cook Islands', 101 | 'CR' => 'Costa Rica', 102 | 'CI' => 'Cote D\'Ivoire', 103 | 'HR' => 'Croatia', 104 | 'CU' => 'Cuba', 105 | 'CY' => 'Cyprus', 106 | 'CZ' => 'Czech Republic', 107 | 'DK' => 'Denmark', 108 | 'DJ' => 'Djibouti', 109 | 'DM' => 'Dominica', 110 | 'DO' => 'Dominican Republic', 111 | 'EC' => 'Ecuador', 112 | 'EG' => 'Egypt', 113 | 'SV' => 'El Salvador', 114 | 'GQ' => 'Equatorial Guinea', 115 | 'ER' => 'Eritrea', 116 | 'EE' => 'Estonia', 117 | 'ET' => 'Ethiopia', 118 | 'FK' => 'Falkland Islands (Malvinas)', 119 | 'FO' => 'Faroe Islands', 120 | 'FJ' => 'Fiji', 121 | 'FI' => 'Finland', 122 | 'FR' => 'France', 123 | 'GF' => 'French Guiana', 124 | 'PF' => 'French Polynesia', 125 | 'TF' => 'French Southern Territories', 126 | 'GA' => 'Gabon', 127 | 'GM' => 'Gambia', 128 | 'GE' => 'Georgia', 129 | 'DE' => 'Germany', 130 | 'GH' => 'Ghana', 131 | 'GI' => 'Gibraltar', 132 | 'GR' => 'Greece', 133 | 'GL' => 'Greenland', 134 | 'GD' => 'Grenada', 135 | 'GP' => 'Guadeloupe', 136 | 'GU' => 'Guam', 137 | 'GT' => 'Guatemala', 138 | 'GG' => 'Guernsey', 139 | 'GN' => 'Guinea', 140 | 'GW' => 'Guinea-Bissau', 141 | 'GY' => 'Guyana', 142 | 'HT' => 'Haiti', 143 | 'HM' => 'Heard Island & Mcdonald Islands', 144 | 'VA' => 'Holy See (Vatican City State)', 145 | 'HN' => 'Honduras', 146 | 'HK' => 'Hong Kong', 147 | 'HU' => 'Hungary', 148 | 'IS' => 'Iceland', 149 | 'IN' => 'India', 150 | 'ID' => 'Indonesia', 151 | 'IR' => 'Iran', 152 | 'IQ' => 'Iraq', 153 | 'IE' => 'Ireland', 154 | 'IM' => 'Isle Of Man', 155 | 'IL' => 'Israel', 156 | 'IT' => 'Italy', 157 | 'JM' => 'Jamaica', 158 | 'JP' => 'Japan', 159 | 'JE' => 'Jersey', 160 | 'JO' => 'Jordan', 161 | 'KZ' => 'Kazakhstan', 162 | 'KE' => 'Kenya', 163 | 'KI' => 'Kiribati', 164 | 'KR' => 'Korea', 165 | 'KW' => 'Kuwait', 166 | 'KG' => 'Kyrgyzstan', 167 | 'LA' => 'Lao People\'s Democratic Republic', 168 | 'LV' => 'Latvia', 169 | 'LB' => 'Lebanon', 170 | 'LS' => 'Lesotho', 171 | 'LR' => 'Liberia', 172 | 'LY' => 'Libyan Arab Jamahiriya', 173 | 'LI' => 'Liechtenstein', 174 | 'LT' => 'Lithuania', 175 | 'LU' => 'Luxembourg', 176 | 'MO' => 'Macao', 177 | 'MK' => 'Macedonia', 178 | 'MG' => 'Madagascar', 179 | 'MW' => 'Malawi', 180 | 'MY' => 'Malaysia', 181 | 'MV' => 'Maldives', 182 | 'ML' => 'Mali', 183 | 'MT' => 'Malta', 184 | 'MH' => 'Marshall Islands', 185 | 'MQ' => 'Martinique', 186 | 'MR' => 'Mauritania', 187 | 'MU' => 'Mauritius', 188 | 'YT' => 'Mayotte', 189 | 'MX' => 'Mexico', 190 | 'FM' => 'Micronesia, Federated States Of', 191 | 'MD' => 'Moldova', 192 | 'MC' => 'Monaco', 193 | 'MN' => 'Mongolia', 194 | 'ME' => 'Montenegro', 195 | 'MS' => 'Montserrat', 196 | 'MA' => 'Morocco', 197 | 'MZ' => 'Mozambique', 198 | 'MM' => 'Myanmar', 199 | 'NA' => 'Namibia', 200 | 'NR' => 'Nauru', 201 | 'NP' => 'Nepal', 202 | 'NL' => 'Netherlands', 203 | 'AN' => 'Netherlands Antilles', 204 | 'NC' => 'New Caledonia', 205 | 'NZ' => 'New Zealand', 206 | 'NI' => 'Nicaragua', 207 | 'NE' => 'Niger', 208 | 'NG' => 'Nigeria', 209 | 'NU' => 'Niue', 210 | 'NF' => 'Norfolk Island', 211 | 'MP' => 'Northern Mariana Islands', 212 | 'NO' => 'Norway', 213 | 'OM' => 'Oman', 214 | 'PK' => 'Pakistan', 215 | 'PW' => 'Palau', 216 | 'PS' => 'Palestinian Territory, Occupied', 217 | 'PA' => 'Panama', 218 | 'PG' => 'Papua New Guinea', 219 | 'PY' => 'Paraguay', 220 | 'PE' => 'Peru', 221 | 'PH' => 'Philippines', 222 | 'PN' => 'Pitcairn', 223 | 'PL' => 'Poland', 224 | 'PT' => 'Portugal', 225 | 'PR' => 'Puerto Rico', 226 | 'QA' => 'Qatar', 227 | 'RE' => 'Reunion', 228 | 'RO' => 'Romania', 229 | 'RU' => 'Russia', 230 | 'RW' => 'Rwanda', 231 | 'BL' => 'Saint Barthelemy', 232 | 'SH' => 'Saint Helena', 233 | 'KN' => 'Saint Kitts And Nevis', 234 | 'LC' => 'Saint Lucia', 235 | 'MF' => 'Saint Martin', 236 | 'PM' => 'Saint Pierre And Miquelon', 237 | 'VC' => 'Saint Vincent And Grenadines', 238 | 'WS' => 'Samoa', 239 | 'SM' => 'San Marino', 240 | 'ST' => 'Sao Tome And Principe', 241 | 'SA' => 'Saudi Arabia', 242 | 'SN' => 'Senegal', 243 | 'RS' => 'Serbia', 244 | 'SC' => 'Seychelles', 245 | 'SL' => 'Sierra Leone', 246 | 'SG' => 'Singapore', 247 | 'SK' => 'Slovakia', 248 | 'SI' => 'Slovenia', 249 | 'SB' => 'Solomon Islands', 250 | 'SO' => 'Somalia', 251 | 'ZA' => 'South Africa', 252 | 'GS' => 'South Georgia And Sandwich Isl.', 253 | 'ES' => 'Spain', 254 | 'LK' => 'Sri Lanka', 255 | 'SD' => 'Sudan', 256 | 'SR' => 'Suriname', 257 | 'SJ' => 'Svalbard And Jan Mayen', 258 | 'SZ' => 'Swaziland', 259 | 'SE' => 'Sweden', 260 | 'CH' => 'Switzerland', 261 | 'SY' => 'Syrian Arab Republic', 262 | 'TW' => 'Taiwan', 263 | 'TJ' => 'Tajikistan', 264 | 'TZ' => 'Tanzania', 265 | 'TH' => 'Thailand', 266 | 'TL' => 'Timor-Leste', 267 | 'TG' => 'Togo', 268 | 'TK' => 'Tokelau', 269 | 'TO' => 'Tonga', 270 | 'TT' => 'Trinidad And Tobago', 271 | 'TN' => 'Tunisia', 272 | 'TR' => 'Turkey', 273 | 'TM' => 'Turkmenistan', 274 | 'TC' => 'Turks And Caicos Islands', 275 | 'TV' => 'Tuvalu', 276 | 'UG' => 'Uganda', 277 | 'UA' => 'Ukraine', 278 | 'AE' => 'United Arab Emirates', 279 | 'GB' => 'United Kingdom', 280 | 'US' => 'United States', 281 | 'UM' => 'United States Outlying Islands', 282 | 'UY' => 'Uruguay', 283 | 'UZ' => 'Uzbekistan', 284 | 'VU' => 'Vanuatu', 285 | 'VE' => 'Venezuela', 286 | 'VN' => 'Viet Nam', 287 | 'VG' => 'Virgin Islands, British', 288 | 'VI' => 'Virgin Islands, U.S.', 289 | 'WF' => 'Wallis And Futuna', 290 | 'EH' => 'Western Sahara', 291 | 'YE' => 'Yemen', 292 | 'ZM' => 'Zambia', 293 | 'ZW' => 'Zimbabwe', 294 | ); 295 | 296 | } -------------------------------------------------------------------------------- /src/AppBundle/Model/Place.php: -------------------------------------------------------------------------------- 1 | first(); 24 | if ($place) return $place->country(); 25 | 26 | // US state 27 | // Lots of special cases for US since when no country is 28 | // specified, it's often assumed it's in the US. 29 | 30 | $countryUs = Country::where('code', '=', 'US')->first(); 31 | 32 | if (self::usStateExists($location)) return $countryUs; 33 | 34 | foreach (self::$usStates_ as $code => $state) { 35 | if ($code == strtoupper($s)) { 36 | return $countryUs; 37 | } 38 | if (strtolower($state) == strtolower($s)) { 39 | return $countryUs; 40 | } 41 | } 42 | 43 | // Country code 44 | 45 | $country = Country::where('code', '=', $s)->first(); 46 | if ($country) return $country; 47 | 48 | // Search the last words of the location as a country 49 | // eg. To find "United States" in "Fayetteville, North Carolina United States" 50 | 51 | $s = explode(' ', $location); 52 | for ($i = count($s); $i >= 1; $i--) { 53 | 54 | $name = implode(' ', array_slice($s, count($s) - $i, $i)); 55 | $place = Place::where('name', '=', $name)->where('country_id', '>', 0)->first(); 56 | if ($place) return $place->country(); 57 | 58 | if (self::usStateExists($name)) return $countryUs; 59 | } 60 | 61 | // Search the whole name 62 | $place = Place::where('name', '=', $location)->first(); 63 | if ($place) return $place->country(); 64 | 65 | $s = str_replace(',', ' ', $location); 66 | $s = str_replace('.', ' ', $s); 67 | $s = str_replace(' ', ' ', $s); 68 | $s = str_replace(' ', ' ', $s); 69 | $s = str_replace(' ', ' ', $s); 70 | $s = explode(' ', $s); 71 | 72 | for ($i = count($s); $i >= 1; $i--) { 73 | 74 | $name = trim(implode(' ', array_slice($s, 0, $i))); 75 | if (empty($name)) continue; 76 | // Check it's not a fake name like "In front of my seat" or "The earth" 77 | // since "in" and "the" are valid city names in some countries. 78 | if ($i == 1 && self::isStopWord($name)) continue; 79 | 80 | $place = Place::where('name', '=', $name)->first(); 81 | // if ($place) echo $place->name . "\n"; 82 | 83 | if ($place) return $place->country(); 84 | 85 | if (self::usStateExists($name)) return $countryUs; 86 | } 87 | 88 | // echo 'NOT FOUND: ' . $location . "\n"; 89 | 90 | return null; 91 | } 92 | 93 | public function country() { 94 | if ($this->country_id) return Country::where('geoname_id', '=', $this->country_id)->first(); 95 | $city = City::where('geoname_id', '=', $this->city_id)->first(); 96 | return Country::where('geoname_id', '=', $city->country_id)->first(); 97 | } 98 | 99 | static private function usStateExists($name) { 100 | foreach (self::$usStates_ as $code => $state) { 101 | if (strtolower($name) == strtolower($state)) return true; 102 | } 103 | return false; 104 | } 105 | 106 | static private function isStopWord($word) { 107 | return in_array(strtolower($word), self::$stopWords_); 108 | } 109 | 110 | static private $usStates_ = array( 111 | 'AL'=>"Alabama", 112 | 'AK'=>"Alaska", 113 | 'AZ'=>"Arizona", 114 | 'AR'=>"Arkansas", 115 | 'CA'=>"California", 116 | 'CO'=>"Colorado", 117 | 'CT'=>"Connecticut", 118 | 'DE'=>"Delaware", 119 | 'DC'=>"District Of Columbia", 120 | 'FL'=>"Florida", 121 | 'GA'=>"Georgia", 122 | 'HI'=>"Hawaii", 123 | 'ID'=>"Idaho", 124 | 'IL'=>"Illinois", 125 | 'IN'=>"Indiana", 126 | 'IA'=>"Iowa", 127 | 'KS'=>"Kansas", 128 | 'KY'=>"Kentucky", 129 | 'LA'=>"Louisiana", 130 | 'ME'=>"Maine", 131 | 'MD'=>"Maryland", 132 | 'MA'=>"Massachusetts", 133 | 'MI'=>"Michigan", 134 | 'MN'=>"Minnesota", 135 | 'MS'=>"Mississippi", 136 | 'MO'=>"Missouri", 137 | 'MT'=>"Montana", 138 | 'NE'=>"Nebraska", 139 | 'NV'=>"Nevada", 140 | 'NH'=>"New Hampshire", 141 | 'NJ'=>"New Jersey", 142 | 'NM'=>"New Mexico", 143 | 'NY'=>"New York", 144 | 'NC'=>"North Carolina", 145 | 'ND'=>"North Dakota", 146 | 'OH'=>"Ohio", 147 | 'OK'=>"Oklahoma", 148 | 'OR'=>"Oregon", 149 | 'PA'=>"Pennsylvania", 150 | 'RI'=>"Rhode Island", 151 | 'SC'=>"South Carolina", 152 | 'SD'=>"South Dakota", 153 | 'TN'=>"Tennessee", 154 | 'TX'=>"Texas", 155 | 'UT'=>"Utah", 156 | 'VT'=>"Vermont", 157 | 'VA'=>"Virginia", 158 | 'WA'=>"Washington", 159 | 'WV'=>"West Virginia", 160 | 'WI'=>"Wisconsin", 161 | 'WY'=>"Wyoming" 162 | ); 163 | 164 | static private $stopWords_ = array('a','able','about','across','after','all','almost','also','am','among','an','and','any','are','as','at','be','because','been','but','by','can','cannot','could','dear','did','do','does','either','else','ever','every','for','from','get','got','had','has','have','he','her','hers','him','his','how','however','i','if','in','into','is','it','its','just','least','let','like','likely','may','me','might','most','must','my','neither','no','nor','not','of','off','often','on','only','or','other','our','own','rather','said','say','says','she','should','since','so','some','than','that','the','their','them','then','there','these','they','this','tis','to','too','twas','us','wants','was','we','were','what','when','where','which','while','who','whom','why','will','with','would','yet','you','your'); 165 | 166 | } -------------------------------------------------------------------------------- /src/AppBundle/Model/Question.php: -------------------------------------------------------------------------------- 1 | question_id = $a['question_id']; 17 | $this->body_markdown = $a['body_markdown']; 18 | $this->creation_date = $a['creation_date']; 19 | $this->owner_id = isset($a['owner']['user_id']) ? $a['owner']['user_id'] : 0; 20 | $this->raw_json = json_encode($a); 21 | } 22 | 23 | static public function bodyToLines($body) { 24 | $body = str_replace("\r\n", "\n", $body); 25 | $body = str_replace("\r", "\n", $body); 26 | $lines = explode("\n", $body); 27 | $output = array(); 28 | foreach ($lines as $line) { 29 | $output[] = mb_convert_encoding($line, 'UTF-8', 'HTML-ENTITIES'); 30 | } 31 | return $output; 32 | } 33 | 34 | static public function earliestQuestionDate() { 35 | return self::min('creation_date'); 36 | } 37 | 38 | static public function lastQuestionDate() { 39 | return self::max('creation_date'); 40 | } 41 | 42 | // Questions that have an associated user ID but 43 | // no correspondance in the users table. 44 | static public function ownerIdsWithNoUserObject($minId, $limit) { 45 | $con = (new self())->getConnection(); 46 | $st = $con->getPdo()->prepare(' 47 | SELECT DISTINCT owner_id 48 | FROM questions 49 | LEFT JOIN users ON questions.owner_id = users.user_id 50 | WHERE 51 | owner_id != 0 52 | AND users.raw_json IS NULL 53 | AND question_id > :question_id 54 | LIMIT :limit 55 | '); 56 | $st->execute(array('question_id' => $minId, 'limit' => $limit)); 57 | $r = $st->fetchAll(\PDO::FETCH_ASSOC); 58 | 59 | $output = array(); 60 | foreach ($r as $v) $output[] = $v['owner_id']; 61 | return $output; 62 | } 63 | 64 | } -------------------------------------------------------------------------------- /src/AppBundle/Model/User.php: -------------------------------------------------------------------------------- 1 | user_id = $a['user_id']; 18 | $this->age = isset($a['age']) ? $a['age'] : 0; 19 | $this->reputation = isset($a['reputation']) ? $a['reputation'] : 0; 20 | $this->is_employee = isset($a['is_employee']) ? (int)$a['is_employee'] : 0; 21 | $this->location = $location; 22 | $this->country = $country ? $country->code : null; 23 | $this->raw_json = json_encode($a); 24 | } 25 | 26 | } -------------------------------------------------------------------------------- /src/AppBundle/QuestionCrawler.php: -------------------------------------------------------------------------------- 1 | db_ = $eloquent->connection(); 19 | $this->api_ = $stackExchangeApi; 20 | } 21 | 22 | public function setOutput($output) { 23 | $this->api_->setOutput($output); 24 | parent::setOutput($output); 25 | } 26 | 27 | public function execute() { 28 | $fromDate = new \DateTime(); 29 | 30 | $lastTimestamp = Question::lastQuestionDate(); 31 | if ($lastTimestamp) { 32 | $fromDate->setTimestamp($lastTimestamp); 33 | } else { 34 | $fromDate->setDate(2008,7,1); // Private beta 35 | } 36 | 37 | $toDate = clone $fromDate; 38 | $toDate->add(new \DateInterval('P' . $fromDate->format('t') . 'D')); 39 | $page = 1; 40 | 41 | while (true) { 42 | $result = $this->api_->questions($fromDate->getTimestamp(), $toDate->getTimestamp(), $page); 43 | 44 | $this->writeln("Got " . $fromDate->format('Y-m-d') . ' to ' . $toDate->format('Y-m-d') . ', page ' . $page . ". Total items: " . $result['total']); 45 | 46 | // {"error_id":502,"error_message":"too many requests from this IP, more requests available in 32322 seconds","error_name":"throttle_violation"} 47 | 48 | if (isset($result['error_id'])) { 49 | $this->writeln(''); 50 | $this->writeln('Got error: ' . json_encode($result)); 51 | if (isset($result['error_message'])) { 52 | preg_match('/available in (.*) seconds/', $result['error_message'], $matches); 53 | if (isset($matches[1])) { 54 | $this->writeln('Waiting for ' . gmdate('H\h i\m s\s', $matches[1]) . '...'); 55 | sleep($matches[1] + 1); 56 | continue; 57 | } else { 58 | throw new \Exception('Unexpected error: ' . json_encode($result)); 59 | } 60 | } 61 | } 62 | 63 | if (!isset($result['items'])) throw new \Exception('No "items" property on object: ' . json_encode($result)); 64 | $this->saveQuestions($result['items']); 65 | 66 | if (!$result['has_more']) { 67 | $expectedPageCount = ceil($result['total'] / $this->api_->pageSize()); 68 | if ($page < $expectedPageCount) { 69 | // Something weird that shouldn't happen but sometime does. 70 | throw new \Exception('Didn\'t get expected number of pages, but has_more parameter is false: ' . json_encode($result)); 71 | } 72 | 73 | $fromDate = $toDate; 74 | $toDate = clone $fromDate; //$fromDate + 60 * 60 * 24; 75 | $toDate->add(new \DateInterval('P' . $fromDate->format('t') . 'D')); 76 | if ($toDate->getTimestamp() > time() - 60 * 60) { 77 | // Don't process very recent questions, because they often have issues like wrong tags 78 | // or bad formatting (or they are questions that are going to be deleted). These issues are 79 | // fixed relatively quickly, so one hour delay should be enough. 80 | $this->writeln("Done all current questions"); 81 | break; 82 | } 83 | $page = 1; 84 | } else { 85 | $page++; 86 | } 87 | 88 | if (isset($result['backoff'])) { 89 | $this->writeln("Got 'backoff' parameter: " . $result['backoff']); 90 | sleep($result['backoff'] + 1); // Wait a bit longer than required so as not to be blocked 91 | } 92 | 93 | sleep(1); 94 | } 95 | } 96 | 97 | private function saveQuestions($questions) { 98 | foreach ($questions as $question) { 99 | try { 100 | $m = new Question(); 101 | $m->fromApiArray($question); 102 | $m->save(); 103 | } catch (\Exception $e) { 104 | // SQLSTATE[23000]: Integrity constraint violation: 1062 Duplicate entry '576908' for key 'PRIMARY' 105 | // Can happen if doing a Ctrl+C while the questions are being saved, and then resuming the script. 106 | if ($e->getCode() == 23000) { 107 | $this->writeln("Skipping question " . $question['question_id'] . ": already saved."); 108 | } else if ($e->getCode() == 22220) { 109 | $this->writeln("Skipping question " . $question['question_id'] . ": " . $e->getMessage()); 110 | } else { 111 | throw $e; 112 | } 113 | } 114 | } 115 | } 116 | 117 | // Loop through all the questions and update the database fields based on the stored 118 | // raw JSON. Useful whenever a new field is added to the database table. 119 | public function refreshQuestions() { 120 | $limit = 10000; 121 | $offset = 0; 122 | while (true) { 123 | $questions = Question::orderBy('question_id', 'asc') 124 | ->limit($limit) 125 | ->offset($offset) 126 | ->get(); 127 | 128 | if (!count($questions)) break; 129 | 130 | $this->db_->beginTransaction(); 131 | try { 132 | foreach ($questions as $question) { 133 | $q = json_decode($question->raw_json, true); 134 | $question->fromApiArray($q); 135 | $question->save(); 136 | } 137 | } catch (\Exception $e) { 138 | $this->db_->rollback(); 139 | throw $e; 140 | } 141 | $this->db_->commit(); 142 | 143 | $offset += $limit; 144 | } 145 | } 146 | 147 | 148 | } -------------------------------------------------------------------------------- /src/AppBundle/ReportService.php: -------------------------------------------------------------------------------- 1 | db_ = $eloquent->connection(); 16 | $this->cache_ = $cache; 17 | } 18 | 19 | public function monthlyInjections() { 20 | $d1 = new \DateTime(); 21 | $d1->setTimestamp(Question::earliestQuestionDate()); 22 | $d1->setDate($d1->format('Y'), $d1->format('m'), 1); 23 | $d1->setTime(0,0,0); 24 | 25 | $lastQuestionDate = Question::lastQuestionDate(); 26 | 27 | $output = array(); 28 | 29 | while (true) { 30 | $d2 = clone $d1; 31 | $d2->add(new \DateInterval('P' . $d2->format('t') . 'D')); 32 | 33 | $cacheTimeout = 60 * 60 * 24 * 31 * 12; 34 | $now = new \DateTime(); 35 | if ($d2->format('Ym') >= $now->format('Ym')) { 36 | $cacheTimeout = 60 * 60 * 24; 37 | } 38 | 39 | $this->writeln("Processing " . $d1->format('Y-m') . '... Cache: ' . $cacheTimeout); 40 | 41 | $params = array('date1' => $d1->getTimestamp(), 'date2' => $d2->getTimestamp()); 42 | 43 | $rows = $this->cache_->getOrSet('ReportService::monthlyInjection' . md5(json_encode($params)), function() use($params) { 44 | $st = $this->db_->getPdo()->prepare(' 45 | SELECT 46 | has_sql_injection, has_sql 47 | FROM questions 48 | WHERE has_sql = 1 AND creation_date >= :date1 AND creation_date < :date2 49 | '); 50 | $st->execute($params); 51 | 52 | return $st->fetchAll(\PDO::FETCH_ASSOC); 53 | }, $cacheTimeout); 54 | 55 | $summary = array( 56 | 'month' => $d1->format('Ym'), 57 | 'has_sql_count' => 0, 58 | 'has_sql_injection_count' => 0, 59 | ); 60 | 61 | foreach ($rows as $row) { 62 | if ((int)$row['has_sql']) $summary['has_sql_count']++; 63 | if ((int)$row['has_sql_injection']) $summary['has_sql_injection_count']++; 64 | } 65 | 66 | $output[] = $summary; 67 | 68 | $d1 = clone $d2; 69 | 70 | if ($d1->getTimestamp() > $lastQuestionDate) break; 71 | } 72 | 73 | return $output; 74 | } 75 | 76 | public function latestInjections() { 77 | $results = $this->db_->getPdo()->query(' 78 | SELECT question_id, body_markdown, sql_injection_line, creation_date 79 | FROM questions 80 | WHERE has_sql_injection = 1 81 | ORDER BY creation_date DESC 82 | LIMIT 1000 83 | '); 84 | 85 | $rows = $results->fetchAll(\PDO::FETCH_ASSOC); 86 | 87 | $output = array(); 88 | foreach ($rows as $row) { 89 | $lines = Question::bodyToLines($row['body_markdown']); 90 | if (count($lines) <= $row['sql_injection_line']) continue; // shouldn't happen 91 | $line = $lines[$row['sql_injection_line']]; 92 | $output[] = array( 93 | 'line' => trim($line), 94 | 'creation_date' => $row['creation_date'], 95 | 'question_id' => $row['question_id'], 96 | ); 97 | } 98 | 99 | return $output; 100 | } 101 | 102 | public function sqlInjectionsPerCountry() { 103 | $output = array(); 104 | $db = $this->db_; 105 | $lastId = 0; 106 | $lastQuestionId = Question::max('question_id'); 107 | while (true) { 108 | $this->writeln(round(($lastId / $lastQuestionId) * 100) . '%'); 109 | $sql = 'SELECT question_id, owner_id, has_sql, has_sql_injection FROM questions WHERE owner_id > 0 AND question_id > :question_id AND has_sql = 1 ORDER BY question_id LIMIT 10000'; 110 | $this->writeln('Fetch questions...'); 111 | $params = array('question_id' => $lastId); 112 | $questions = $this->cache_->getOrSet('ReportService::sqlInjectionsPerCountry_questions' . md5($sql . json_encode($params)), function() use($sql, $db, $params) { 113 | $st = $db->getPdo()->prepare($sql); 114 | $st->execute($params); 115 | return $st->fetchAll(\PDO::FETCH_ASSOC); 116 | }, 60 * 60 * 24 * 31); 117 | 118 | if (!count($questions)) break; 119 | 120 | $lastId = $questions[count($questions) - 1]['question_id']; 121 | 122 | $userIds = array(); 123 | foreach ($questions as $q) $userIds[] = (int)$q['owner_id']; 124 | 125 | $this->writeln('Fetch users...'); 126 | 127 | $s = implode(',', $userIds); 128 | $sql = 'SELECT user_id, country FROM users WHERE country IS NOT NULL AND user_id IN (' . $s . ')'; 129 | $users = $this->cache_->getOrSet('ReportService::sqlInjectionsPerCountry_users' . md5($sql), function() use($sql, $db) { 130 | return $db->getPdo()->query($sql)->fetchAll(\PDO::FETCH_ASSOC); 131 | }); 132 | 133 | $temp = array(); 134 | foreach ($questions as $qIndex => $q) { 135 | $country = null; 136 | foreach ($users as $u) { 137 | if ($u['user_id'] == $q['owner_id']) { 138 | $country = $u['country']; 139 | break; 140 | } 141 | } 142 | if (!$country) continue; 143 | 144 | $q['country'] = $country; 145 | $temp[] = $q; 146 | } 147 | $questions = $temp; 148 | 149 | foreach ($questions as $q) { 150 | if (!array_key_exists($q['country'], $output)) $output[$q['country']] = array('has_sql' => 0, 'has_sql_injection' => 0); 151 | $output[$q['country']]['has_sql']++; 152 | $output[$q['country']]['has_sql_injection'] += (int)$q['has_sql_injection'] ? 1 : 0; 153 | } 154 | } 155 | 156 | $topCountries = Country::topCountries(); 157 | $topCountryCodes = array(); 158 | foreach ($topCountries as $c) $topCountryCodes[] = $c['country']; 159 | 160 | $temp = array(); 161 | foreach ($output as $country => $o) { 162 | if (!in_array($country, $topCountryCodes)) continue; 163 | $o['ratio'] = $o['has_sql'] ? $o['has_sql_injection'] / $o['has_sql'] : 0; 164 | $o['code'] = $country; 165 | $o['country_name'] = Country::countryName($country); 166 | $temp[] = $o; 167 | } 168 | $output = $temp; 169 | 170 | usort($output, function($a, $b) { 171 | return $a['ratio'] > $b['ratio'] ? -1 : +1; 172 | }); 173 | 174 | return $output; 175 | } 176 | 177 | } -------------------------------------------------------------------------------- /src/AppBundle/StackExchangeApi.php: -------------------------------------------------------------------------------- 1 | pageSize_; 12 | } 13 | 14 | private function executeQuery($path, $query) { 15 | $query['key'] = $this->key_; 16 | $query['site'] = 'stackoverflow'; 17 | 18 | $baseUrl = 'https://api.stackexchange.com/2.2'; 19 | 20 | $url = $baseUrl . '/' . $path . '?' . http_build_query($query); 21 | 22 | $this->writeln($url); 23 | 24 | $ch = curl_init($url); 25 | curl_setopt($ch, CURLOPT_ENCODING, "gzip"); 26 | curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); 27 | $data = curl_exec($ch); 28 | curl_close($ch); 29 | 30 | $d = json_decode($data, true); 31 | if ($d === false) throw new \Exception('Could not decode JSON: ' . $data); 32 | return $d; 33 | } 34 | 35 | public function questions($fromDate, $toDate, $page) { 36 | $query = array(); 37 | $query['filter'] = '!-*f(6rkvD-tO'; 38 | $query['tagged'] = 'php'; 39 | $query['fromdate'] = $fromDate; 40 | $query['todate'] = $toDate; 41 | $query['page'] = $page; 42 | $query['pagesize'] = $this->pageSize(); 43 | 44 | return $this->executeQuery('questions', $query); 45 | } 46 | 47 | public function users($ids) { 48 | if (!count($ids)) throw new \Exception('No user IDs specified'); 49 | $query = array(); 50 | $query['filter'] = '!LnO)*RBjDUSz2sWDlSDTDB'; 51 | $query['pagesize'] = $this->pageSize(); 52 | 53 | $path = 'users/' . implode(';', $ids); 54 | 55 | return $this->executeQuery($path, $query); 56 | } 57 | 58 | } 59 | -------------------------------------------------------------------------------- /src/AppBundle/UserFinder.php: -------------------------------------------------------------------------------- 1 | db_ = $eloquent->connection(); 18 | $this->api_ = $stackExchangeApi; 19 | } 20 | 21 | public function setOutput($output) { 22 | $this->api_->setOutput($output); 23 | parent::setOutput($output); 24 | } 25 | 26 | public function execute() { 27 | $limit = 1000; 28 | $lastId = 0; 29 | 30 | while (true) { 31 | $ownerIds = Question::ownerIdsWithNoUserObject($lastId, $limit); 32 | if (!count($ownerIds)) break; 33 | $this->writeln(sprintf('Adding %s users...', count($ownerIds))); 34 | $this->processUsers($ownerIds); 35 | if (count($ownerIds) < $this->bufferSize_) break; 36 | $lastId = $ownerIds[count($ownerIds) - 1]; 37 | } 38 | 39 | $this->processAllUsers(); 40 | } 41 | 42 | private function processAllUsers() { 43 | $this->processUsers('all'); 44 | } 45 | 46 | private function processUsers($userIds) { 47 | $allUsers = $userIds === 'all'; 48 | if (!count($userIds) && !$allUsers) return; 49 | 50 | if (!$allUsers) { 51 | $this->userIds_ = array_merge($this->userIds_, $userIds); 52 | $this->userIds_ = array_unique($this->userIds_); 53 | } 54 | 55 | while (count($this->userIds_) >= $this->bufferSize_ || $allUsers) { 56 | $d = array_slice($this->userIds_, 0, $this->bufferSize_); 57 | $this->userIds_ = array_slice($this->userIds_, $this->bufferSize_); 58 | 59 | if (!count($d)) break; 60 | 61 | $results = array(); 62 | while (true) { 63 | $results = $this->api_->users($d); 64 | if (isset($results['backoff'])) { 65 | $this->writeln("Got 'backoff' parameter: " . $results['backoff']); 66 | sleep($results['backoff'] + 1); // Wait a bit longer than required so as not to be blocked 67 | continue; 68 | } 69 | break; 70 | } 71 | 72 | if (!isset($results['items'])) throw new \Exception('Missing "items" property: ' . json_encode($results)); 73 | 74 | $this->db_->beginTransaction(); 75 | 76 | try { 77 | foreach ($results['items'] as $result) { 78 | $user = new User(); 79 | $user->fromApiArray($result); 80 | $user->save(); 81 | } 82 | } catch (\Exception $e) { 83 | $this->db_->rollback(); 84 | throw $e; 85 | } 86 | 87 | $this->db_->commit(); 88 | 89 | sleep(1); 90 | } 91 | } 92 | 93 | public function refreshUsers() { 94 | $limit = 10000; 95 | $offset = 0; 96 | while (true) { 97 | $users = User::orderBy('user_id', 'asc') 98 | ->limit($limit) 99 | ->offset($offset) 100 | ->get(); 101 | 102 | if (!count($users)) break; 103 | 104 | $this->db_->beginTransaction(); 105 | try { 106 | foreach ($users as $user) { 107 | $u = json_decode($user->raw_json, true); 108 | $user->fromApiArray($u); 109 | $user->save(); 110 | } 111 | } catch (\Exception $e) { 112 | $this->db_->rollback(); 113 | throw $e; 114 | } 115 | $this->db_->commit(); 116 | 117 | $offset += $limit; 118 | } 119 | } 120 | 121 | 122 | } -------------------------------------------------------------------------------- /structure.sql: -------------------------------------------------------------------------------- 1 | # Questions 2 | 3 | CREATE TABLE `questions` ( 4 | `id` int(11) NOT NULL, 5 | `body_markdown` TEXT, 6 | `raw_json` TEXT, 7 | PRIMARY KEY (`id`) 8 | ) CHARACTER SET=utf8; 9 | ALTER TABLE questions ADD creation_date int(11) default "0"; 10 | ALTER TABLE questions ADD has_sql_injection tinyint(1) default "0"; 11 | ALTER TABLE questions ADD sql_injection_line int(11) default "0"; 12 | ALTER TABLE questions ADD is_processed tinyint(1) default "0"; 13 | ALTER TABLE questions ADD has_sql tinyint(1) default "0"; 14 | ALTER TABLE questions CHANGE id question_id int(11) NOT NULL; 15 | ALTER TABLE questions ADD updated_at datetime; -- For Eloquent: 16 | ALTER TABLE questions ADD created_at datetime; -- For Eloquent: 17 | UPDATE questions SET created_at = '2016-09-29 00:00:00', updated_at = '2016-09-29 00:00:00'; 18 | ALTER TABLE questions ADD owner_id int(11) default "0"; 19 | ALTER TABLE `questions` ADD INDEX `is_processed` (`is_processed`); 20 | ALTER TABLE `questions` ADD INDEX `creation_date` (`creation_date`); 21 | ALTER TABLE `questions` ADD INDEX `question_id` (`question_id`); 22 | ALTER TABLE `questions` ADD INDEX `has_sql` (`has_sql`); 23 | ALTER TABLE `questions` ADD INDEX `has_sql_injection` (`has_sql_injection`); 24 | ALTER TABLE `questions` ADD INDEX `owner_id` (`owner_id`); 25 | 26 | # Users 27 | 28 | CREATE TABLE `users` ( 29 | `user_id` int(11) NOT NULL, 30 | `age` int(11) NOT NULL default "0", 31 | `reputation` int(11) NOT NULL default "0", 32 | `is_employee` tinyint(1) NOT NULL default "0", 33 | `location` varchar(255) NOT NULL default "", 34 | `raw_json` TEXT NOT NULL DEFAULT "", 35 | PRIMARY KEY (`user_id`) 36 | ) CHARACTER SET=utf8; 37 | ALTER TABLE users ADD country varchar(2); 38 | ALTER TABLE users ADD updated_at datetime; 39 | ALTER TABLE users ADD created_at datetime; 40 | ALTER TABLE users CHANGE location location varchar(255) NULL; 41 | UPDATE users SET created_at = '2016-09-30 00:00:00', updated_at = '2016-09-30 00:00:00'; 42 | ALTER TABLE `users` ADD INDEX `user_id` (`user_id`); 43 | ALTER TABLE `users` ADD INDEX `country` (`country`); 44 | 45 | # Countries 46 | 47 | CREATE TABLE `countries` ( 48 | `geoname_id` int(11) NOT NULL, 49 | `code` varchar(2) NOT NULL, 50 | PRIMARY KEY (`geoname_id`) 51 | ) CHARACTER SET=utf8; 52 | ALTER TABLE `countries` ADD INDEX `geoname_id` (`geoname_id`); 53 | ALTER TABLE `countries` ADD INDEX `code` (`code`); 54 | 55 | # Cities 56 | 57 | CREATE TABLE `cities` ( 58 | `geoname_id` int(11) NOT NULL, 59 | `country_id` int(11) NOT NULL, 60 | PRIMARY KEY (`geoname_id`) 61 | ) CHARACTER SET=utf8; 62 | ALTER TABLE `cities` ADD INDEX `geoname_id` (`geoname_id`); 63 | ALTER TABLE `cities` ADD INDEX `country_id` (`country_id`); 64 | 65 | # Places 66 | 67 | CREATE TABLE `places` ( 68 | `id` int(11) NOT NULL AUTO_INCREMENT PRIMARY KEY, 69 | `city_id` int(11) NULL DEFAULT NULL, 70 | `country_id` int(11) NULL DEFAULT NULL, 71 | `name` varchar(64) NOT NULL 72 | ) CHARACTER SET=utf8; 73 | ALTER TABLE `places` ADD INDEX `city_id` (`city_id`); 74 | ALTER TABLE `places` ADD INDEX `country_id` (`country_id`); 75 | ALTER TABLE `places` ADD INDEX `name` (`name`); 76 | -------------------------------------------------------------------------------- /testimport.sublime-project: -------------------------------------------------------------------------------- 1 | { 2 | "folders": 3 | [ 4 | { 5 | "folder_exclude_patterns": 6 | [ 7 | "var/cache", 8 | "var/logs" 9 | ], 10 | "path": "." 11 | } 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /tests/AppBundle/Controller/DefaultControllerTest.php: -------------------------------------------------------------------------------- 1 | request('GET', '/'); 14 | 15 | $this->assertEquals(200, $client->getResponse()->getStatusCode()); 16 | $this->assertContains('Welcome to Symfony', $crawler->filter('#container h1')->text()); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /var/SymfonyRequirements.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | /* 13 | * Users of PHP 5.2 should be able to run the requirements checks. 14 | * This is why the file and all classes must be compatible with PHP 5.2+ 15 | * (e.g. not using namespaces and closures). 16 | * 17 | * ************** CAUTION ************** 18 | * 19 | * DO NOT EDIT THIS FILE as it will be overridden by Composer as part of 20 | * the installation/update process. The original file resides in the 21 | * SensioDistributionBundle. 22 | * 23 | * ************** CAUTION ************** 24 | */ 25 | 26 | /** 27 | * Represents a single PHP requirement, e.g. an installed extension. 28 | * It can be a mandatory requirement or an optional recommendation. 29 | * There is a special subclass, named PhpIniRequirement, to check a php.ini configuration. 30 | * 31 | * @author Tobias Schultze 32 | */ 33 | class Requirement 34 | { 35 | private $fulfilled; 36 | private $testMessage; 37 | private $helpText; 38 | private $helpHtml; 39 | private $optional; 40 | 41 | /** 42 | * Constructor that initializes the requirement. 43 | * 44 | * @param bool $fulfilled Whether the requirement is fulfilled 45 | * @param string $testMessage The message for testing the requirement 46 | * @param string $helpHtml The help text formatted in HTML for resolving the problem 47 | * @param string|null $helpText The help text (when null, it will be inferred from $helpHtml, i.e. stripped from HTML tags) 48 | * @param bool $optional Whether this is only an optional recommendation not a mandatory requirement 49 | */ 50 | public function __construct($fulfilled, $testMessage, $helpHtml, $helpText = null, $optional = false) 51 | { 52 | $this->fulfilled = (bool) $fulfilled; 53 | $this->testMessage = (string) $testMessage; 54 | $this->helpHtml = (string) $helpHtml; 55 | $this->helpText = null === $helpText ? strip_tags($this->helpHtml) : (string) $helpText; 56 | $this->optional = (bool) $optional; 57 | } 58 | 59 | /** 60 | * Returns whether the requirement is fulfilled. 61 | * 62 | * @return bool true if fulfilled, otherwise false 63 | */ 64 | public function isFulfilled() 65 | { 66 | return $this->fulfilled; 67 | } 68 | 69 | /** 70 | * Returns the message for testing the requirement. 71 | * 72 | * @return string The test message 73 | */ 74 | public function getTestMessage() 75 | { 76 | return $this->testMessage; 77 | } 78 | 79 | /** 80 | * Returns the help text for resolving the problem. 81 | * 82 | * @return string The help text 83 | */ 84 | public function getHelpText() 85 | { 86 | return $this->helpText; 87 | } 88 | 89 | /** 90 | * Returns the help text formatted in HTML. 91 | * 92 | * @return string The HTML help 93 | */ 94 | public function getHelpHtml() 95 | { 96 | return $this->helpHtml; 97 | } 98 | 99 | /** 100 | * Returns whether this is only an optional recommendation and not a mandatory requirement. 101 | * 102 | * @return bool true if optional, false if mandatory 103 | */ 104 | public function isOptional() 105 | { 106 | return $this->optional; 107 | } 108 | } 109 | 110 | /** 111 | * Represents a PHP requirement in form of a php.ini configuration. 112 | * 113 | * @author Tobias Schultze 114 | */ 115 | class PhpIniRequirement extends Requirement 116 | { 117 | /** 118 | * Constructor that initializes the requirement. 119 | * 120 | * @param string $cfgName The configuration name used for ini_get() 121 | * @param bool|callback $evaluation Either a boolean indicating whether the configuration should evaluate to true or false, 122 | * or a callback function receiving the configuration value as parameter to determine the fulfillment of the requirement 123 | * @param bool $approveCfgAbsence If true the Requirement will be fulfilled even if the configuration option does not exist, i.e. ini_get() returns false. 124 | * This is helpful for abandoned configs in later PHP versions or configs of an optional extension, like Suhosin. 125 | * Example: You require a config to be true but PHP later removes this config and defaults it to true internally. 126 | * @param string|null $testMessage The message for testing the requirement (when null and $evaluation is a boolean a default message is derived) 127 | * @param string|null $helpHtml The help text formatted in HTML for resolving the problem (when null and $evaluation is a boolean a default help is derived) 128 | * @param string|null $helpText The help text (when null, it will be inferred from $helpHtml, i.e. stripped from HTML tags) 129 | * @param bool $optional Whether this is only an optional recommendation not a mandatory requirement 130 | */ 131 | public function __construct($cfgName, $evaluation, $approveCfgAbsence = false, $testMessage = null, $helpHtml = null, $helpText = null, $optional = false) 132 | { 133 | $cfgValue = ini_get($cfgName); 134 | 135 | if (is_callable($evaluation)) { 136 | if (null === $testMessage || null === $helpHtml) { 137 | throw new InvalidArgumentException('You must provide the parameters testMessage and helpHtml for a callback evaluation.'); 138 | } 139 | 140 | $fulfilled = call_user_func($evaluation, $cfgValue); 141 | } else { 142 | if (null === $testMessage) { 143 | $testMessage = sprintf('%s %s be %s in php.ini', 144 | $cfgName, 145 | $optional ? 'should' : 'must', 146 | $evaluation ? 'enabled' : 'disabled' 147 | ); 148 | } 149 | 150 | if (null === $helpHtml) { 151 | $helpHtml = sprintf('Set %s to %s in php.ini*.', 152 | $cfgName, 153 | $evaluation ? 'on' : 'off' 154 | ); 155 | } 156 | 157 | $fulfilled = $evaluation == $cfgValue; 158 | } 159 | 160 | parent::__construct($fulfilled || ($approveCfgAbsence && false === $cfgValue), $testMessage, $helpHtml, $helpText, $optional); 161 | } 162 | } 163 | 164 | /** 165 | * A RequirementCollection represents a set of Requirement instances. 166 | * 167 | * @author Tobias Schultze 168 | */ 169 | class RequirementCollection implements IteratorAggregate 170 | { 171 | /** 172 | * @var Requirement[] 173 | */ 174 | private $requirements = array(); 175 | 176 | /** 177 | * Gets the current RequirementCollection as an Iterator. 178 | * 179 | * @return Traversable A Traversable interface 180 | */ 181 | public function getIterator() 182 | { 183 | return new ArrayIterator($this->requirements); 184 | } 185 | 186 | /** 187 | * Adds a Requirement. 188 | * 189 | * @param Requirement $requirement A Requirement instance 190 | */ 191 | public function add(Requirement $requirement) 192 | { 193 | $this->requirements[] = $requirement; 194 | } 195 | 196 | /** 197 | * Adds a mandatory requirement. 198 | * 199 | * @param bool $fulfilled Whether the requirement is fulfilled 200 | * @param string $testMessage The message for testing the requirement 201 | * @param string $helpHtml The help text formatted in HTML for resolving the problem 202 | * @param string|null $helpText The help text (when null, it will be inferred from $helpHtml, i.e. stripped from HTML tags) 203 | */ 204 | public function addRequirement($fulfilled, $testMessage, $helpHtml, $helpText = null) 205 | { 206 | $this->add(new Requirement($fulfilled, $testMessage, $helpHtml, $helpText, false)); 207 | } 208 | 209 | /** 210 | * Adds an optional recommendation. 211 | * 212 | * @param bool $fulfilled Whether the recommendation is fulfilled 213 | * @param string $testMessage The message for testing the recommendation 214 | * @param string $helpHtml The help text formatted in HTML for resolving the problem 215 | * @param string|null $helpText The help text (when null, it will be inferred from $helpHtml, i.e. stripped from HTML tags) 216 | */ 217 | public function addRecommendation($fulfilled, $testMessage, $helpHtml, $helpText = null) 218 | { 219 | $this->add(new Requirement($fulfilled, $testMessage, $helpHtml, $helpText, true)); 220 | } 221 | 222 | /** 223 | * Adds a mandatory requirement in form of a php.ini configuration. 224 | * 225 | * @param string $cfgName The configuration name used for ini_get() 226 | * @param bool|callback $evaluation Either a boolean indicating whether the configuration should evaluate to true or false, 227 | * or a callback function receiving the configuration value as parameter to determine the fulfillment of the requirement 228 | * @param bool $approveCfgAbsence If true the Requirement will be fulfilled even if the configuration option does not exist, i.e. ini_get() returns false. 229 | * This is helpful for abandoned configs in later PHP versions or configs of an optional extension, like Suhosin. 230 | * Example: You require a config to be true but PHP later removes this config and defaults it to true internally. 231 | * @param string $testMessage The message for testing the requirement (when null and $evaluation is a boolean a default message is derived) 232 | * @param string $helpHtml The help text formatted in HTML for resolving the problem (when null and $evaluation is a boolean a default help is derived) 233 | * @param string|null $helpText The help text (when null, it will be inferred from $helpHtml, i.e. stripped from HTML tags) 234 | */ 235 | public function addPhpIniRequirement($cfgName, $evaluation, $approveCfgAbsence = false, $testMessage = null, $helpHtml = null, $helpText = null) 236 | { 237 | $this->add(new PhpIniRequirement($cfgName, $evaluation, $approveCfgAbsence, $testMessage, $helpHtml, $helpText, false)); 238 | } 239 | 240 | /** 241 | * Adds an optional recommendation in form of a php.ini configuration. 242 | * 243 | * @param string $cfgName The configuration name used for ini_get() 244 | * @param bool|callback $evaluation Either a boolean indicating whether the configuration should evaluate to true or false, 245 | * or a callback function receiving the configuration value as parameter to determine the fulfillment of the requirement 246 | * @param bool $approveCfgAbsence If true the Requirement will be fulfilled even if the configuration option does not exist, i.e. ini_get() returns false. 247 | * This is helpful for abandoned configs in later PHP versions or configs of an optional extension, like Suhosin. 248 | * Example: You require a config to be true but PHP later removes this config and defaults it to true internally. 249 | * @param string $testMessage The message for testing the requirement (when null and $evaluation is a boolean a default message is derived) 250 | * @param string $helpHtml The help text formatted in HTML for resolving the problem (when null and $evaluation is a boolean a default help is derived) 251 | * @param string|null $helpText The help text (when null, it will be inferred from $helpHtml, i.e. stripped from HTML tags) 252 | */ 253 | public function addPhpIniRecommendation($cfgName, $evaluation, $approveCfgAbsence = false, $testMessage = null, $helpHtml = null, $helpText = null) 254 | { 255 | $this->add(new PhpIniRequirement($cfgName, $evaluation, $approveCfgAbsence, $testMessage, $helpHtml, $helpText, true)); 256 | } 257 | 258 | /** 259 | * Adds a requirement collection to the current set of requirements. 260 | * 261 | * @param RequirementCollection $collection A RequirementCollection instance 262 | */ 263 | public function addCollection(RequirementCollection $collection) 264 | { 265 | $this->requirements = array_merge($this->requirements, $collection->all()); 266 | } 267 | 268 | /** 269 | * Returns both requirements and recommendations. 270 | * 271 | * @return Requirement[] 272 | */ 273 | public function all() 274 | { 275 | return $this->requirements; 276 | } 277 | 278 | /** 279 | * Returns all mandatory requirements. 280 | * 281 | * @return Requirement[] 282 | */ 283 | public function getRequirements() 284 | { 285 | $array = array(); 286 | foreach ($this->requirements as $req) { 287 | if (!$req->isOptional()) { 288 | $array[] = $req; 289 | } 290 | } 291 | 292 | return $array; 293 | } 294 | 295 | /** 296 | * Returns the mandatory requirements that were not met. 297 | * 298 | * @return Requirement[] 299 | */ 300 | public function getFailedRequirements() 301 | { 302 | $array = array(); 303 | foreach ($this->requirements as $req) { 304 | if (!$req->isFulfilled() && !$req->isOptional()) { 305 | $array[] = $req; 306 | } 307 | } 308 | 309 | return $array; 310 | } 311 | 312 | /** 313 | * Returns all optional recommendations. 314 | * 315 | * @return Requirement[] 316 | */ 317 | public function getRecommendations() 318 | { 319 | $array = array(); 320 | foreach ($this->requirements as $req) { 321 | if ($req->isOptional()) { 322 | $array[] = $req; 323 | } 324 | } 325 | 326 | return $array; 327 | } 328 | 329 | /** 330 | * Returns the recommendations that were not met. 331 | * 332 | * @return Requirement[] 333 | */ 334 | public function getFailedRecommendations() 335 | { 336 | $array = array(); 337 | foreach ($this->requirements as $req) { 338 | if (!$req->isFulfilled() && $req->isOptional()) { 339 | $array[] = $req; 340 | } 341 | } 342 | 343 | return $array; 344 | } 345 | 346 | /** 347 | * Returns whether a php.ini configuration is not correct. 348 | * 349 | * @return bool php.ini configuration problem? 350 | */ 351 | public function hasPhpIniConfigIssue() 352 | { 353 | foreach ($this->requirements as $req) { 354 | if (!$req->isFulfilled() && $req instanceof PhpIniRequirement) { 355 | return true; 356 | } 357 | } 358 | 359 | return false; 360 | } 361 | 362 | /** 363 | * Returns the PHP configuration file (php.ini) path. 364 | * 365 | * @return string|false php.ini file path 366 | */ 367 | public function getPhpIniConfigPath() 368 | { 369 | return get_cfg_var('cfg_file_path'); 370 | } 371 | } 372 | 373 | /** 374 | * This class specifies all requirements and optional recommendations that 375 | * are necessary to run the Symfony Standard Edition. 376 | * 377 | * @author Tobias Schultze 378 | * @author Fabien Potencier 379 | */ 380 | class SymfonyRequirements extends RequirementCollection 381 | { 382 | const LEGACY_REQUIRED_PHP_VERSION = '5.3.3'; 383 | const REQUIRED_PHP_VERSION = '5.5.9'; 384 | 385 | /** 386 | * Constructor that initializes the requirements. 387 | */ 388 | public function __construct() 389 | { 390 | /* mandatory requirements follow */ 391 | 392 | $installedPhpVersion = phpversion(); 393 | $requiredPhpVersion = $this->getPhpRequiredVersion(); 394 | 395 | $this->addRecommendation( 396 | $requiredPhpVersion, 397 | 'Vendors should be installed in order to check all requirements.', 398 | 'Run the composer install command.', 399 | 'Run the "composer install" command.' 400 | ); 401 | 402 | if (false !== $requiredPhpVersion) { 403 | $this->addRequirement( 404 | version_compare($installedPhpVersion, $requiredPhpVersion, '>='), 405 | sprintf('PHP version must be at least %s (%s installed)', $requiredPhpVersion, $installedPhpVersion), 406 | sprintf('You are running PHP version "%s", but Symfony needs at least PHP "%s" to run. 407 | Before using Symfony, upgrade your PHP installation, preferably to the latest version.', 408 | $installedPhpVersion, $requiredPhpVersion), 409 | sprintf('Install PHP %s or newer (installed version is %s)', $requiredPhpVersion, $installedPhpVersion) 410 | ); 411 | } 412 | 413 | $this->addRequirement( 414 | version_compare($installedPhpVersion, '5.3.16', '!='), 415 | 'PHP version must not be 5.3.16 as Symfony won\'t work properly with it', 416 | 'Install PHP 5.3.17 or newer (or downgrade to an earlier PHP version)' 417 | ); 418 | 419 | $this->addRequirement( 420 | is_dir(__DIR__.'/../vendor/composer'), 421 | 'Vendor libraries must be installed', 422 | 'Vendor libraries are missing. Install composer following instructions from http://getcomposer.org/. '. 423 | 'Then run "php composer.phar install" to install them.' 424 | ); 425 | 426 | $cacheDir = is_dir(__DIR__.'/../var/cache') ? __DIR__.'/../var/cache' : __DIR__.'/cache'; 427 | 428 | $this->addRequirement( 429 | is_writable($cacheDir), 430 | 'app/cache/ or var/cache/ directory must be writable', 431 | 'Change the permissions of either "app/cache/" or "var/cache/" directory so that the web server can write into it.' 432 | ); 433 | 434 | $logsDir = is_dir(__DIR__.'/../var/logs') ? __DIR__.'/../var/logs' : __DIR__.'/logs'; 435 | 436 | $this->addRequirement( 437 | is_writable($logsDir), 438 | 'app/logs/ or var/logs/ directory must be writable', 439 | 'Change the permissions of either "app/logs/" or "var/logs/" directory so that the web server can write into it.' 440 | ); 441 | 442 | if (version_compare($installedPhpVersion, '7.0.0', '<')) { 443 | $this->addPhpIniRequirement( 444 | 'date.timezone', true, false, 445 | 'date.timezone setting must be set', 446 | 'Set the "date.timezone" setting in php.ini* (like Europe/Paris).' 447 | ); 448 | } 449 | 450 | if (false !== $requiredPhpVersion && version_compare($installedPhpVersion, $requiredPhpVersion, '>=')) { 451 | $timezones = array(); 452 | foreach (DateTimeZone::listAbbreviations() as $abbreviations) { 453 | foreach ($abbreviations as $abbreviation) { 454 | $timezones[$abbreviation['timezone_id']] = true; 455 | } 456 | } 457 | 458 | $this->addRequirement( 459 | isset($timezones[@date_default_timezone_get()]), 460 | sprintf('Configured default timezone "%s" must be supported by your installation of PHP', @date_default_timezone_get()), 461 | 'Your default timezone is not supported by PHP. Check for typos in your php.ini file and have a look at the list of deprecated timezones at http://php.net/manual/en/timezones.others.php.' 462 | ); 463 | } 464 | 465 | $this->addRequirement( 466 | function_exists('iconv'), 467 | 'iconv() must be available', 468 | 'Install and enable the iconv extension.' 469 | ); 470 | 471 | $this->addRequirement( 472 | function_exists('json_encode'), 473 | 'json_encode() must be available', 474 | 'Install and enable the JSON extension.' 475 | ); 476 | 477 | $this->addRequirement( 478 | function_exists('session_start'), 479 | 'session_start() must be available', 480 | 'Install and enable the session extension.' 481 | ); 482 | 483 | $this->addRequirement( 484 | function_exists('ctype_alpha'), 485 | 'ctype_alpha() must be available', 486 | 'Install and enable the ctype extension.' 487 | ); 488 | 489 | $this->addRequirement( 490 | function_exists('token_get_all'), 491 | 'token_get_all() must be available', 492 | 'Install and enable the Tokenizer extension.' 493 | ); 494 | 495 | $this->addRequirement( 496 | function_exists('simplexml_import_dom'), 497 | 'simplexml_import_dom() must be available', 498 | 'Install and enable the SimpleXML extension.' 499 | ); 500 | 501 | if (function_exists('apc_store') && ini_get('apc.enabled')) { 502 | if (version_compare($installedPhpVersion, '5.4.0', '>=')) { 503 | $this->addRequirement( 504 | version_compare(phpversion('apc'), '3.1.13', '>='), 505 | 'APC version must be at least 3.1.13 when using PHP 5.4', 506 | 'Upgrade your APC extension (3.1.13+).' 507 | ); 508 | } else { 509 | $this->addRequirement( 510 | version_compare(phpversion('apc'), '3.0.17', '>='), 511 | 'APC version must be at least 3.0.17', 512 | 'Upgrade your APC extension (3.0.17+).' 513 | ); 514 | } 515 | } 516 | 517 | $this->addPhpIniRequirement('detect_unicode', false); 518 | 519 | if (extension_loaded('suhosin')) { 520 | $this->addPhpIniRequirement( 521 | 'suhosin.executor.include.whitelist', 522 | create_function('$cfgValue', 'return false !== stripos($cfgValue, "phar");'), 523 | false, 524 | 'suhosin.executor.include.whitelist must be configured correctly in php.ini', 525 | 'Add "phar" to suhosin.executor.include.whitelist in php.ini*.' 526 | ); 527 | } 528 | 529 | if (extension_loaded('xdebug')) { 530 | $this->addPhpIniRequirement( 531 | 'xdebug.show_exception_trace', false, true 532 | ); 533 | 534 | $this->addPhpIniRequirement( 535 | 'xdebug.scream', false, true 536 | ); 537 | 538 | $this->addPhpIniRecommendation( 539 | 'xdebug.max_nesting_level', 540 | create_function('$cfgValue', 'return $cfgValue > 100;'), 541 | true, 542 | 'xdebug.max_nesting_level should be above 100 in php.ini', 543 | 'Set "xdebug.max_nesting_level" to e.g. "250" in php.ini* to stop Xdebug\'s infinite recursion protection erroneously throwing a fatal error in your project.' 544 | ); 545 | } 546 | 547 | $pcreVersion = defined('PCRE_VERSION') ? (float) PCRE_VERSION : null; 548 | 549 | $this->addRequirement( 550 | null !== $pcreVersion, 551 | 'PCRE extension must be available', 552 | 'Install the PCRE extension (version 8.0+).' 553 | ); 554 | 555 | if (extension_loaded('mbstring')) { 556 | $this->addPhpIniRequirement( 557 | 'mbstring.func_overload', 558 | create_function('$cfgValue', 'return (int) $cfgValue === 0;'), 559 | true, 560 | 'string functions should not be overloaded', 561 | 'Set "mbstring.func_overload" to 0 in php.ini* to disable function overloading by the mbstring extension.' 562 | ); 563 | } 564 | 565 | /* optional recommendations follow */ 566 | 567 | if (file_exists(__DIR__.'/../vendor/composer')) { 568 | require_once __DIR__.'/../vendor/autoload.php'; 569 | 570 | try { 571 | $r = new ReflectionClass('Sensio\Bundle\DistributionBundle\SensioDistributionBundle'); 572 | 573 | $contents = file_get_contents(dirname($r->getFileName()).'/Resources/skeleton/app/SymfonyRequirements.php'); 574 | } catch (ReflectionException $e) { 575 | $contents = ''; 576 | } 577 | $this->addRecommendation( 578 | file_get_contents(__FILE__) === $contents, 579 | 'Requirements file should be up-to-date', 580 | 'Your requirements file is outdated. Run composer install and re-check your configuration.' 581 | ); 582 | } 583 | 584 | $this->addRecommendation( 585 | version_compare($installedPhpVersion, '5.3.4', '>='), 586 | 'You should use at least PHP 5.3.4 due to PHP bug #52083 in earlier versions', 587 | 'Your project might malfunction randomly due to PHP bug #52083 ("Notice: Trying to get property of non-object"). Install PHP 5.3.4 or newer.' 588 | ); 589 | 590 | $this->addRecommendation( 591 | version_compare($installedPhpVersion, '5.3.8', '>='), 592 | 'When using annotations you should have at least PHP 5.3.8 due to PHP bug #55156', 593 | 'Install PHP 5.3.8 or newer if your project uses annotations.' 594 | ); 595 | 596 | $this->addRecommendation( 597 | version_compare($installedPhpVersion, '5.4.0', '!='), 598 | 'You should not use PHP 5.4.0 due to the PHP bug #61453', 599 | 'Your project might not work properly due to the PHP bug #61453 ("Cannot dump definitions which have method calls"). Install PHP 5.4.1 or newer.' 600 | ); 601 | 602 | $this->addRecommendation( 603 | version_compare($installedPhpVersion, '5.4.11', '>='), 604 | 'When using the logout handler from the Symfony Security Component, you should have at least PHP 5.4.11 due to PHP bug #63379 (as a workaround, you can also set invalidate_session to false in the security logout handler configuration)', 605 | 'Install PHP 5.4.11 or newer if your project uses the logout handler from the Symfony Security Component.' 606 | ); 607 | 608 | $this->addRecommendation( 609 | (version_compare($installedPhpVersion, '5.3.18', '>=') && version_compare($installedPhpVersion, '5.4.0', '<')) 610 | || 611 | version_compare($installedPhpVersion, '5.4.8', '>='), 612 | 'You should use PHP 5.3.18+ or PHP 5.4.8+ to always get nice error messages for fatal errors in the development environment due to PHP bug #61767/#60909', 613 | 'Install PHP 5.3.18+ or PHP 5.4.8+ if you want nice error messages for all fatal errors in the development environment.' 614 | ); 615 | 616 | if (null !== $pcreVersion) { 617 | $this->addRecommendation( 618 | $pcreVersion >= 8.0, 619 | sprintf('PCRE extension should be at least version 8.0 (%s installed)', $pcreVersion), 620 | 'PCRE 8.0+ is preconfigured in PHP since 5.3.2 but you are using an outdated version of it. Symfony probably works anyway but it is recommended to upgrade your PCRE extension.' 621 | ); 622 | } 623 | 624 | $this->addRecommendation( 625 | class_exists('DomDocument'), 626 | 'PHP-DOM and PHP-XML modules should be installed', 627 | 'Install and enable the PHP-DOM and the PHP-XML modules.' 628 | ); 629 | 630 | $this->addRecommendation( 631 | function_exists('mb_strlen'), 632 | 'mb_strlen() should be available', 633 | 'Install and enable the mbstring extension.' 634 | ); 635 | 636 | $this->addRecommendation( 637 | function_exists('iconv'), 638 | 'iconv() should be available', 639 | 'Install and enable the iconv extension.' 640 | ); 641 | 642 | $this->addRecommendation( 643 | function_exists('utf8_decode'), 644 | 'utf8_decode() should be available', 645 | 'Install and enable the XML extension.' 646 | ); 647 | 648 | $this->addRecommendation( 649 | function_exists('filter_var'), 650 | 'filter_var() should be available', 651 | 'Install and enable the filter extension.' 652 | ); 653 | 654 | if (!defined('PHP_WINDOWS_VERSION_BUILD')) { 655 | $this->addRecommendation( 656 | function_exists('posix_isatty'), 657 | 'posix_isatty() should be available', 658 | 'Install and enable the php_posix extension (used to colorize the CLI output).' 659 | ); 660 | } 661 | 662 | $this->addRecommendation( 663 | extension_loaded('intl'), 664 | 'intl extension should be available', 665 | 'Install and enable the intl extension (used for validators).' 666 | ); 667 | 668 | if (extension_loaded('intl')) { 669 | // in some WAMP server installations, new Collator() returns null 670 | $this->addRecommendation( 671 | null !== new Collator('fr_FR'), 672 | 'intl extension should be correctly configured', 673 | 'The intl extension does not behave properly. This problem is typical on PHP 5.3.X x64 WIN builds.' 674 | ); 675 | 676 | // check for compatible ICU versions (only done when you have the intl extension) 677 | if (defined('INTL_ICU_VERSION')) { 678 | $version = INTL_ICU_VERSION; 679 | } else { 680 | $reflector = new ReflectionExtension('intl'); 681 | 682 | ob_start(); 683 | $reflector->info(); 684 | $output = strip_tags(ob_get_clean()); 685 | 686 | preg_match('/^ICU version +(?:=> )?(.*)$/m', $output, $matches); 687 | $version = $matches[1]; 688 | } 689 | 690 | $this->addRecommendation( 691 | version_compare($version, '4.0', '>='), 692 | 'intl ICU version should be at least 4+', 693 | 'Upgrade your intl extension with a newer ICU version (4+).' 694 | ); 695 | 696 | if (class_exists('Symfony\Component\Intl\Intl')) { 697 | $this->addRecommendation( 698 | \Symfony\Component\Intl\Intl::getIcuDataVersion() <= \Symfony\Component\Intl\Intl::getIcuVersion(), 699 | sprintf('intl ICU version installed on your system is outdated (%s) and does not match the ICU data bundled with Symfony (%s)', \Symfony\Component\Intl\Intl::getIcuVersion(), \Symfony\Component\Intl\Intl::getIcuDataVersion()), 700 | 'To get the latest internationalization data upgrade the ICU system package and the intl PHP extension.' 701 | ); 702 | if (\Symfony\Component\Intl\Intl::getIcuDataVersion() <= \Symfony\Component\Intl\Intl::getIcuVersion()) { 703 | $this->addRecommendation( 704 | \Symfony\Component\Intl\Intl::getIcuDataVersion() === \Symfony\Component\Intl\Intl::getIcuVersion(), 705 | sprintf('intl ICU version installed on your system (%s) does not match the ICU data bundled with Symfony (%s)', \Symfony\Component\Intl\Intl::getIcuVersion(), \Symfony\Component\Intl\Intl::getIcuDataVersion()), 706 | 'To avoid internationalization data inconsistencies upgrade the symfony/intl component.' 707 | ); 708 | } 709 | } 710 | 711 | $this->addPhpIniRecommendation( 712 | 'intl.error_level', 713 | create_function('$cfgValue', 'return (int) $cfgValue === 0;'), 714 | true, 715 | 'intl.error_level should be 0 in php.ini', 716 | 'Set "intl.error_level" to "0" in php.ini* to inhibit the messages when an error occurs in ICU functions.' 717 | ); 718 | } 719 | 720 | $accelerator = 721 | (extension_loaded('eaccelerator') && ini_get('eaccelerator.enable')) 722 | || 723 | (extension_loaded('apc') && ini_get('apc.enabled')) 724 | || 725 | (extension_loaded('Zend Optimizer+') && ini_get('zend_optimizerplus.enable')) 726 | || 727 | (extension_loaded('Zend OPcache') && ini_get('opcache.enable')) 728 | || 729 | (extension_loaded('xcache') && ini_get('xcache.cacher')) 730 | || 731 | (extension_loaded('wincache') && ini_get('wincache.ocenabled')) 732 | ; 733 | 734 | $this->addRecommendation( 735 | $accelerator, 736 | 'a PHP accelerator should be installed', 737 | 'Install and/or enable a PHP accelerator (highly recommended).' 738 | ); 739 | 740 | if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') { 741 | $this->addRecommendation( 742 | $this->getRealpathCacheSize() >= 5 * 1024 * 1024, 743 | 'realpath_cache_size should be at least 5M in php.ini', 744 | 'Setting "realpath_cache_size" to e.g. "5242880" or "5M" in php.ini* may improve performance on Windows significantly in some cases.' 745 | ); 746 | } 747 | 748 | $this->addPhpIniRecommendation('short_open_tag', false); 749 | 750 | $this->addPhpIniRecommendation('magic_quotes_gpc', false, true); 751 | 752 | $this->addPhpIniRecommendation('register_globals', false, true); 753 | 754 | $this->addPhpIniRecommendation('session.auto_start', false); 755 | 756 | $this->addRecommendation( 757 | class_exists('PDO'), 758 | 'PDO should be installed', 759 | 'Install PDO (mandatory for Doctrine).' 760 | ); 761 | 762 | if (class_exists('PDO')) { 763 | $drivers = PDO::getAvailableDrivers(); 764 | $this->addRecommendation( 765 | count($drivers) > 0, 766 | sprintf('PDO should have some drivers installed (currently available: %s)', count($drivers) ? implode(', ', $drivers) : 'none'), 767 | 'Install PDO drivers (mandatory for Doctrine).' 768 | ); 769 | } 770 | } 771 | 772 | /** 773 | * Loads realpath_cache_size from php.ini and converts it to int. 774 | * 775 | * (e.g. 16k is converted to 16384 int) 776 | * 777 | * @return int 778 | */ 779 | protected function getRealpathCacheSize() 780 | { 781 | $size = ini_get('realpath_cache_size'); 782 | $size = trim($size); 783 | $unit = strtolower(substr($size, -1, 1)); 784 | switch ($unit) { 785 | case 'g': 786 | return $size * 1024 * 1024 * 1024; 787 | case 'm': 788 | return $size * 1024 * 1024; 789 | case 'k': 790 | return $size * 1024; 791 | default: 792 | return (int) $size; 793 | } 794 | } 795 | 796 | /** 797 | * Defines PHP required version from Symfony version. 798 | * 799 | * @return string|false The PHP required version or false if it could not be guessed 800 | */ 801 | protected function getPhpRequiredVersion() 802 | { 803 | if (!file_exists($path = __DIR__.'/../composer.lock')) { 804 | return false; 805 | } 806 | 807 | $composerLock = json_decode(file_get_contents($path), true); 808 | foreach ($composerLock['packages'] as $package) { 809 | $name = $package['name']; 810 | if ('symfony/symfony' !== $name && 'symfony/http-kernel' !== $name) { 811 | continue; 812 | } 813 | 814 | return (int) $package['version'][1] > 2 ? self::REQUIRED_PHP_VERSION : self::LEGACY_REQUIRED_PHP_VERSION; 815 | } 816 | 817 | return false; 818 | } 819 | } 820 | -------------------------------------------------------------------------------- /var/cache/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/laurent22/so-sql-injections/6515f8bc1b7f6f3489411ffda168a000be7b6257/var/cache/.gitkeep -------------------------------------------------------------------------------- /var/logs/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/laurent22/so-sql-injections/6515f8bc1b7f6f3489411ffda168a000be7b6257/var/logs/.gitkeep -------------------------------------------------------------------------------- /var/places/README.md: -------------------------------------------------------------------------------- 1 | Download from http://www.geonames.org/ 2 | 3 | - allCountries.txt 4 | - cities1000.txt 5 | - countryInfo.txt -------------------------------------------------------------------------------- /var/sessions/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/laurent22/so-sql-injections/6515f8bc1b7f6f3489411ffda168a000be7b6257/var/sessions/.gitkeep -------------------------------------------------------------------------------- /web/.htaccess: -------------------------------------------------------------------------------- 1 | # Use the front controller as index file. It serves as a fallback solution when 2 | # every other rewrite/redirect fails (e.g. in an aliased environment without 3 | # mod_rewrite). Additionally, this reduces the matching process for the 4 | # start page (path "/") because otherwise Apache will apply the rewriting rules 5 | # to each configured DirectoryIndex file (e.g. index.php, index.html, index.pl). 6 | DirectoryIndex app.php 7 | 8 | # By default, Apache does not evaluate symbolic links if you did not enable this 9 | # feature in your server configuration. Uncomment the following line if you 10 | # install assets as symlinks or if you experience problems related to symlinks 11 | # when compiling LESS/Sass/CoffeScript assets. 12 | # Options FollowSymlinks 13 | 14 | # Disabling MultiViews prevents unwanted negotiation, e.g. "/app" should not resolve 15 | # to the front controller "/app.php" but be rewritten to "/app.php/app". 16 | 17 | Options -MultiViews 18 | 19 | 20 | 21 | RewriteEngine On 22 | 23 | # Determine the RewriteBase automatically and set it as environment variable. 24 | # If you are using Apache aliases to do mass virtual hosting or installed the 25 | # project in a subdirectory, the base path will be prepended to allow proper 26 | # resolution of the app.php file and to redirect to the correct URI. It will 27 | # work in environments without path prefix as well, providing a safe, one-size 28 | # fits all solution. But as you do not need it in this case, you can comment 29 | # the following 2 lines to eliminate the overhead. 30 | RewriteCond %{REQUEST_URI}::$1 ^(/.+)/(.*)::\2$ 31 | RewriteRule ^(.*) - [E=BASE:%1] 32 | 33 | # Sets the HTTP_AUTHORIZATION header removed by Apache 34 | RewriteCond %{HTTP:Authorization} . 35 | RewriteRule ^ - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}] 36 | 37 | # Redirect to URI without front controller to prevent duplicate content 38 | # (with and without `/app.php`). Only do this redirect on the initial 39 | # rewrite by Apache and not on subsequent cycles. Otherwise we would get an 40 | # endless redirect loop (request -> rewrite to front controller -> 41 | # redirect -> request -> ...). 42 | # So in case you get a "too many redirects" error or you always get redirected 43 | # to the start page because your Apache does not expose the REDIRECT_STATUS 44 | # environment variable, you have 2 choices: 45 | # - disable this feature by commenting the following 2 lines or 46 | # - use Apache >= 2.3.9 and replace all L flags by END flags and remove the 47 | # following RewriteCond (best solution) 48 | RewriteCond %{ENV:REDIRECT_STATUS} ^$ 49 | RewriteRule ^app\.php(?:/(.*)|$) %{ENV:BASE}/$1 [R=301,L] 50 | 51 | # If the requested filename exists, simply serve it. 52 | # We only want to let Apache serve files and not directories. 53 | RewriteCond %{REQUEST_FILENAME} -f 54 | RewriteRule ^ - [L] 55 | 56 | # Rewrite all other queries to the front controller. 57 | RewriteRule ^ %{ENV:BASE}/app.php [L] 58 | 59 | 60 | 61 | 62 | # When mod_rewrite is not available, we instruct a temporary redirect of 63 | # the start page to the front controller explicitly so that the website 64 | # and the generated links can still be used. 65 | RedirectMatch 302 ^/$ /app.php/ 66 | # RedirectTemp cannot be used instead 67 | 68 | 69 | -------------------------------------------------------------------------------- /web/app.php: -------------------------------------------------------------------------------- 1 | loadClassCache(); 13 | //$kernel = new AppCache($kernel); 14 | 15 | // When using the HttpCache, you need to call the method in your front controller instead of relying on the configuration parameter 16 | //Request::enableHttpMethodParameterOverride(); 17 | $request = Request::createFromGlobals(); 18 | $response = $kernel->handle($request); 19 | $response->send(); 20 | $kernel->terminate($request, $response); 21 | -------------------------------------------------------------------------------- /web/app_dev.php: -------------------------------------------------------------------------------- 1 | loadClassCache(); 31 | $request = Request::createFromGlobals(); 32 | $response = $kernel->handle($request); 33 | $response->send(); 34 | $kernel->terminate($request, $response); 35 | -------------------------------------------------------------------------------- /web/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/laurent22/so-sql-injections/6515f8bc1b7f6f3489411ffda168a000be7b6257/web/apple-touch-icon.png -------------------------------------------------------------------------------- /web/config.php: -------------------------------------------------------------------------------- 1 | getFailedRequirements(); 30 | $minorProblems = $symfonyRequirements->getFailedRecommendations(); 31 | $hasMajorProblems = (bool) count($majorProblems); 32 | $hasMinorProblems = (bool) count($minorProblems); 33 | 34 | ?> 35 | 36 | 37 | 38 | 39 | 40 | Symfony Configuration Checker 41 | 42 | 43 | 124 | 125 | 126 |
127 |
128 | 131 | 132 | 152 |
153 | 154 |
155 |
156 |
157 |

Configuration Checker

158 |

159 | This script analyzes your system to check whether is 160 | ready to run Symfony applications. 161 |

162 | 163 | 164 |

Major problems

165 |

Major problems have been detected and must be fixed before continuing:

166 |
    167 | 168 |
  1. getTestMessage() ?> 169 |

    getHelpHtml() ?>

    170 |
  2. 171 | 172 |
173 | 174 | 175 | 176 |

Recommendations

177 |

178 | Additionally, toTo enhance your Symfony experience, 179 | it’s recommended that you fix the following: 180 |

181 |
    182 | 183 |
  1. getTestMessage() ?> 184 |

    getHelpHtml() ?>

    185 |
  2. 186 | 187 |
188 | 189 | 190 | hasPhpIniConfigIssue()): ?> 191 |

* 192 | getPhpIniConfigPath()): ?> 193 | Changes to the php.ini file must be done in "getPhpIniConfigPath() ?>". 194 | 195 | To change settings, create a "php.ini". 196 | 197 |

198 | 199 | 200 | 201 |

All checks passed successfully. Your system is ready to run Symfony applications.

202 | 203 | 204 | 209 |
210 |
211 |
212 |
Symfony Standard Edition
213 |
214 | 215 | 216 | -------------------------------------------------------------------------------- /web/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/laurent22/so-sql-injections/6515f8bc1b7f6f3489411ffda168a000be7b6257/web/favicon.ico -------------------------------------------------------------------------------- /web/index_template.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 60 | 61 | 62 | 63 |
64 |

Potential SQL injections vulnerabilities in Stack Overflow PHP questions

65 | 66 |
67 | 68 | 69 | 70 |

Latest SQL injection vulnerabilities in PHP questions

71 |

(Click on the date to open the question)

72 |
73 | 74 |
75 |
76 | 77 | 302 | 303 | 304 | -------------------------------------------------------------------------------- /web/robots.txt: -------------------------------------------------------------------------------- 1 | # www.robotstxt.org/ 2 | # www.google.com/support/webmasters/bin/answer.py?hl=en&answer=156449 3 | 4 | User-agent: * 5 | Disallow: 6 | --------------------------------------------------------------------------------