├── .gitignore ├── .travis.yml ├── CHANGELOG ├── LICENSE ├── composer.json ├── phpunit.xml ├── readme.md ├── src └── SleepingOwl │ └── Apist │ ├── Apist.php │ ├── DomCrawler │ └── Crawler.php │ ├── Methods │ └── ApistMethod.php │ ├── Selectors │ ├── ApistFilter.php │ ├── ApistSelector.php │ └── ResultCallback.php │ └── Yaml │ ├── Parser.php │ └── YamlApist.php └── tests ├── .gitkeep ├── ApistMethodTest.php ├── ApistTest.php ├── TestApi.php └── stub └── index.html /.gitignore: -------------------------------------------------------------------------------- 1 | /vendor 2 | composer.phar 3 | composer.lock 4 | .DS_Store 5 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: php 2 | 3 | php: 4 | - 5.4 5 | - 5.5 6 | - 5.6 7 | - hhvm 8 | 9 | before_script: 10 | - travis_retry composer self-update 11 | - travis_retry composer install --prefer-source --no-interaction --dev 12 | 13 | script: phpunit 14 | -------------------------------------------------------------------------------- /CHANGELOG: -------------------------------------------------------------------------------- 1 | CHANGELOG 2 | ========= 3 | 4 | 2014-11-18, v1.3.3 5 | ------------------ 6 | 7 | * HTTP-error handler updated 8 | 9 | 2014-11-10, v1.3.2 10 | ------------------ 11 | 12 | * Blueprint marked as optional parameter in request. You can get request result without parsing if blueprint is omitted. 13 | 14 | 2014-10-31, v1.3.1 15 | ------------------ 16 | 17 | * Exception messages improvements. Now you'll see filter that throws an exception. 18 | 19 | 2014-10-30, v1.3.0 20 | ------------------ 21 | 22 | * New method Apist::current() to create filter based on current node element. Usefull in ->each() callback. 23 | * New YAML default structure :current. Same as Apist::current() method. 24 | * New pseudo class support in css selectors: :first, :last, :eq(pos). 25 | * Code cleanup: all basic filters was moved to ApistFilter class, your IDE now will work better with Apist. 26 | * Exception suppression now can be disabled using `$api->setSuppressExceptions(false)`. By default suppression is on. Otherwise all nodes with exceptions will be silently replaced with `null`. 27 | * New filter: ->children() - get all children nodes 28 | * New filters: ->prev(), ->prevAll(), ->prevUntil($selector) - get previous node (nodes) 29 | * New filters: ->next(), ->nextAll(), ->nextUntil($selector) - get next node (nodes) 30 | * New filter: ->is($selector) - check if this node can be retrieved by $selector 31 | * New filter: ->find($selector) - find children by $selector 32 | * New filter: ->filterNodes($selector) - filter selected nodes by $selector (same as jquery "filter" method) 33 | * New filter: ->closest($selector) - find closest parent by $selector 34 | * New filter: ->hasAttr($attribute) - check if attribute exists 35 | 36 | 2014-10-25, v1.2.0 37 | ------------------ 38 | 39 | * Updated filter chain method: each() also can be used without any arguments, return array of nodes 40 | * Filter chain methods can be applied to array: ->each()->text()->mb_strtoupper() will return array of uppercase strings 41 | * Yaml configuration files support added. For details see [documentation](http://sleeping-owl-apist.gopagoda.com/documentation#yaml-configuration) 42 | * Now you can initialize api from yaml file without writing your own classes using Apist::fromYaml($file) method 43 | 44 | 2014-10-24, v1.1.0 45 | ------------------ 46 | 47 | * getBaseUrl() overriding method instead of protected field 48 | * Api class methods now can be used in filter chain methods 49 | * New filter chain method for conditional check by callback: ->check($callback) 50 | * New filter chain method for custom callback call: ->call($callback) 51 | * Updated filter chain methods: each() also accepts callback with ($node, $index) parameters 52 | * New api class method parse($content, $blueprint) to parse content by blueprint without http-requests 53 | * Code cleanup 54 | 55 | 2014-10-22, v1.0.0 56 | ----------------- 57 | 58 | * Initial Version 59 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright 2014 SleepingOwl 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "sleeping-owl/apist", 3 | "description": "Package to provide api-like access to foreign sites based on html parsing", 4 | "homepage": "http://sleeping-owl-apist.gopagoda.com", 5 | "license": "MIT", 6 | "keywords": ["api", "crawler"], 7 | "authors": [ 8 | { 9 | "name": "Sleeping Owl", 10 | "email": "owl.sleeping@yahoo.com" 11 | } 12 | ], 13 | "require": { 14 | "php": ">=5.4.0", 15 | "guzzlehttp/guzzle": "5.*", 16 | "symfony/css-selector": "~2.0", 17 | "symfony/dom-crawler": "~2.0", 18 | "symfony/yaml": "~2.0" 19 | }, 20 | "require-dev": { 21 | "phpunit/phpunit": "4.*", 22 | "mockery/mockery": "0.*" 23 | }, 24 | "autoload": { 25 | "psr-0": { 26 | "SleepingOwl\\Apist": "src/" 27 | } 28 | }, 29 | "minimum-stability": "stable" 30 | } 31 | -------------------------------------------------------------------------------- /phpunit.xml: -------------------------------------------------------------------------------- 1 | 2 | 13 | 14 | 15 | ./tests/ 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | ## SleepingOwl Apist 2 | 3 | [![Build Status](https://travis-ci.org/sleeping-owl/apist.svg?branch=master)](https://travis-ci.org/sleeping-owl/apist) 4 | [![Latest Stable Version](https://poser.pugx.org/sleeping-owl/apist/v/stable.svg)](https://packagist.org/packages/sleeping-owl/apist) 5 | [![Total Downloads](https://poser.pugx.org/sleeping-owl/apist/downloads.svg)](https://packagist.org/packages/sleeping-owl/apist) 6 | [![License](https://poser.pugx.org/sleeping-owl/apist/license.svg)](https://packagist.org/packages/sleeping-owl/apist) 7 | [![Code Climate](https://codeclimate.com/github/sleeping-owl/apist/badges/gpa.svg)](https://codeclimate.com/github/sleeping-owl/apist) 8 | 9 | SleepingOwl Apist is a small library which allows you to access any site in api-like style, based on html parsing. 10 | 11 | ## Overview 12 | 13 | This package allows you to write method like this: 14 | 15 | ```php 16 | class WikiApi extends Apist 17 | { 18 | 19 | public function getBaseUrl() 20 | { 21 | return 'http://en.wikipedia.org'; 22 | } 23 | 24 | public function index() 25 | { 26 | return $this->get('/wiki/Main_Page', [ 27 | 'welcome_message' => Apist::filter('#mp-topbanner div:first')->text()->mb_substr(0, -1), 28 | 'portals' => Apist::filter('a[title^="Portal:"]')->each([ 29 | 'link' => Apist::current()->attr('href')->call(function ($href) 30 | { 31 | return $this->getBaseUrl() . $href; 32 | }), 33 | 'label' => Apist::current()->text() 34 | ]), 35 | 'languages' => Apist::filter('#p-lang li a[title]')->each([ 36 | 'label' => Apist::current()->text(), 37 | 'lang' => Apist::current()->attr('title'), 38 | 'link' => Apist::current()->attr('href')->call(function ($href) 39 | { 40 | return 'http:' . $href; 41 | }) 42 | ]), 43 | 'sister_projects' => Apist::filter('#mp-sister b a')->each()->text(), 44 | 'featured_article' => Apist::filter('#mp-tfa')->html() 45 | ]); 46 | } 47 | } 48 | ``` 49 | 50 | and get the following result: 51 | 52 | ```json 53 | { 54 | "welcome_message": "Welcome to Wikipedia", 55 | "portals": [ 56 | { 57 | "link": "http:\/\/en.wikipedia.org\/wiki\/Portal:Arts", 58 | "label": "Arts" 59 | }, 60 | { 61 | "link": "http:\/\/en.wikipedia.org\/wiki\/Portal:Biography", 62 | "label": "Biography" 63 | }, 64 | ... 65 | ], 66 | "languages": [ 67 | { 68 | "label": "Simple English", 69 | "lang": "Simple English", 70 | "link": "http:\/\/simple.wikipedia.org\/wiki\/" 71 | }, 72 | { 73 | "label": "العربية", 74 | "lang": "Arabic", 75 | "link": "http:\/\/ar.wikipedia.org\/wiki\/" 76 | }, 77 | { 78 | "label": "Bahasa Indonesia", 79 | "lang": "Indonesian", 80 | "link": "http:\/\/id.wikipedia.org\/wiki\/" 81 | }, 82 | ... 83 | ], 84 | "sister_projects": [ 85 | "Commons", 86 | "MediaWiki", 87 | ... 88 | ], 89 | "featured_article": "
...<\/div>" 90 | } 91 | ``` 92 | 93 | ## Installation 94 | 95 | Require this package in your composer.json and run composer update (or run `composer require sleeping-owl/apist:1.x` directly): 96 | 97 | "sleeping-owl/apist": "1.*" 98 | 99 | ## Documentation 100 | 101 | Documentation can be found at [sleeping owl apist](http://sleepingowlapist.cloudcontrolled.com/en/php/documentation). 102 | 103 | ## Examples 104 | 105 | View [examples](http://sleepingowlapist.cloudcontrolled.com/en/php#examples). 106 | 107 | ## Support Library 108 | 109 | You can donate via [PayPal](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=AXJMWMRPCBGVA), Yandex money (410012943296949) or in BTC: 13k36pym383rEmsBSLyWfT3TxCQMN2Lekd 110 | 111 | ## Copyright and License 112 | 113 | Apist was written by Sleeping Owl and is released under the MIT License. See the LICENSE file for details. -------------------------------------------------------------------------------- /src/SleepingOwl/Apist/Apist.php: -------------------------------------------------------------------------------- 1 | getBaseUrl(); 39 | $this->guzzle = new Client($options); 40 | } 41 | 42 | /** 43 | * @return Client 44 | */ 45 | public function getGuzzle() 46 | { 47 | return $this->guzzle; 48 | } 49 | 50 | /** 51 | * @param Client $guzzle 52 | */ 53 | public function setGuzzle($guzzle) 54 | { 55 | $this->guzzle = $guzzle; 56 | } 57 | 58 | /** 59 | * Create filter object 60 | * 61 | * @param $cssSelector 62 | * @return ApistFilter 63 | */ 64 | public static function filter($cssSelector) 65 | { 66 | return new ApistSelector($cssSelector); 67 | } 68 | 69 | /** 70 | * Get current node 71 | * 72 | * @return ApistFilter 73 | */ 74 | public static function current() 75 | { 76 | return static::filter('*'); 77 | } 78 | 79 | /** 80 | * Initialize api from yaml configuration file 81 | * 82 | * @param $file 83 | * @return YamlApist 84 | */ 85 | public static function fromYaml($file) 86 | { 87 | return new YamlApist($file, []); 88 | } 89 | 90 | /** 91 | * @return ApistMethod 92 | */ 93 | public function getCurrentMethod() 94 | { 95 | return $this->currentMethod; 96 | } 97 | 98 | /** 99 | * @return string 100 | */ 101 | public function getBaseUrl() 102 | { 103 | return $this->baseUrl; 104 | } 105 | 106 | /** 107 | * @return ApistMethod 108 | */ 109 | public function getLastMethod() 110 | { 111 | return $this->lastMethod; 112 | } 113 | 114 | /** 115 | * @param string $baseUrl 116 | */ 117 | public function setBaseUrl($baseUrl) 118 | { 119 | $this->baseUrl = $baseUrl; 120 | } 121 | 122 | /** 123 | * @return boolean 124 | */ 125 | public function isSuppressExceptions() 126 | { 127 | return $this->suppressExceptions; 128 | } 129 | 130 | /** 131 | * @param boolean $suppressExceptions 132 | */ 133 | public function setSuppressExceptions($suppressExceptions) 134 | { 135 | $this->suppressExceptions = $suppressExceptions; 136 | } 137 | 138 | /** 139 | * @param $httpMethod 140 | * @param $url 141 | * @param $blueprint 142 | * @param array $options 143 | * @return array 144 | */ 145 | protected function request($httpMethod, $url, $blueprint, $options = []) 146 | { 147 | $this->currentMethod = new ApistMethod($this, $url, $blueprint); 148 | $this->lastMethod = $this->currentMethod; 149 | $this->currentMethod->setMethod($httpMethod); 150 | $result = $this->currentMethod->get($options); 151 | $this->currentMethod = null; 152 | return $result; 153 | } 154 | 155 | /** 156 | * @param $content 157 | * @param $blueprint 158 | * @return array|string 159 | */ 160 | protected function parse($content, $blueprint) 161 | { 162 | $this->currentMethod = new ApistMethod($this, null, $blueprint); 163 | $this->currentMethod->setContent($content); 164 | $result = $this->currentMethod->parseBlueprint($blueprint); 165 | $this->currentMethod = null; 166 | return $result; 167 | } 168 | 169 | /** 170 | * @param $url 171 | * @param $blueprint 172 | * @param array $options 173 | * @return array 174 | */ 175 | protected function get($url, $blueprint = null, $options = []) 176 | { 177 | return $this->request('GET', $url, $blueprint, $options); 178 | } 179 | 180 | /** 181 | * @param $url 182 | * @param $blueprint 183 | * @param array $options 184 | * @return array 185 | */ 186 | protected function head($url, $blueprint = null, $options = []) 187 | { 188 | return $this->request('HEAD', $url, $blueprint, $options); 189 | } 190 | 191 | /** 192 | * @param $url 193 | * @param $blueprint 194 | * @param array $options 195 | * @return array 196 | */ 197 | protected function post($url, $blueprint = null, $options = []) 198 | { 199 | return $this->request('POST', $url, $blueprint, $options); 200 | } 201 | 202 | /** 203 | * @param $url 204 | * @param $blueprint 205 | * @param array $options 206 | * @return array 207 | */ 208 | protected function put($url, $blueprint = null, $options = []) 209 | { 210 | return $this->request('PUT', $url, $blueprint, $options); 211 | } 212 | 213 | /** 214 | * @param $url 215 | * @param $blueprint 216 | * @param array $options 217 | * @return array 218 | */ 219 | protected function patch($url, $blueprint = null, $options = []) 220 | { 221 | return $this->request('PATCH', $url, $blueprint, $options); 222 | } 223 | 224 | /** 225 | * @param $url 226 | * @param $blueprint 227 | * @param array $options 228 | * @return array 229 | */ 230 | protected function delete($url, $blueprint = null, $options = []) 231 | { 232 | return $this->request('DELETE', $url, $blueprint, $options); 233 | } 234 | 235 | } -------------------------------------------------------------------------------- /src/SleepingOwl/Apist/DomCrawler/Crawler.php: -------------------------------------------------------------------------------- 1 | parsePseudoClasses($selector)) 16 | { 17 | return $result; 18 | } 19 | return parent::filter($selector); 20 | } 21 | 22 | protected function parsePseudoClasses($selector) 23 | { 24 | foreach ($this->pseudoClasses as $pseudoClass) 25 | { 26 | if (preg_match('/^(?.*?):' . $pseudoClass . '(\((?[0-9]+)\))?(?.*)$/', $selector, $attrs)) 27 | { 28 | $result = $this->filter($attrs['first']); 29 | $result = call_user_func([ 30 | $result, 31 | $pseudoClass 32 | ], $attrs['param']); 33 | $filter = $attrs['last']; 34 | if (trim($filter) != '') 35 | { 36 | $result = $result->filter($filter); 37 | } 38 | return $result; 39 | } 40 | } 41 | return null; 42 | } 43 | 44 | public function remove() 45 | { 46 | foreach ($this as $node) 47 | { 48 | $node->parentNode->removeChild($node); 49 | } 50 | } 51 | 52 | public function parent() 53 | { 54 | $ar = $this->parents(); 55 | return new static($ar->getNode(0), $this->uri); 56 | } 57 | 58 | } -------------------------------------------------------------------------------- /src/SleepingOwl/Apist/Methods/ApistMethod.php: -------------------------------------------------------------------------------- 1 | resource = $resource; 49 | $this->url = $url; 50 | $this->schemaBlueprint = $schemaBlueprint; 51 | $this->crawler = new Crawler(); 52 | } 53 | 54 | /** 55 | * Perform method action 56 | * 57 | * @param array $arguments 58 | * @return array 59 | */ 60 | public function get($arguments = []) 61 | { 62 | try 63 | { 64 | $this->makeRequest($arguments); 65 | } catch (ConnectException $e) 66 | { 67 | $url = $e->getRequest()->getUrl(); 68 | return $this->errorResponse($e->getCode(), $e->getMessage(), $url); 69 | } catch (RequestException $e) 70 | { 71 | $url = $e->getRequest()->getUrl(); 72 | $status = $e->getCode(); 73 | $response = $e->getResponse(); 74 | $reason = $e->getMessage(); 75 | if ( ! is_null($response)) 76 | { 77 | $reason = $response->getReasonPhrase(); 78 | } 79 | return $this->errorResponse($status, $reason, $url); 80 | } 81 | 82 | return $this->parseBlueprint($this->schemaBlueprint); 83 | } 84 | 85 | /** 86 | * Make http request 87 | * 88 | * @param array $arguments 89 | */ 90 | protected function makeRequest($arguments = []) 91 | { 92 | $defaults = $this->getDefaultOptions(); 93 | $arguments = array_merge($defaults, $arguments); 94 | $client = $this->resource->getGuzzle(); 95 | $request = $client->createRequest($this->getMethod(), $this->url, $arguments); 96 | $response = $client->send($request); 97 | $this->setResponse($response); 98 | $this->setContent((string)$response->getBody()); 99 | } 100 | 101 | /** 102 | * @param $blueprint 103 | * @param null $node 104 | * @return array|string 105 | */ 106 | public function parseBlueprint($blueprint, $node = null) 107 | { 108 | if (is_null($blueprint)) 109 | { 110 | return $this->content; 111 | } 112 | if ( ! is_array($blueprint)) 113 | { 114 | $blueprint = $this->parseBlueprintValue($blueprint, $node); 115 | } else 116 | { 117 | array_walk_recursive($blueprint, function (&$value) use ($node) 118 | { 119 | $value = $this->parseBlueprintValue($value, $node); 120 | }); 121 | } 122 | return $blueprint; 123 | } 124 | 125 | /** 126 | * @param $value 127 | * @param $node 128 | * @return array|string 129 | */ 130 | protected function parseBlueprintValue($value, $node) 131 | { 132 | if ($value instanceof ApistSelector) 133 | { 134 | return $value->getValue($this, $node); 135 | } 136 | return $value; 137 | } 138 | 139 | /** 140 | * Response with error 141 | * 142 | * @param $status 143 | * @param $reason 144 | * @param $url 145 | * @return array 146 | */ 147 | protected function errorResponse($status, $reason, $url) 148 | { 149 | return [ 150 | 'url' => $url, 151 | 'error' => [ 152 | 'status' => $status, 153 | 'reason' => $reason, 154 | ] 155 | ]; 156 | } 157 | 158 | /** 159 | * @return Crawler 160 | */ 161 | public function getCrawler() 162 | { 163 | return $this->crawler; 164 | } 165 | 166 | /** 167 | * @return string 168 | */ 169 | public function getMethod() 170 | { 171 | return $this->method; 172 | } 173 | 174 | /** 175 | * @param string $method 176 | * @return $this 177 | */ 178 | public function setMethod($method) 179 | { 180 | $this->method = $method; 181 | return $this; 182 | } 183 | 184 | /** 185 | * @param string $content 186 | * @return $this 187 | */ 188 | public function setContent($content) 189 | { 190 | $this->content = $content; 191 | $this->crawler->addContent($content); 192 | return $this; 193 | } 194 | 195 | /** 196 | * @return array 197 | */ 198 | protected function getDefaultOptions() 199 | { 200 | return [ 201 | 'cookies' => true 202 | ]; 203 | } 204 | 205 | /** 206 | * @return Apist 207 | */ 208 | public function getResource() 209 | { 210 | return $this->resource; 211 | } 212 | 213 | /** 214 | * @return \GuzzleHttp\Message\Response 215 | */ 216 | public function getResponse() 217 | { 218 | return $this->response; 219 | } 220 | 221 | /** 222 | * @param \GuzzleHttp\Message\Response $response 223 | */ 224 | public function setResponse($response) 225 | { 226 | $this->response = $response; 227 | } 228 | 229 | } -------------------------------------------------------------------------------- /src/SleepingOwl/Apist/Selectors/ApistFilter.php: -------------------------------------------------------------------------------- 1 | node = $node; 34 | $this->method = $method; 35 | $this->resource = $method->getResource(); 36 | } 37 | 38 | /** 39 | * @return ApistFilter 40 | */ 41 | public function text() 42 | { 43 | $this->guardCrawler(); 44 | return $this->node->text(); 45 | } 46 | 47 | /** 48 | * @return ApistFilter 49 | */ 50 | public function html() 51 | { 52 | $this->guardCrawler(); 53 | return $this->node->html(); 54 | } 55 | 56 | /** 57 | * @param $selector 58 | * @return ApistFilter 59 | */ 60 | public function filter($selector) 61 | { 62 | $this->guardCrawler(); 63 | return $this->node->filter($selector); 64 | } 65 | 66 | /** 67 | * @param $selector 68 | * @return ApistFilter 69 | */ 70 | public function filterNodes($selector) 71 | { 72 | $this->guardCrawler(); 73 | $rootNode = $this->method->getCrawler(); 74 | $crawler = new Crawler; 75 | $rootNode->filter($selector)->each(function (Crawler $filteredNode) use ($crawler) 76 | { 77 | $filteredNode = $filteredNode->getNode(0); 78 | foreach ($this->node as $node) 79 | { 80 | if ($filteredNode === $node) 81 | { 82 | $crawler->add($node); 83 | break; 84 | } 85 | } 86 | }); 87 | return $crawler; 88 | } 89 | 90 | /** 91 | * @param $selector 92 | * @return ApistFilter 93 | */ 94 | public function find($selector) 95 | { 96 | $this->guardCrawler(); 97 | return $this->node->filter($selector); 98 | } 99 | 100 | /** 101 | * @return ApistFilter 102 | */ 103 | public function children() 104 | { 105 | $this->guardCrawler(); 106 | return $this->node->children(); 107 | } 108 | 109 | /** 110 | * @return ApistFilter 111 | */ 112 | public function prev() 113 | { 114 | $this->guardCrawler(); 115 | return $this->prevAll()->first(); 116 | } 117 | 118 | /** 119 | * @return ApistFilter 120 | */ 121 | public function prevAll() 122 | { 123 | $this->guardCrawler(); 124 | return $this->node->previousAll(); 125 | } 126 | 127 | /** 128 | * @param $selector 129 | * @return ApistFilter 130 | */ 131 | public function prevUntil($selector) 132 | { 133 | return $this->nodeUntil($selector, 'prev'); 134 | } 135 | 136 | /** 137 | * @return ApistFilter 138 | */ 139 | public function next() 140 | { 141 | $this->guardCrawler(); 142 | return $this->nextAll()->first(); 143 | } 144 | 145 | /** 146 | * @return ApistFilter 147 | */ 148 | public function nextAll() 149 | { 150 | $this->guardCrawler(); 151 | return $this->node->nextAll(); 152 | } 153 | 154 | /** 155 | * @param $selector 156 | * @return ApistFilter 157 | */ 158 | public function nextUntil($selector) 159 | { 160 | return $this->nodeUntil($selector, 'next'); 161 | } 162 | 163 | /** 164 | * @param $selector 165 | * @param $direction 166 | * @return Crawler 167 | */ 168 | public function nodeUntil($selector, $direction) 169 | { 170 | $this->guardCrawler(); 171 | $crawler = new Crawler; 172 | $filter = new static($this->node, $this->method); 173 | while (1) 174 | { 175 | $node = $filter->$direction(); 176 | if (is_null($node)) 177 | { 178 | break; 179 | } 180 | $filter->node = $node; 181 | if ($filter->is($selector)) break; 182 | $crawler->add($node->getNode(0)); 183 | } 184 | return $crawler; 185 | } 186 | 187 | /** 188 | * @return ApistFilter 189 | */ 190 | public function is($selector) 191 | { 192 | $this->guardCrawler(); 193 | return count($this->filterNodes($selector)) > 0; 194 | } 195 | 196 | /** 197 | * @return ApistFilter 198 | */ 199 | public function closest($selector) 200 | { 201 | $this->guardCrawler(); 202 | $this->node = $this->node->parents(); 203 | return $this->filterNodes($selector)->last(); 204 | } 205 | 206 | /** 207 | * @param $attribute 208 | * @return ApistFilter 209 | */ 210 | public function attr($attribute) 211 | { 212 | $this->guardCrawler(); 213 | return $this->node->attr($attribute); 214 | } 215 | 216 | /** 217 | * @param $attribute 218 | * @return ApistFilter 219 | */ 220 | public function hasAttr($attribute) 221 | { 222 | $this->guardCrawler(); 223 | return ! is_null($this->node->attr($attribute)); 224 | } 225 | 226 | /** 227 | * @param $position 228 | * @return ApistFilter 229 | */ 230 | public function eq($position) 231 | { 232 | $this->guardCrawler(); 233 | return $this->node->eq($position); 234 | } 235 | 236 | /** 237 | * @return ApistFilter 238 | */ 239 | public function first() 240 | { 241 | $this->guardCrawler(); 242 | return $this->node->first(); 243 | } 244 | 245 | /** 246 | * @return ApistFilter 247 | */ 248 | public function last() 249 | { 250 | $this->guardCrawler(); 251 | return $this->node->last(); 252 | } 253 | 254 | /** 255 | * @return ApistFilter 256 | */ 257 | public function element() 258 | { 259 | return $this->node; 260 | } 261 | 262 | /** 263 | * @param $callback 264 | * @return ApistFilter 265 | */ 266 | public function call($callback) 267 | { 268 | return $callback($this->node); 269 | } 270 | 271 | /** 272 | * @return ApistFilter 273 | */ 274 | public function trim($mask = " \t\n\r\0\x0B") 275 | { 276 | $this->guardText(); 277 | return trim($this->node, $mask); 278 | } 279 | 280 | /** 281 | * @return ApistFilter 282 | */ 283 | public function ltrim($mask = " \t\n\r\0\x0B") 284 | { 285 | $this->guardText(); 286 | return ltrim($this->node, $mask); 287 | } 288 | 289 | /** 290 | * @return ApistFilter 291 | */ 292 | public function rtrim($mask = " \t\n\r\0\x0B") 293 | { 294 | $this->guardText(); 295 | return rtrim($this->node, $mask); 296 | } 297 | 298 | /** 299 | * @return ApistFilter 300 | */ 301 | public function str_replace($search, $replace, $count = null) 302 | { 303 | $this->guardText(); 304 | return str_replace($search, $replace, $this->node, $count); 305 | } 306 | 307 | /** 308 | * @return ApistFilter 309 | */ 310 | public function intval() 311 | { 312 | $this->guardText(); 313 | return intval($this->node); 314 | } 315 | 316 | /** 317 | * @return ApistFilter 318 | */ 319 | public function floatval() 320 | { 321 | $this->guardText(); 322 | return floatval($this->node); 323 | } 324 | 325 | /** 326 | * @return ApistFilter 327 | */ 328 | public function exists() 329 | { 330 | return count($this->node) > 0; 331 | } 332 | 333 | /** 334 | * @param $callback 335 | * @return ApistFilter 336 | */ 337 | public function check($callback) 338 | { 339 | return $this->call($callback); 340 | } 341 | 342 | /** 343 | * @param $blueprint 344 | * @return ApistFilter 345 | */ 346 | public function then($blueprint) 347 | { 348 | if ($this->node === true) 349 | { 350 | return $this->method->parseBlueprint($blueprint); 351 | } 352 | return $this->node; 353 | } 354 | 355 | /** 356 | * @param $blueprint 357 | * @return ApistFilter 358 | */ 359 | public function each($blueprint = null) 360 | { 361 | $callback = $blueprint; 362 | if (is_null($callback)) 363 | { 364 | $callback = function ($node) 365 | { 366 | return $node; 367 | }; 368 | } 369 | if ( ! is_callable($callback)) 370 | { 371 | $callback = function ($node) use ($blueprint) 372 | { 373 | return $this->method->parseBlueprint($blueprint, $node); 374 | }; 375 | } 376 | return $this->node->each($callback); 377 | } 378 | 379 | /** 380 | * Guard string method to be called with Crawler object 381 | */ 382 | protected function guardText() 383 | { 384 | if (is_object($this->node)) 385 | { 386 | $this->node = $this->node->text(); 387 | } 388 | } 389 | 390 | /** 391 | * Guard method to be called with Crawler object 392 | */ 393 | protected function guardCrawler() 394 | { 395 | if ( ! $this->node instanceof Crawler) 396 | { 397 | throw new \InvalidArgumentException('Current node isnt instance of Crawler.'); 398 | } 399 | } 400 | 401 | } 402 | -------------------------------------------------------------------------------- /src/SleepingOwl/Apist/Selectors/ApistSelector.php: -------------------------------------------------------------------------------- 1 | selector = $selector; 24 | } 25 | 26 | /** 27 | * Get value from content by css selector 28 | * 29 | * @param ApistMethod $method 30 | * @param Crawler $rootNode 31 | * @return array|null|string|Crawler 32 | */ 33 | public function getValue(ApistMethod $method, Crawler $rootNode = null) 34 | { 35 | if (is_null($rootNode)) 36 | { 37 | $rootNode = $method->getCrawler(); 38 | } 39 | $result = $rootNode->filter($this->selector); 40 | return $this->applyResultCallbackChain($result, $method); 41 | } 42 | 43 | /** 44 | * Save callable method as result callback to perform it after getValue method 45 | * 46 | * @param $name 47 | * @param $arguments 48 | * @return $this 49 | */ 50 | function __call($name, $arguments) 51 | { 52 | return $this->addCallback($name, $arguments); 53 | } 54 | 55 | /** 56 | * Apply all result callbacks 57 | * 58 | * @param Crawler $node 59 | * @param ApistMethod $method 60 | * @return array|string|Crawler 61 | */ 62 | protected function applyResultCallbackChain(Crawler $node, ApistMethod $method) 63 | { 64 | if (empty($this->resultMethodChain)) 65 | { 66 | $this->addCallback('text'); 67 | } 68 | /** @var ResultCallback[] $traceStack */ 69 | $traceStack = []; 70 | foreach ($this->resultMethodChain as $resultCallback) 71 | { 72 | try 73 | { 74 | $traceStack[] = $resultCallback; 75 | $node = $resultCallback->apply($node, $method); 76 | } catch (InvalidArgumentException $e) 77 | { 78 | if ($method->getResource()->isSuppressExceptions()) 79 | { 80 | return null; 81 | } 82 | $message = $this->createExceptionMessage($e, $traceStack); 83 | throw new InvalidArgumentException($message, 0, $e); 84 | } 85 | } 86 | return $node; 87 | } 88 | 89 | /** 90 | * @param $name 91 | * @param $arguments 92 | * @return $this 93 | */ 94 | public function addCallback($name, $arguments = []) 95 | { 96 | $resultCallback = new ResultCallback($name, $arguments); 97 | $this->resultMethodChain[] = $resultCallback; 98 | return $this; 99 | } 100 | 101 | /** 102 | * @param $e 103 | * @param ResultCallback[] $traceStack 104 | * @return string 105 | */ 106 | protected function createExceptionMessage(\Exception $e, $traceStack) 107 | { 108 | $message = "[ filter({$this->selector})"; 109 | foreach ($traceStack as $callback) 110 | { 111 | $message .= '->' . $callback->getMethodName() . '('; 112 | try 113 | { 114 | $message .= implode(', ', $callback->getArguments()); 115 | } catch (\Exception $_e) 116 | { 117 | } 118 | $message .= ')'; 119 | } 120 | $message .= ' ] ' . $e->getMessage(); 121 | return $message; 122 | } 123 | 124 | } -------------------------------------------------------------------------------- /src/SleepingOwl/Apist/Selectors/ResultCallback.php: -------------------------------------------------------------------------------- 1 | methodName = $methodName; 24 | $this->arguments = $arguments; 25 | } 26 | 27 | /** 28 | * Apply result callback to the $node, provided by $method 29 | * 30 | * @param Crawler $node 31 | * @param ApistMethod $method 32 | * @return array|string 33 | */ 34 | public function apply($node, ApistMethod $method) 35 | { 36 | if (is_array($node)) 37 | { 38 | return $this->applyToArray($node, $method); 39 | } 40 | if ($this->methodName === 'else') 41 | { 42 | if (is_bool($node)) $node = ! $node; 43 | $this->methodName = 'then'; 44 | } 45 | 46 | $filter = new ApistFilter($node, $method); 47 | if (method_exists($filter, $this->methodName)) 48 | { 49 | return call_user_func_array([ 50 | $filter, 51 | $this->methodName 52 | ], $this->arguments); 53 | } 54 | 55 | if ($this->isResourceMethod($method)) 56 | { 57 | return $this->callResourceMethod($method, $node); 58 | } 59 | if ($this->isNodeMethod($node)) 60 | { 61 | return $this->callNodeMethod($node); 62 | } 63 | if ($this->isGlobalFunction()) 64 | { 65 | return $this->callGlobalFunction($node); 66 | } 67 | throw new \InvalidArgumentException("Method '{$this->methodName}' was not found"); 68 | } 69 | 70 | protected function applyToArray($array, ApistMethod $method) 71 | { 72 | $result = []; 73 | foreach ($array as $node) 74 | { 75 | $result[] = $this->apply($node, $method); 76 | } 77 | return $result; 78 | } 79 | 80 | /** 81 | * @param ApistMethod $method 82 | * @return bool 83 | */ 84 | protected function isResourceMethod(ApistMethod $method) 85 | { 86 | return method_exists($method->getResource(), $this->methodName); 87 | } 88 | 89 | /** 90 | * @param ApistMethod $method 91 | * @param $node 92 | * @return mixed 93 | */ 94 | protected function callResourceMethod(ApistMethod $method, $node) 95 | { 96 | $arguments = $this->arguments; 97 | array_unshift($arguments, $node); 98 | return call_user_func_array([ 99 | $method->getResource(), 100 | $this->methodName 101 | ], $arguments); 102 | } 103 | 104 | /** 105 | * @param $node 106 | * @return bool 107 | */ 108 | protected function isNodeMethod($node) 109 | { 110 | return method_exists($node, $this->methodName); 111 | } 112 | 113 | /** 114 | * @param $node 115 | * @return mixed 116 | */ 117 | protected function callNodeMethod($node) 118 | { 119 | return call_user_func_array([ 120 | $node, 121 | $this->methodName 122 | ], $this->arguments); 123 | } 124 | 125 | /** 126 | * @return bool 127 | */ 128 | protected function isGlobalFunction() 129 | { 130 | return function_exists($this->methodName); 131 | } 132 | 133 | /** 134 | * @param $node 135 | * @return mixed 136 | */ 137 | protected function callGlobalFunction($node) 138 | { 139 | if (is_object($node)) 140 | { 141 | $node = $node->text(); 142 | } 143 | $arguments = $this->arguments; 144 | array_unshift($arguments, $node); 145 | return call_user_func_array($this->methodName, $arguments); 146 | } 147 | 148 | /** 149 | * @return string 150 | */ 151 | public function getMethodName() 152 | { 153 | return $this->methodName; 154 | } 155 | 156 | /** 157 | * @return array 158 | */ 159 | public function getArguments() 160 | { 161 | return $this->arguments; 162 | } 163 | 164 | } -------------------------------------------------------------------------------- /src/SleepingOwl/Apist/Yaml/Parser.php: -------------------------------------------------------------------------------- 1 | file = $file; 28 | } 29 | 30 | /** 31 | * @param Apist $resource 32 | */ 33 | public function load(Apist $resource) 34 | { 35 | $data = Yaml::parse($this->file); 36 | if (isset($data['baseUrl'])) 37 | { 38 | $resource->setBaseUrl($data['baseUrl']); 39 | unset($data['baseUrl']); 40 | } 41 | foreach ($data as $method => $methodConfig) 42 | { 43 | if ($method[0] === '_') 44 | { 45 | # structure 46 | $this->structures[$method] = $methodConfig; 47 | } else 48 | { 49 | # method 50 | if ( ! isset($methodConfig['blueprint'])) 51 | { 52 | $methodConfig['blueprint'] = null; 53 | } 54 | $methodConfig['blueprint'] = $this->parseBlueprint($methodConfig['blueprint']); 55 | $this->methods[$method] = $methodConfig; 56 | } 57 | } 58 | } 59 | 60 | /** 61 | * @param $blueprint 62 | * @return array 63 | */ 64 | protected function parseBlueprint($blueprint) 65 | { 66 | $callback = function (&$value) 67 | { 68 | if (is_string($value)) 69 | { 70 | $value = str_replace(':current', '*', $value); 71 | } 72 | if ($value[0] === ':') 73 | { 74 | # structure 75 | $structure = $this->getStructure($value); 76 | $value = $this->parseBlueprint($structure); 77 | return; 78 | } 79 | if (strpos($value, '|') === false) return; 80 | 81 | $parts = preg_split('/\s?\|\s?/', $value); 82 | $selector = array_shift($parts); 83 | $value = Apist::filter($selector); 84 | foreach ($parts as $part) 85 | { 86 | $this->addCallbackToFilter($value, $part); 87 | } 88 | }; 89 | if ( ! is_array($blueprint)) 90 | { 91 | $callback($blueprint); 92 | } else 93 | { 94 | array_walk_recursive($blueprint, $callback); 95 | } 96 | return $blueprint; 97 | } 98 | 99 | /** 100 | * @param ApistSelector $filter 101 | * @param $callback 102 | */ 103 | protected function addCallbackToFilter(ApistSelector $filter, $callback) 104 | { 105 | $method = strtok($callback, '(),'); 106 | $arguments = []; 107 | while (($argument = strtok('(),')) !== false) 108 | { 109 | $argument = trim($argument); 110 | if (preg_match('/^[\'"].*[\'"]$/', $argument)) 111 | { 112 | $argument = substr($argument, 1, -1); 113 | } 114 | if ($argument[0] === ':') 115 | { 116 | # structure 117 | $structure = $this->getStructure($argument); 118 | $argument = $this->parseBlueprint($structure); 119 | } 120 | $arguments[] = $argument; 121 | } 122 | $filter->addCallback($method, $arguments); 123 | } 124 | 125 | /** 126 | * @param $name 127 | * @return mixed 128 | */ 129 | protected function getStructure($name) 130 | { 131 | $structure = '_' . substr($name, 1); 132 | if ( ! isset($this->structures[$structure])) 133 | { 134 | throw new \InvalidArgumentException("Structure '$structure' not found.'"); 135 | } 136 | return $this->structures[$structure]; 137 | } 138 | 139 | /** 140 | * @param $name 141 | * @return array 142 | */ 143 | public function getMethod($name) 144 | { 145 | if ( ! isset($this->methods[$name])) 146 | { 147 | throw new \InvalidArgumentException("Method '$name' not found.'"); 148 | } 149 | $methodConfig = $this->methods[$name]; 150 | return $methodConfig; 151 | } 152 | 153 | /** 154 | * @param $method 155 | * @param $arguments 156 | * @return mixed 157 | */ 158 | public function insertMethodArguments($method, $arguments) 159 | { 160 | array_walk_recursive($method, function (&$value) use ($arguments) 161 | { 162 | if ( ! is_string($value)) return; 163 | $value = preg_replace_callback('/\$(?[0-9]+)/', function ($finded) use ($arguments) 164 | { 165 | $argumentPosition = intval($finded['num']) - 1; 166 | return isset($arguments[$argumentPosition]) ? $arguments[$argumentPosition] : null; 167 | }, $value); 168 | }); 169 | return $method; 170 | } 171 | } -------------------------------------------------------------------------------- /src/SleepingOwl/Apist/Yaml/YamlApist.php: -------------------------------------------------------------------------------- 1 | loadFromYml($file); 18 | } 19 | parent::__construct($options); 20 | } 21 | 22 | /** 23 | * Load method data from yaml file 24 | * @param $file 25 | */ 26 | protected function loadFromYml($file) 27 | { 28 | $this->parser = new Parser($file); 29 | $this->parser->load($this); 30 | } 31 | 32 | /** 33 | * @param $name 34 | * @param $arguments 35 | * @return array 36 | */ 37 | function __call($name, $arguments) 38 | { 39 | if (is_null($this->parser)) 40 | { 41 | throw new \InvalidArgumentException("Method '$name' not found.'"); 42 | } 43 | $method = $this->parser->getMethod($name); 44 | $method = $this->parser->insertMethodArguments($method, $arguments); 45 | $httpMethod = isset($method['method']) ? strtoupper($method['method']) : 'GET'; 46 | $options = isset($method['options']) ? $method['options'] : []; 47 | return $this->request($httpMethod, $method['url'], $method['blueprint'], $options); 48 | } 49 | 50 | } -------------------------------------------------------------------------------- /tests/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sleeping-owl/apist/b65658d0e5a024ca41348e9d273883fc567e6a8b/tests/.gitkeep -------------------------------------------------------------------------------- /tests/ApistMethodTest.php: -------------------------------------------------------------------------------- 1 | resource = new TestApi; 16 | 17 | $response = Mockery::mock(); 18 | $response->shouldReceive('getBody')->andReturn(file_get_contents(__DIR__ . '/stub/index.html')); 19 | 20 | $client = Mockery::mock(); 21 | $client->shouldReceive('createRequest')->andReturnUsing(function ($method, $url) 22 | { 23 | return $url; 24 | }); 25 | $client->shouldReceive('send')->andReturn($response); 26 | 27 | $this->resource->setGuzzle($client); 28 | } 29 | 30 | /** @test */ 31 | public function it_parses_result_by_blueprint() 32 | { 33 | $result = $this->resource->index(); 34 | 35 | $this->assertEquals('Моя лента', $result['title']); 36 | $this->assertEquals('http://tmtm.ru/', $result['copyright']); 37 | $this->assertCount(10, $result['posts']); 38 | } 39 | 40 | /** @test */ 41 | public function it_returns_null_if_element_not_found() 42 | { 43 | $result = $this->resource->element_not_found(); 44 | 45 | $this->assertEquals(['title' => null], $result); 46 | } 47 | 48 | /** @test */ 49 | public function it_parses_non_array_blueprint() 50 | { 51 | $result = $this->resource->non_array_blueprint(); 52 | 53 | $this->assertEquals('Моя лента', $result); 54 | } 55 | 56 | } 57 | -------------------------------------------------------------------------------- /tests/ApistTest.php: -------------------------------------------------------------------------------- 1 | resource = new TestApi; 16 | } 17 | 18 | /** @test */ 19 | public function it_registers_new_resource() 20 | { 21 | $this->assertInstanceOf('\SleepingOwl\Apist\Apist', $this->resource); 22 | } 23 | 24 | } 25 | -------------------------------------------------------------------------------- /tests/TestApi.php: -------------------------------------------------------------------------------- 1 | get('/', [ 11 | 'title' => Apist::filter('.page_head .title'), 12 | 'copyright' => Apist::filter('.copyright .about a')->first()->attr('href'), 13 | 'posts' => Apist::filter('.posts .post')->each(function () 14 | { 15 | return [ 16 | 'title' => Apist::filter('h1.title a')->text() 17 | ]; 18 | }), 19 | ]); 20 | } 21 | 22 | public function element_not_found() 23 | { 24 | return $this->get('/', [ 25 | 'title' => Apist::filter('.page_header') 26 | ]); 27 | } 28 | 29 | public function non_array_blueprint() 30 | { 31 | return $this->get('/', Apist::filter('.page_head .title')); 32 | } 33 | 34 | } -------------------------------------------------------------------------------- /tests/stub/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Интересные публикации / Лента / Хабрахабр 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 64 | 68 | 69 | 70 | 72 | 73 | 74 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 |
109 |
110 | 119 |
120 |
121 | 122 | 123 |
124 | 133 | 144 | 145 | 146 | 147 | 283 | 284 |
285 | 286 | 287 |
288 |

Моя лента

289 | 290 |
291 | настроить 292 | +17 новых хабов 293 |
294 |
295 | 296 |
297 | 298 | 299 | 300 | 301 | 304 | 308 | 309 | 310 | 312 |
313 | 314 | 315 |
316 | 317 |
318 |
319 |
320 | закрыть 321 | sleeping-owl, вы подписаны на 10 хабов из 270. Если настроить 323 | ленту, можно читать еще больше постов. 324 |
325 |
326 | 327 | 328 |
329 |
сегодня в 08:20
330 |

331 | 332 | Проверьте своего хостера на 333 | уязвимость Shellshock (часть 2) 334 | 335 | 336 |

337 | 338 | 346 | 347 | 348 |
349 | Недавно ХостТрекер представил функцию проверки уязвимости Shellshock с 350 | помощью куки, что было описано в соответствующей 351 | публикации. Следуя пожеланиям наших клиентов, а также комментариям к предыдущей статье, мы несколько 352 | расширили функционал проверки — теперь можно тестить Shellshock с помощью любого поля в http запросе, а не 353 | только куки.
354 |
355 |
356 |
357 | 358 | 362 |
363 |
364 | 365 |
366 |
367 | 368 |
369 | 370 | 371 | 372 |
373 | 375 |
376 | 377 | 379 | 380 |
381 | 382 |
1688
383 | 384 |
385 | 387 |
388 |
18
389 | 390 | 391 |
392 | smiHT 393 | 26,9 394 |
395 | 396 | 397 |
398 | 4 400 | +4 401 | 402 |
403 | 404 | 405 |
406 |
407 |
408 | 409 | 410 |
411 | 412 | 413 |
414 |
вчера в 22:36
415 |

416 | 417 | Курсы этичного хакинга и 418 | тестирования на проникновение от PentestIT 419 | tutorial 420 | 421 |

422 | 423 | 431 | 432 | 433 |
434 | Постоянно обновляя программы обучения в области информационной безопасности («Zero Security: A» и «Корпоративные лаборатории»), мы отказываемся 437 | от устаревшего материала, позволяя всем, кто проходит обучение в PentestIT, обладать самыми актуальными знаниями 438 | и навыками. По мере устаревания материала мы будем публиковать его в специализированном плейлисте для всех 440 | желающих.
441 |
442 | Основы безопасности сетевой инфраструктуры. Часть 1.
443 | 445 |
446 |
447 | Основы безопасности сетевой инфраструктуры. Часть 2.
448 | 450 |
451 |
452 | Разведка и сбор информации. Часть 1.
453 | 455 |
456 | 457 | 461 |
462 |
463 | 464 |
465 |
466 | 467 |
468 | 469 | 470 | 471 |
472 | 474 |
475 | 476 | 478 | 479 |
480 | 481 |
8317
482 | 483 |
484 | 486 |
487 |
558
488 | 489 | 490 |
491 | pentestit-team 492 | 36,4 493 |
494 | 495 | 496 |
497 | 9 +9 499 | 500 |
501 | 502 | 503 |
504 |
505 |
506 | 507 | 508 |
509 | 510 | 511 |
512 |
вчера в 22:51
513 |

514 | 515 | 516 | Комнатная метеостанция на Arduino 517 | 518 | из песочницы 519 | 520 | 521 |

522 | 523 | 531 | 532 | 533 |
534 | Однажды, исследуя просторы интернета наткнулся я на интересную плату Arduino. Меня очень заинтересовала эта 535 | плата. С ее помощью можно сделать самому робота, метеостанцию, сигнализацию и даже что-то посерьезней, например 536 | — «Умный Дом».
537 |
538 | Прикупив сей девайс, начал изучать его особенности. Наигравшись со светодиодами, датчиком температуры и LCD 539 | дисплеем, решил сделать что-то такое интересное и то что может пригодиться мне дома.
540 | И вот что получилось из этого…
541 |
542 | Сегодня я хочу рассказать про свой небольшой домашний проект, а именно — о комнатной метеостанции на Arduino. 543 | Думаю, каждый бы хотел увидеть, например, какая у него температура в комнате или влажность, так вот, мой проект 544 | позволит вам сделать это.
545 |
546 | Вот так метеостанция будет выглядеть в сборе:
547 |
548 | image
550 |
551 | Наверно, вам захотелось собрать такое же устройство, ну что же, не будем тянуть.
552 | 553 | 556 |
557 |
558 | 559 | 560 |
561 |
562 | 563 |
564 | 565 | 566 | 567 |
568 | 570 |
571 | 572 | 574 | 575 |
576 | 577 |
10329
578 | 579 |
580 | 582 |
583 |
201
584 | 585 | 586 |
587 | bvlad 588 | 18,3 589 |
590 | 591 | 592 |
593 | 65 +65 595 | 596 |
597 | 598 | 599 |
600 |
601 |
602 | 603 | 604 |
605 | 606 | 607 |
608 |
вчера в 22:08
609 |

610 | 611 | FTDI наносит ответный удар 612 | 613 | 614 |

615 | 616 | 624 | 625 | 626 |
627 | Иногда борьба корпораций с конечными пользователями приобретает гротескную форму, что и побудило меня написать 628 | этот пост. Коротко: я перестал беспокоиться любить FTDI.
629 |
630 | Если вы используете устройство с подключением через конвертер USB2COM на чипе FTDI под Windows — 631 | берегитесь. В рамках борьбы с пользователями за интеллектуальную собственность FTDI ломает 632 | контрафактные чипы программными средствами.
633 |
634 | 635 | 639 |
640 |
641 | 642 |
643 |
644 | 645 |
646 | 647 | 648 | 649 |
650 | 652 |
653 | 654 | 656 | 657 |
658 | 659 |
20443
660 | 661 |
662 | 664 |
665 |
101
666 | 667 | 668 |
669 | UnknownType 670 | 65,9 671 |
672 | 673 | 674 |
675 | 49 +49 677 | 678 |
679 | 680 | 681 |
682 |
683 |
684 | 685 | 686 |
687 | 688 | 689 |
690 |
вчера в 19:44
691 |

692 | 693 | Китай осуществляет MiTM-атаку на пользователей 694 | iCloud 695 | 696 | 697 |

698 | 699 | 703 | 704 | 705 |
706 | Так называемый «великий китайский файрвол» освоил работу с iCloud и теперь перехватывает трафик от китайских 707 | пользователей Apple к серверам iCloud.
708 |
709 | Исследователи из организации Greatfire.org выложили 711 | доказательства MiTM-атаки, при которой власти получают конфиденциальную информацию пользователей: сообщения 712 | iMessage, контакты, фотографии и проч.
713 |
714 | Атака осуществляется с помощью поддельного цифрового сертификата: если пользователь невнимателен и проигнорирует 715 | предупреждение, то его соединение с iCloud будет шифроваться ключами китайского правительства.
716 |
717 | Предупреждение о неправильном сертификате при попытке подключиться к https://www.icloud.com
718 |
719 | 720 | 723 |
724 |
725 | 726 |
727 |
728 | 729 |
730 | 731 | 732 | 733 |
734 | 736 |
737 | 738 | 740 | 741 |
742 | 743 |
18552
744 | 745 |
746 | 748 |
749 |
34
750 | 751 | 752 |
753 | alizar 754 | 1469,3 755 |
756 | 757 | 758 |
759 | 41 +41 761 | 762 |
763 | 764 | 765 |
766 |
767 |
768 | 769 | 770 |
771 | 772 | 773 |
774 |
вчера в 17:52
775 |

776 | 777 | 778 | Вы тоже можете стать жертвой паралича 779 | разработчика 780 | 781 | перевод 782 | 783 | 784 |

785 | 786 | 790 | 791 | 792 |
793 | Дорогие разработчики, вы чувствуете себя неуютно, потому что вы быстро пишете только на восьми языках для трех 794 | семейств устройств? Вздрагиваете ли вы, едва заслышав о переходе на очередной фреймворк? Откладывали ли вы 795 | любимый проект из-за того, что не могли определить, какая облачная платформа подойдет лучше всего?
796 | Возможно, у вас тоже паралич разработчика. Бойтесь! Это не лечится.
797 |
798 | Богатство доступных программистам опций сегодня просто чудовищно. Мы задохнулись под тем, что извергает рог 799 | изобилия. Последние несколько лет я зарабатывал на том, что писал на Java, Objective-C, C, C++, Python, Ruby, 800 | JavaScript, PHP (прошу прощения) с разными вариантами SQL/баз данных (MySQL, PostgreSQL, MongoDB, BigTable, 801 | Redis, Memcached и другими). Нравится ли это мне? Черт побери, нет. По большей части я просто чувствую вину за 802 | то, что не делал ничего на Erlang, Clojure, Rust, Go, C#, Scala, Haskell, Julia, Scheme, Swift или OCaml.
803 | image
805 |
806 | Я — жертва паралича разработчика: чувства ущербности из-за того, что индустрия софта развивается быстрее, чем 807 | может успеть один человек.
808 | 809 | 812 |
813 |
814 | 815 | 816 |
817 |
818 | 819 |
820 | 821 | 822 | 823 |
824 | 826 |
827 | 828 | 830 | 831 |
832 | 833 |
45682
834 | 835 |
836 | 838 |
839 |
211
840 | 841 | 842 | 847 | 848 | 849 |
850 | PerseptronYar 851 | 96,4 852 |
853 | 854 | 855 |
856 | 116 +116 858 | 859 |
860 | 861 | 862 |
863 |
864 |
865 | 866 | 867 |
868 | 869 | 870 |
871 |
вчера в 14:25
872 |

873 | 874 | 875 | Обновление tzdata для России (системное и java в 876 | Ubuntu/Debian, а также в MySQL) 877 | 878 | из песочницы 879 | 880 | 881 |

882 | 883 | 891 | 892 | 893 |
894 |

Предисловие


895 | Как многие помнят, в этом году был принят закон, в связи с которым поменялись часовые пояса в России с 26 896 | октября 2014 г. Само собой, сразу после принятия закона я поставил в календарь напоминалку на начало октября 897 | «обязательно обновить tzdata». Каково же было моё удивление, когда я не обнаружил апдейтов tzdata в debian и 898 | ubuntu. Решил подождать еще немного, наткнулся на открытые баги в дистрибутивах (Ubuntu #1377813, #1371758, Debian #761267). Коллеги усиленно напоминали о 902 | необходимости апдейтов, но мейнтейнеры не реагировали. Чтобы не устроить себе аврал к концу месяца, решил 903 | собрать пакеты и проапдейтить вручную. Важно отметить, что информацию о зонах в некотором софте, например, в 904 | MySQL, потребуется обновить вручную. Далее последует короткий мануал.
905 | 906 | 909 |
910 |
911 | 912 | 913 |
914 |
915 | 916 |
917 | 918 | 919 | 920 |
921 | 923 |
924 | 925 | 927 | 928 |
929 | 930 |
6646
931 | 932 |
933 | 935 |
936 |
82
937 | 938 | 939 |
940 | motienko 941 | 17,2 942 |
943 | 944 | 945 |
946 | 21 +21 948 | 949 |
950 | 951 | 952 |
953 |
954 |
955 | 956 | 957 |
958 | 959 | 960 |
961 |
вчера в 14:31
962 |

963 | 964 | Facebook запустил систему сбора и проверки данных 965 | скомпрометированных аккаунтов 966 | 967 | 968 |

969 | 970 | 974 | 975 | 976 |
977 |
979 |
980 | Социальная сеть Facebook, после ряда крупных утечек данных пользовательских аккаунтов Gmail, Yandex и других 981 | ресурсов, решила запустить новую систему сбора и проверки подобных данных. Принцип работы системы простой — в 982 | Сети ищутся пары логин/пароль от аккаунтов, выложенные в открытый доступ (например, на Pastebin). Затем 983 | проверяется корректность данных, и если пара подходит, владелец скомпрометрированного аккаунта получает 984 | предупреждение о необходимости смены пароля.
985 |
986 | При этом проверяются любые обнаруженные пары логин/пароль, вне зависимости от «принадлежности» данных 987 | какому-либо сервису. Другими словами, если злоумышленник выложил такие данные для Gmail, Yahoo, Outlook Mail — 988 | Facebook будет проверять, подходит ли логин и пароль к какому-либо аккаунту в Facebook. Авторы системы объясняют 989 | это тем, что пользователи очень часто используют одинаковые логины и пароли для многих сервисов, поэтому 990 | проверять нужно все.
991 |
992 | 993 | 996 |
997 |
998 | 999 |
1000 |
1001 | 1002 |
1003 | 1004 | 1005 | 1006 |
1007 | 1009 |
1010 | 1011 | 1013 | 1014 |
1015 | 1016 |
5281
1017 | 1018 |
1019 | 1021 |
1022 |
15
1023 | 1024 | 1025 |
1026 | marks 1027 | 407,7 1028 |
1029 | 1030 | 1031 |
1032 | 7 +7 1034 | 1035 |
1036 | 1037 | 1038 |
1039 |
1040 |
1041 | 1042 | 1043 |
1044 | 1045 | 1046 |
1047 |
вчера в 14:59
1048 |

1049 | 1050 | Особенности отражения DDoS атак и 1051 | история атаки на один крупный банк 1052 | 1053 | 1054 |

1055 | 1056 | 1064 | 1065 | 1066 |
1067 |
1068 |
1069 | Раньше DDoS-атаки могли успешно отбиваться на стороне ЦОДа атакуемой компании. Быстрые умные действия админа и 1070 | хорошее фильтрующее железо гарантировали достаточную защиту от атак. Сегодня услуги ботнетов стремительно 1071 | дешевеют, и DDoS становится доступен чуть ли не в малом бизнесе.
1072 |
1073 | Около половины атак идут на интернет-магазины или коммерческие сайты компаний в духе «завалить конкурента», 1074 | почти всегда атакуют сайты СМИ, особенно после «горячих» публикаций, намного чаще, чем кажется, бьют по 1075 | госсервисам. В России главные цели – банки, розница, СМИ и тендерные площадки. Один из крупных российских 1076 | банков, например, периодически блокирует трафик из Китая – атаки оттуда приходят с завидной регулярностью, одна 1077 | из последних была больше 100 Гб/с.
1078 |
1079 | Соответственно, когда атака переваливает, скажем, за 10 Гб/с, отражать её на своей стороне становится 1080 | проблематично из-за банального забивания канала. Именно в этот момент нужно делать переключение на центр очистки 1081 | данных, чтобы весь «плохой» трафик отсеивался ещё где-то около магистральных каналов, а не шёл к вам. Сейчас 1082 | расскажу, как это работает у одного из наших вендоров защитных средств – Arbor, мониторящего около 90 Тбит/сек 1083 | (45% мирового трафика Интернета). 1084 | 1088 |
1089 |
1090 | 1091 |
1092 |
1093 | 1094 |
1095 | 1096 | 1097 | 1098 |
1099 | 1101 |
1102 | 1103 | 1105 | 1106 |
1107 | 1108 |
25978
1109 | 1110 |
1111 | 1113 |
1114 |
174
1115 | 1116 | 1117 |
1118 | AVrublev 1119 | 32,0 1120 |
1121 | 1122 | 1123 |
1124 | 12 +12 1126 | 1127 |
1128 | 1129 | 1130 |
1131 |
1132 |
1133 | 1134 | 1135 |
1136 | 1137 | 1138 |
1139 |
вчера в 12:20
1140 |

1141 | 1142 | 1143 | Готовим Debian к переводу часов 26 октября 2014 1144 | года 1145 | 1146 | из песочницы 1147 | 1148 | 1149 |

1150 | 1151 | 1157 | 1158 | 1159 |
1160 | Приближается 26 октября 2014 года — день, когда в 2 часа ночи в большинстве регионов России в очередной (и как 1161 | снова обещано в последний) раз часы буду переведены на час назад. Кроме того, в некоторых регионах происходит 1162 | смена часового пояса. Ознакомиться подробно где и что меняется можно в Федеральном законе от 21.07.2014 № 248-ФЗ «О внесении 1164 | изменений в Федеральный закон „Об исчислении времени“.
1165 |
1166 | В этом посте я хочу акцентироваться на вопросе приведения в актуальное состояние данных о часовых поясах в 1167 | Debian.
1168 | 1169 | 1172 |
1173 |
1174 | 1175 | 1176 |
1177 |
1178 | 1179 |
1180 | 1181 | 1182 | 1183 |
1184 | 1186 |
1187 | 1188 | 1190 | 1191 |
1192 | 1193 |
10531
1194 | 1195 |
1196 | 1198 |
1199 |
106
1200 | 1201 | 1202 |
1203 | rantal 1204 | 18,3 1205 |
1206 | 1207 | 1208 |
1209 | 22 +22 1211 | 1212 |
1213 | 1214 | 1215 |
1216 |
1217 |
1218 | 1219 | 1220 |
1221 | 1222 |
1223 | 1254 | 1255 | 1256 | 1270 | 1271 | 1272 |
1273 |
1274 | 1789 |
1790 | 1791 | 1792 | 1810 | 1811 |
1812 | 1866 | 1921 |
1922 |
1923 | 1924 | 1925 |
1926 | 1927 | 2022 |
2023 |
2024 | 2025 | 2026 | 2036 | 2037 | 2038 | 2039 | 2040 | 2041 | 2042 | 2043 | 2074 | 2077 | 2078 | 2079 | 2080 | 2081 | 2082 | 2083 | --------------------------------------------------------------------------------