├── .gitattributes ├── .gitignore ├── .htaccess ├── README.md ├── class.sosumi.php ├── config-sample.php ├── flintstone.class.php ├── index.php ├── location.json.php └── track.php /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | 4 | # Custom for Visual Studio 5 | *.cs diff=csharp 6 | *.sln merge=union 7 | *.csproj merge=union 8 | *.vbproj merge=union 9 | *.fsproj merge=union 10 | *.dbproj merge=union 11 | 12 | # Standard to msysgit 13 | *.doc diff=astextplain 14 | *.DOC diff=astextplain 15 | *.docx diff=astextplain 16 | *.DOCX diff=astextplain 17 | *.dot diff=astextplain 18 | *.DOT diff=astextplain 19 | *.pdf diff=astextplain 20 | *.PDF diff=astextplain 21 | *.rtf diff=astextplain 22 | *.RTF diff=astextplain 23 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | config.php 2 | 3 | ################# 4 | ## Eclipse 5 | ################# 6 | 7 | *.pydevproject 8 | .project 9 | .metadata 10 | bin/ 11 | tmp/ 12 | *.tmp 13 | *.bak 14 | *.swp 15 | *~.nib 16 | local.properties 17 | .classpath 18 | .settings/ 19 | .loadpath 20 | 21 | # External tool builders 22 | .externalToolBuilders/ 23 | 24 | # Locally stored "Eclipse launch configurations" 25 | *.launch 26 | 27 | # CDT-specific 28 | .cproject 29 | 30 | # PDT-specific 31 | .buildpath 32 | 33 | 34 | ################# 35 | ## Visual Studio 36 | ################# 37 | 38 | ## Ignore Visual Studio temporary files, build results, and 39 | ## files generated by popular Visual Studio add-ons. 40 | 41 | # User-specific files 42 | *.suo 43 | *.user 44 | *.sln.docstates 45 | 46 | # Build results 47 | 48 | [Dd]ebug/ 49 | [Rr]elease/ 50 | x64/ 51 | build/ 52 | [Bb]in/ 53 | [Oo]bj/ 54 | 55 | # MSTest test Results 56 | [Tt]est[Rr]esult*/ 57 | [Bb]uild[Ll]og.* 58 | 59 | *_i.c 60 | *_p.c 61 | *.ilk 62 | *.meta 63 | *.obj 64 | *.pch 65 | *.pdb 66 | *.pgc 67 | *.pgd 68 | *.rsp 69 | *.sbr 70 | *.tlb 71 | *.tli 72 | *.tlh 73 | *.tmp 74 | *.tmp_proj 75 | *.log 76 | *.vspscc 77 | *.vssscc 78 | .builds 79 | *.pidb 80 | *.log 81 | *.scc 82 | 83 | # Visual C++ cache files 84 | ipch/ 85 | *.aps 86 | *.ncb 87 | *.opensdf 88 | *.sdf 89 | *.cachefile 90 | 91 | # Visual Studio profiler 92 | *.psess 93 | *.vsp 94 | *.vspx 95 | 96 | # Guidance Automation Toolkit 97 | *.gpState 98 | 99 | # ReSharper is a .NET coding add-in 100 | _ReSharper*/ 101 | *.[Rr]e[Ss]harper 102 | 103 | # TeamCity is a build add-in 104 | _TeamCity* 105 | 106 | # DotCover is a Code Coverage Tool 107 | *.dotCover 108 | 109 | # NCrunch 110 | *.ncrunch* 111 | .*crunch*.local.xml 112 | 113 | # Installshield output folder 114 | [Ee]xpress/ 115 | 116 | # DocProject is a documentation generator add-in 117 | DocProject/buildhelp/ 118 | DocProject/Help/*.HxT 119 | DocProject/Help/*.HxC 120 | DocProject/Help/*.hhc 121 | DocProject/Help/*.hhk 122 | DocProject/Help/*.hhp 123 | DocProject/Help/Html2 124 | DocProject/Help/html 125 | 126 | # Click-Once directory 127 | publish/ 128 | 129 | # Publish Web Output 130 | *.Publish.xml 131 | *.pubxml 132 | 133 | # NuGet Packages Directory 134 | ## TODO: If you have NuGet Package Restore enabled, uncomment the next line 135 | #packages/ 136 | 137 | # Windows Azure Build Output 138 | csx 139 | *.build.csdef 140 | 141 | # Windows Store app package directory 142 | AppPackages/ 143 | 144 | # Others 145 | sql/ 146 | *.Cache 147 | ClientBin/ 148 | [Ss]tyle[Cc]op.* 149 | ~$* 150 | *~ 151 | *.dbmdl 152 | *.[Pp]ublish.xml 153 | *.pfx 154 | *.publishsettings 155 | 156 | # RIA/Silverlight projects 157 | Generated_Code/ 158 | 159 | # Backup & report files from converting an old project file to a newer 160 | # Visual Studio version. Backup files are not needed, because we have git ;-) 161 | _UpgradeReport_Files/ 162 | Backup*/ 163 | UpgradeLog*.XML 164 | UpgradeLog*.htm 165 | 166 | # SQL Server files 167 | App_Data/*.mdf 168 | App_Data/*.ldf 169 | 170 | ############# 171 | ## Windows detritus 172 | ############# 173 | 174 | # Windows image file caches 175 | Thumbs.db 176 | ehthumbs.db 177 | 178 | # Folder config file 179 | Desktop.ini 180 | 181 | # Recycle Bin used on file shares 182 | $RECYCLE.BIN/ 183 | 184 | # Mac crap 185 | .DS_Store 186 | 187 | 188 | ############# 189 | ## Python 190 | ############# 191 | 192 | *.py[co] 193 | 194 | # Packages 195 | *.egg 196 | *.egg-info 197 | dist/ 198 | build/ 199 | eggs/ 200 | parts/ 201 | var/ 202 | sdist/ 203 | develop-eggs/ 204 | .installed.cfg 205 | 206 | # Installer logs 207 | pip-log.txt 208 | 209 | # Unit test / coverage reports 210 | .coverage 211 | .tox 212 | 213 | #Translations 214 | *.mo 215 | 216 | #Mr Developer 217 | .mr.developer.cfg 218 | -------------------------------------------------------------------------------- /.htaccess: -------------------------------------------------------------------------------- 1 | # Only from local Network 2 | Order Deny,Allow 3 | Deny from all 4 | Allow from 192.168.178.0/24 5 | Allow from 127.0.0.0/24 6 | 7 | 8 | deny from all 9 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | php-location-tracking-icloud 2 | ============================ 3 | mit "find my iPhone" 4 | 5 | 6 | track.php 7 | --------- 8 | Speichert Daten von Find my iPhone in Datenbank. Wird durch Cron aufgerufen. 9 | 10 | 11 | index.php 12 | --------- 13 | zeigt Daten der Location-Datenbank auf Google Map oder zusätzlich als als Tabelle mit ?showtable 14 | -------------------------------------------------------------------------------- /class.sosumi.php: -------------------------------------------------------------------------------- 1 | 10 | // http://github.com/tylerhall/sosumi/tree/master 11 | // 12 | // Usage: 13 | // $ssm = new Sosumi('username', 'password'); 14 | // $location_info = $ssm->locate(); 15 | // $ssm->sendMessage('Your Message', true, , 'Important Message'); 16 | // 17 | 18 | class Sosumi 19 | { 20 | public $devices; 21 | public $debug; 22 | private $username; 23 | private $password; 24 | private $partition; 25 | 26 | public function __construct($mobile_me_username, $mobile_me_password, $debug = false) 27 | { 28 | $this->devices = array(); 29 | $this->debug = $debug; 30 | $this->username = $mobile_me_username; 31 | $this->password = $mobile_me_password; 32 | $this->getPartition(); 33 | $this->updateDevices(); 34 | } 35 | 36 | private function getPartition() 37 | { 38 | $this->iflog('Getting partition...'); 39 | $post = '{"clientContext":{"appName":"FindMyiPhone","appVersion":"1.4","buildVersion":"145","deviceUDID":"0000000000000000000000000000000000000000","inactiveTime":2147483647,"osVersion":"4.2.1","personID":0,"productType":"iPad1,1"}}'; 40 | $response = $this->curlPost("/fmipservice/device/{$this->username}/initClient", $post, array(), true); 41 | preg_match('/MMe-Host:(.*?)$/msi', $response, $matches); 42 | if(isset($matches[1])) $this->partition = trim($matches[1]); 43 | } 44 | 45 | public function locate($device_num = 0, $max_wait = 300) 46 | { 47 | $start = time(); 48 | 49 | // Loop until the device has been located... 50 | while(!$this->devices[$device_num]->latitude || !$this->devices[$device_num]->longitude) 51 | { 52 | $this->iflog('Waiting for location...'); 53 | if((time() - $start) > $max_wait) 54 | { 55 | throw new Exception("Unable to find location within '$max_wait' seconds\n"); 56 | } 57 | 58 | sleep(5); 59 | $this->updateDevices(); 60 | } 61 | 62 | $loc = array( 63 | "latitude" => $this->devices[$device_num]->latitude, 64 | "longitude" => $this->devices[$device_num]->longitude, 65 | "accuracy" => $this->devices[$device_num]->horizontalAccuracy, 66 | "timestamp" => $this->devices[$device_num]->locationTimestamp, 67 | ); 68 | 69 | return $loc; 70 | } 71 | 72 | public function sendMessage($msg, $alarm = false, $device_num = 0, $subject = 'Important Message') 73 | { 74 | $post = sprintf('{"clientContext":{"appName":"FindMyiPhone","appVersion":"1.4","buildVersion":"145","deviceUDID":"0000000000000000000000000000000000000000","inactiveTime":5911,"osVersion":"3.2","productType":"iPad1,1","selectedDevice":"%s","shouldLocate":false},"device":"%s","serverContext":{"callbackIntervalInMS":3000,"clientId":"0000000000000000000000000000000000000000","deviceLoadStatus":"203","hasDevices":true,"lastSessionExtensionTime":null,"maxDeviceLoadTime":60000,"maxLocatingTime":90000,"preferredLanguage":"en","prefsUpdateTime":1276872996660,"sessionLifespan":900000,"timezone":{"currentOffset":-25200000,"previousOffset":-28800000,"previousTransition":1268560799999,"tzCurrentName":"Pacific Daylight Time","tzName":"America/Los_Angeles"},"validRegion":true},"sound":%s,"subject":"%s","text":"%s","userText":true}', 75 | $this->devices[$device_num]->id, $this->devices[$device_num]->id, 76 | $alarm ? 'true' : 'false', $subject, $msg); 77 | 78 | $this->iflog('Sending message...'); 79 | $this->curlPost("/fmipservice/device/{$this->username}/sendMessage", $post); 80 | $this->iflog('Message sent'); 81 | } 82 | 83 | public function remoteLock($passcode, $device_num = 0) 84 | { 85 | $post = sprintf('{"clientContext":{"appName":"FindMyiPhone","appVersion":"1.4","buildVersion":"145","deviceUDID":"0000000000000000000000000000000000000000","inactiveTime":5911,"osVersion":"3.2","productType":"iPad1,1","selectedDevice":"%s","shouldLocate":false},"device":"%s","oldPasscode":"","passcode":"%s","serverContext":{"callbackIntervalInMS":3000,"clientId":"0000000000000000000000000000000000000000","deviceLoadStatus":"203","hasDevices":true,"lastSessionExtensionTime":null,"maxDeviceLoadTime":60000,"maxLocatingTime":90000,"preferredLanguage":"en","prefsUpdateTime":1276872996660,"sessionLifespan":900000,"timezone":{"currentOffset":-25200000,"previousOffset":-28800000,"previousTransition":1268560799999,"tzCurrentName":"Pacific Daylight Time","tzName":"America/Los_Angeles"},"validRegion":true}}', 86 | $this->devices[$device_num]->id, $this->devices[$device_num]->id, $passcode); 87 | 88 | $this->iflog('Sending remote lock...'); 89 | $this->curlPost("/fmipservice/device/{$this->username}/remoteLock", $post); 90 | $this->iflog('Remote lock sent'); 91 | } 92 | 93 | // This hasn't been tested (for obvious reasons). Please let me know if it does/doesn't work... 94 | public function remoteWipe($device_num = 0) 95 | { 96 | $post = sprintf('{"clientContext":{"appName":"FindMyiPhone","appVersion":"1.4","buildVersion":"145","deviceUDID":"0000000000000000000000000000000000000000","inactiveTime":5911,"osVersion":"3.2","productType":"iPad1,1","selectedDevice":"%s","shouldLocate":false},"device":"%s","oldPasscode":"","passcode":"%s","serverContext":{"callbackIntervalInMS":3000,"clientId":"0000000000000000000000000000000000000000","deviceLoadStatus":"203","hasDevices":true,"lastSessionExtensionTime":null,"maxDeviceLoadTime":60000,"maxLocatingTime":90000,"preferredLanguage":"en","prefsUpdateTime":1276872996660,"sessionLifespan":900000,"timezone":{"currentOffset":-25200000,"previousOffset":-28800000,"previousTransition":1268560799999,"tzCurrentName":"Pacific Daylight Time","tzName":"America/Los_Angeles"},"validRegion":true}}', 97 | $this->devices[$device_num]->id, $this->devices[$device_num]->id, $passcode); 98 | 99 | $this->iflog('Sending remote wipe...'); 100 | $this->curlPost("/fmipservice/device/{$this->username}/remoteWipe", $post); 101 | $this->iflog('Remote wipe sent'); 102 | } 103 | 104 | private function updateDevices() 105 | { 106 | $this->iflog('updateDevices...'); 107 | $post = '{"clientContext":{"appName":"FindMyiPhone","appVersion":"1.4","buildVersion":"145","deviceUDID":"0000000000000000000000000000000000000000","inactiveTime":2147483647,"osVersion":"4.2.1","personID":0,"productType":"iPad1,1"}}'; 108 | $json_str = $this->curlPost("/fmipservice/device/{$this->username}/initClient", $post); 109 | $this->iflog('updateDevices Returned: ' . $json_str); 110 | $json = json_decode($json_str); 111 | 112 | if(is_null($json)) 113 | throw new Exception("Error parsing json string"); 114 | 115 | if(isset($json->error)) 116 | throw new Exception("Error from web service: '$json->error'"); 117 | 118 | $this->devices = array(); 119 | if(isset($json) && isset($json->content) && (is_array($json->content) || is_object($json->content))){ 120 | $this->iflog('Parsing ' . count($json->content) . ' devices...'); 121 | foreach($json->content as $json_device) 122 | { 123 | $device = new SosumiDevice(); 124 | if(isset($json_device->location) && is_object($json_device->location)) 125 | { 126 | $device->locationTimestamp = date('Y-m-d H:i:s', $json_device->location->timeStamp / 1000); 127 | $device->locationType = $json_device->location->positionType; 128 | $device->horizontalAccuracy = $json_device->location->horizontalAccuracy; 129 | $device->locationFinished = $json_device->location->locationFinished; 130 | $device->longitude = $json_device->location->longitude; 131 | $device->latitude = $json_device->location->latitude; 132 | } 133 | $device->isLocating = $json_device->isLocating; 134 | $device->deviceModel = $json_device->deviceModel; 135 | $device->deviceStatus = $json_device->deviceStatus; 136 | $device->id = $json_device->id; 137 | $device->name = $json_device->name; 138 | $device->deviceClass = $json_device->deviceClass; 139 | $device->chargingStatus = $json_device->batteryStatus; 140 | $device->batteryLevel = $json_device->batteryLevel; 141 | $this->devices[] = $device; 142 | } 143 | } 144 | } 145 | 146 | private function curlPost($url, $post_vars = '', $headers = array(), $return_headers = false) 147 | { 148 | if(isset($this->partition)) 149 | $url = 'https://' . $this->partition . $url; 150 | else 151 | $url = 'https://fmipmobile.icloud.com' . $url; 152 | 153 | $this->iflog("URL: $url"); 154 | $this->iflog("POST DATA: $post_vars"); 155 | 156 | $headers[] = 'Content-Type: application/json; charset=utf-8'; 157 | $headers[] = 'X-Apple-Find-Api-Ver: 2.0'; 158 | $headers[] = 'X-Apple-Authscheme: UserIdGuest'; 159 | $headers[] = 'X-Apple-Realm-Support: 1.0'; 160 | $headers[] = 'User-agent: Find iPhone/1.4 MeKit (iPad: iPhone OS/4.2.1)'; 161 | $headers[] = 'X-Client-Name: iPad'; 162 | $headers[] = 'X-Client-UUID: 0cf3dc501ff812adb0b202baed4f37274b210853'; 163 | $headers[] = 'Accept-Language: en-us'; 164 | $headers[] = "Connection: keep-alive"; 165 | 166 | $ch = curl_init($url); 167 | curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); 168 | curl_setopt($ch, CURLOPT_USERPWD, $this->username . ':' . $this->password); 169 | curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true); 170 | curl_setopt($ch, CURLOPT_AUTOREFERER, true); 171 | curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); 172 | curl_setopt($ch, CURLOPT_POST, true); 173 | curl_setopt($ch, CURLOPT_POSTFIELDS, $post_vars); 174 | if(!is_null($headers)) curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); 175 | 176 | // curl_setopt($ch, CURLOPT_VERBOSE, true); 177 | 178 | if($return_headers) 179 | curl_setopt($ch, CURLOPT_HEADER, true); 180 | 181 | return curl_exec($ch); 182 | } 183 | 184 | private function iflog($str) 185 | { 186 | if($this->debug === true) 187 | echo $str . "\n"; 188 | } 189 | } 190 | 191 | class SosumiDevice 192 | { 193 | public $isLocating; 194 | public $locationTimestamp; 195 | public $locationType; 196 | public $horizontalAccuracy; 197 | public $locationFinished; 198 | public $longitude; 199 | public $latitude; 200 | public $deviceModel; 201 | public $deviceStatus; 202 | public $id; 203 | public $name; 204 | public $deviceClass; 205 | 206 | // These values only recently appeared in Apple's JSON response. 207 | // Their final names will probably change to something other than 208 | // 'a' and 'b'. 209 | public $chargingStatus; // location->a 210 | public $batteryLevel; // location->b 211 | } 212 | 213 | 214 | -------------------------------------------------------------------------------- /config-sample.php: -------------------------------------------------------------------------------- 1 | 24 | * @version 1.3 25 | * @package flintstone 26 | */ 27 | 28 | class Flintstone { 29 | 30 | /** 31 | * Static instance 32 | * @access private 33 | * @var array 34 | */ 35 | private static $instance = array(); 36 | 37 | /** 38 | * Load a database 39 | * @param string $database the database name 40 | * @param array $options an array of options 41 | * @return object the FlintstoneDB class 42 | */ 43 | public static function load($database, $options = array()) { 44 | if (!array_key_exists($database, self::$instance)) { 45 | self::$instance[$database] = new FlintstoneDB($database, $options); 46 | } 47 | 48 | return self::$instance[$database]; 49 | } 50 | } 51 | 52 | class FlintstoneDB { 53 | 54 | /** 55 | * Database name 56 | * @access private 57 | * @var string 58 | */ 59 | private $db = null; 60 | 61 | /** 62 | * Database data 63 | * @access private 64 | * @var array 65 | */ 66 | private $data = array(); 67 | 68 | /** 69 | * Flintstone options: 70 | * 71 | * - string $dir the directory to the database files 72 | * - string $ext the database file extension 73 | * - boolean $gzip use gzip to compress database 74 | * - boolean $cache store get() results in memory 75 | * - integer $swap_memory_limit write out each line to a temporary file and swap if database is larger than limit (0 to always do this) 76 | * 77 | * @access public 78 | * @var array 79 | */ 80 | public $options = array('dir' => '', 'ext' => '.dat', 'gzip' => false, 'cache' => true, 'swap_memory_limit' => 1048576); 81 | 82 | /** 83 | * Flintstone constructor 84 | * @param string $database the database name 85 | * @param array $options an array of options 86 | * @return void 87 | */ 88 | public function __construct($database, $options) { 89 | 90 | // Check valid characters in database name 91 | if (!preg_match("/^([A-Za-z0-9_]+)$/", $database)) { 92 | throw new FlintstoneException('Invalid characters in database name'); 93 | } 94 | 95 | // Set current database 96 | $this->db = $database; 97 | 98 | // Set options 99 | if (!empty($options)) { 100 | $this->setOptions($options); 101 | } 102 | } 103 | 104 | /** 105 | * Set flintstone options 106 | * @param array $options an array of options 107 | * @return void 108 | */ 109 | public function setOptions($options) { 110 | foreach ($options as $key => $value) { 111 | $this->options[$key] = $value; 112 | } 113 | } 114 | 115 | /** 116 | * Setup the database and perform pre-flight checks 117 | * @return void 118 | */ 119 | public function setupDatabase() { 120 | if (empty($this->data)) { 121 | 122 | // Check database directory 123 | $dir = rtrim($this->options['dir'], '/\\') . DIRECTORY_SEPARATOR; 124 | 125 | if (!is_dir($dir)) { 126 | throw new FlintstoneException($dir . ' is not a valid directory'); 127 | } 128 | 129 | // Set data 130 | $ext = $this->options['ext']; 131 | if (substr($ext, 0, 1) !== ".") $ext = "." . $ext; 132 | if ($this->options['gzip'] === true && substr($ext, -3) !== ".gz") $ext .= ".gz"; 133 | $this->data['file'] = $dir . $this->db . $ext; 134 | $this->data['file_tmp'] = $dir . $this->db . "_tmp" . $ext; 135 | $this->data['cache'] = array(); 136 | 137 | // Create database 138 | if (!file_exists($this->data['file'])) { 139 | if (($fp = $this->openFile($this->data['file'], "wb")) !== false) { 140 | @fclose($fp); 141 | @chmod($this->data['file'], 0777); 142 | clearstatcache(); 143 | } 144 | else { 145 | throw new FlintstoneException('Could not create database ' . $this->db); 146 | } 147 | } 148 | 149 | // Check file is readable 150 | if (!is_readable($this->data['file'])) { 151 | throw new FlintstoneException('Could not read database ' . $this->db); 152 | } 153 | 154 | // Check file is writable 155 | if (!is_writable($this->data['file'])) { 156 | throw new FlintstoneException('Could not write to database ' . $this->db); 157 | } 158 | } 159 | } 160 | 161 | /** 162 | * Open the database file 163 | * @param string $file the file path 164 | * @param string $mode the file mode 165 | * @return object file pointer 166 | */ 167 | private function openFile($file, $mode) { 168 | if ($this->options['gzip'] === true) $file = 'compress.zlib://' . $file; 169 | return @fopen($file, $mode); 170 | } 171 | 172 | /** 173 | * Get a key from the database 174 | * @param string $key the key 175 | * @return mixed the data 176 | */ 177 | private function getKey($key) { 178 | 179 | $data = false; 180 | 181 | // Look in cache for key 182 | if ($this->options['cache'] === true && array_key_exists($key, $this->data['cache'])) { 183 | return $this->data['cache'][$key]; 184 | } 185 | 186 | // Open file 187 | if (($fp = $this->openFile($this->data['file'], "rb")) !== false) { 188 | 189 | // Lock file 190 | @flock($fp, LOCK_SH); 191 | 192 | // Loop through each line of file 193 | while (($line = fgets($fp)) !== false) { 194 | 195 | // Remove new line character from end 196 | $line = rtrim($line); 197 | 198 | // Split up seperator 199 | $pieces = explode("=", $line); 200 | 201 | // Match found 202 | if ($pieces[0] == $key) { 203 | 204 | // Put remaining pieces back together 205 | if (count($pieces) > 2) { 206 | array_shift($pieces); 207 | $data = implode("=", $pieces); 208 | } 209 | else { 210 | $data = $pieces[1]; 211 | } 212 | 213 | // Unserialize data 214 | $data = unserialize($data); 215 | 216 | // Preserve new lines 217 | $data = $this->preserveLines($data, true); 218 | 219 | // Save to cache 220 | if ($this->options['cache'] === true) { 221 | $this->data['cache'][$key] = $data; 222 | } 223 | 224 | break; 225 | } 226 | } 227 | 228 | // Unlock and close file 229 | @flock($fp, LOCK_UN); 230 | @fclose($fp); 231 | } 232 | else { 233 | throw new FlintstoneException('Could not open database ' . $this->db); 234 | } 235 | 236 | return $data; 237 | } 238 | 239 | /** 240 | * Replace a key in the database 241 | * @param string $key the key 242 | * @param mixed $data the data to store, or false to delete 243 | * @return boolean successful replace 244 | */ 245 | private function replaceKey($key, $data) { 246 | 247 | // Use memory or swap? 248 | $swap = true; 249 | if ($this->options['swap_memory_limit'] > 0) { 250 | clearstatcache(); 251 | if (filesize($this->data['file']) <= $this->options['swap_memory_limit']) { 252 | $swap = false; 253 | $contents = ""; 254 | } 255 | } 256 | 257 | if ($data !== false) { 258 | 259 | // Create a copy of data to push into cache 260 | if ($this->options['cache'] === true) { 261 | $orig_data = $data; 262 | } 263 | 264 | // Preserve new lines 265 | $data = $this->preserveLines($data, false); 266 | 267 | // Serialize data 268 | $data = serialize($data); 269 | } 270 | 271 | // Open tmp file 272 | if ($swap) { 273 | if (($tp = $this->openFile($this->data['file_tmp'], "ab")) !== false) { 274 | @flock($tp, LOCK_EX); 275 | } 276 | else { 277 | throw new FlintstoneException('Could not create temporary database for ' . $this->db); 278 | } 279 | } 280 | 281 | // Open file 282 | if (($fp = $this->openFile($this->data['file'], "rb")) !== false) { 283 | 284 | // Lock file 285 | @flock($fp, LOCK_SH); 286 | 287 | // Loop through each line of file 288 | while (($line = fgets($fp)) !== false) { 289 | 290 | // Split up seperator 291 | $pieces = explode("=", $line); 292 | 293 | // Match found 294 | if ($pieces[0] == $key) { 295 | 296 | // Skip line to delete 297 | if ($data === false) continue; 298 | 299 | // New line 300 | $line = $key . "=" . $data . "\n"; 301 | 302 | // Save to cache 303 | if ($this->options['cache'] === true) { 304 | $this->data['cache'][$key] = $orig_data; 305 | } 306 | } 307 | 308 | if ($swap) { 309 | 310 | // Write line 311 | $fwrite = @fwrite($tp, $line); 312 | 313 | if ($fwrite === false) { 314 | throw new FlintstoneException('Could not write to temporary database ' . $this->db); 315 | } 316 | } 317 | else { 318 | 319 | // Save line to memory 320 | $contents .= $line; 321 | } 322 | } 323 | 324 | // Unlock and close file 325 | @flock($fp, LOCK_UN); 326 | @fclose($fp); 327 | 328 | if ($swap) { 329 | 330 | // Unlock and close tmp file 331 | @flock($tp, LOCK_UN); 332 | @fclose($tp); 333 | 334 | // Remove file 335 | if (!@unlink($this->data['file'])) { 336 | throw new FlintstoneException('Could not remove old database ' . $this->db); 337 | } 338 | 339 | // Rename tmp file 340 | if (!@rename($this->data['file_tmp'], $this->data['file'])) { 341 | throw new FlintstoneException('Could not rename temporary database ' . $this->db); 342 | } 343 | 344 | // Set permissions 345 | @chmod($this->data['file'], 0777); 346 | } 347 | else { 348 | 349 | // Open file 350 | if (($fp = $this->openFile($this->data['file'], "wb")) !== false) { 351 | 352 | // Lock file 353 | @flock($fp, LOCK_EX); 354 | 355 | // Write contents 356 | $fwrite = @fwrite($fp, $contents); 357 | 358 | // Unlock and close file 359 | @flock($fp, LOCK_UN); 360 | @fclose($fp); 361 | 362 | // Free up memory 363 | unset($contents); 364 | 365 | if ($fwrite === false) { 366 | throw new FlintstoneException('Could not write to database ' . $this->db); 367 | } 368 | } 369 | else { 370 | throw new FlintstoneException('Could not open database ' . $this->db); 371 | } 372 | } 373 | } 374 | else { 375 | throw new FlintstoneException('Could not open database ' . $this->db); 376 | } 377 | 378 | return true; 379 | } 380 | 381 | /** 382 | * Set a key to store in the database 383 | * @param string $key the key 384 | * @param mixed $data the data to store 385 | * @return boolean successful set 386 | */ 387 | private function setKey($key, $data) { 388 | 389 | // Replace existing key? 390 | if ($this->getKey($key) !== false) { 391 | return $this->replaceKey($key, $data); 392 | } 393 | 394 | // Create a copy of data to push into cache 395 | if ($this->options['cache'] === true) { 396 | $orig_data = $data; 397 | } 398 | 399 | // Preserve new lines 400 | $data = $this->preserveLines($data, false); 401 | 402 | // Serialize data 403 | $data = serialize($data); 404 | 405 | // Open file 406 | if (($fp = $this->openFile($this->data['file'], "ab")) !== false) { 407 | 408 | // Lock file 409 | @flock($fp, LOCK_EX); 410 | 411 | // Set line, we don't use PHP_EOL to keep it cross-platform compatible 412 | $line = $key . "=" . $data . "\n"; 413 | 414 | // Write line 415 | $fwrite = @fwrite($fp, $line); 416 | 417 | // Unlock and close file 418 | @flock($fp, LOCK_UN); 419 | @fclose($fp); 420 | 421 | if ($fwrite === false) { 422 | throw new FlintstoneException('Could not write to database ' . $this->db); 423 | } 424 | 425 | // Save to cache 426 | if ($this->options['cache'] === true) { 427 | $this->data['cache'][$key] = $orig_data; 428 | } 429 | } 430 | else { 431 | throw new FlintstoneException('Could not open database ' . $this->db); 432 | } 433 | 434 | return true; 435 | } 436 | 437 | /** 438 | * Delete a key from the database 439 | * @param string $key the key 440 | * @return boolean successful delete 441 | */ 442 | private function deleteKey($key) { 443 | 444 | // Find key 445 | if ($this->getKey($key) !== false) { 446 | 447 | // Replace existing key 448 | if ($this->replaceKey($key, false)) { 449 | 450 | // Remove from cache 451 | if ($this->options['cache'] === true && array_key_exists($key, $this->data['cache'])) { 452 | unset($this->data['cache'][$key]); 453 | } 454 | 455 | return true; 456 | } 457 | } 458 | 459 | return false; 460 | } 461 | 462 | /** 463 | * Flush the database 464 | * @return boolean successful flush 465 | */ 466 | private function flushDatabase() { 467 | 468 | // Open file to truncate (w mode) 469 | if (($fp = $this->openFile($this->data['file'], "wb")) !== false) { 470 | 471 | // Close file 472 | @fclose($fp); 473 | 474 | // Empty cache 475 | if ($this->options['cache'] === true) { 476 | $this->data['cache'] = array(); 477 | } 478 | } 479 | else { 480 | throw new FlintstoneException('Could not open database ' . $this->db); 481 | } 482 | 483 | return true; 484 | } 485 | 486 | /** 487 | * Get all keys from the database 488 | * @return array of keys 489 | */ 490 | private function getAllKeys() { 491 | 492 | $keys = array(); 493 | 494 | // Open file 495 | if (($fp = $this->openFile($this->data['file'], "rb")) !== false) { 496 | 497 | // Lock file 498 | @flock($fp, LOCK_SH); 499 | 500 | // Loop through each line of file 501 | while (($line = fgets($fp)) !== false) { 502 | 503 | // Split up seperator 504 | $pieces = explode("=", $line); 505 | $keys[] = $pieces[0]; 506 | } 507 | 508 | // Unlock and close file 509 | @flock($fp, LOCK_UN); 510 | @fclose($fp); 511 | } 512 | else { 513 | throw new FlintstoneException('Could not open database ' . $this->db); 514 | } 515 | 516 | return $keys; 517 | } 518 | 519 | /** 520 | * Preserve new lines, recursive function 521 | * @param mixed $data the data 522 | * @param boolean $reverse to reverse the replacement order 523 | * @return mixed the data 524 | */ 525 | private function preserveLines($data, $reverse) { 526 | 527 | if ($reverse) { 528 | $from = array("\\n", "\\r"); 529 | $to = array("\n", "\r"); 530 | } 531 | else { 532 | $from = array("\n", "\r"); 533 | $to = array("\\n", "\\r"); 534 | } 535 | 536 | if (is_string($data)) { 537 | $data = str_replace($from, $to, $data); 538 | } 539 | elseif (is_array($data)) { 540 | foreach ($data as $key => $value) { 541 | $data[$key] = $this->preserveLines($value, $reverse); 542 | } 543 | } 544 | 545 | return $data; 546 | } 547 | 548 | /** 549 | * Check the database has been loaded and valid key 550 | * @param string $key the key 551 | * @return boolean 552 | */ 553 | private function isValidKey($key) { 554 | 555 | // Check key length 556 | $len = strlen($key); 557 | 558 | if ($len < 1) { 559 | throw new FlintstoneException('No key has been set'); 560 | } 561 | 562 | if ($len > 50) { 563 | throw new FlintstoneException('Maximum key length is 50 characters'); 564 | } 565 | 566 | // Check valid characters in key 567 | if (!preg_match("/^([A-Za-z0-9_]+)$/", $key)) { 568 | throw new FlintstoneException('Invalid characters in key'); 569 | } 570 | 571 | return true; 572 | } 573 | 574 | /** 575 | * Check the data type is valid 576 | * @param mixed $data the data 577 | * @return boolean 578 | */ 579 | private function isValidData($data) { 580 | if (!is_string($data) && !is_int($data) && !is_float($data) && !is_array($data)) { 581 | throw new FlintstoneException('Invalid data type'); 582 | } 583 | return true; 584 | } 585 | 586 | /** 587 | * Get a key from the database 588 | * @param string $key the key 589 | * @return mixed the data 590 | */ 591 | public function get($key) { 592 | $this->setupDatabase(); 593 | 594 | if ($this->isValidKey($key)) { 595 | return $this->getKey($key); 596 | } 597 | 598 | return false; 599 | } 600 | 601 | /** 602 | * Set a key to store in the database 603 | * @param string $key the key 604 | * @param mixed $data the data to store 605 | * @return boolean successful set 606 | */ 607 | public function set($key, $data) { 608 | $this->setupDatabase(); 609 | 610 | if ($this->isValidKey($key) && $this->isValidData($data)) { 611 | return $this->setKey($key, $data); 612 | } 613 | 614 | return false; 615 | } 616 | 617 | /** 618 | * Replace a key in the database 619 | * @param string $key the key 620 | * @param mixed $data the data to store 621 | * @return boolean successful replace 622 | */ 623 | public function replace($key, $data) { 624 | $this->setupDatabase(); 625 | 626 | if ($this->isValidKey($key) && $this->isValidData($data)) { 627 | return $this->replaceKey($key, $data); 628 | } 629 | 630 | return false; 631 | } 632 | 633 | /** 634 | * Delete a key from the database 635 | * @param string $key the key 636 | * @return boolean successful delete 637 | */ 638 | public function delete($key) { 639 | $this->setupDatabase(); 640 | 641 | if ($this->isValidKey($key)) { 642 | return $this->deleteKey($key); 643 | } 644 | 645 | return false; 646 | } 647 | 648 | /** 649 | * Flush the database 650 | * @return boolean successful flush 651 | */ 652 | public function flush() { 653 | $this->setupDatabase(); 654 | return $this->flushDatabase(); 655 | } 656 | 657 | /** 658 | * Get all keys from the database 659 | * @return array list of keys 660 | */ 661 | public function getKeys() { 662 | $this->setupDatabase(); 663 | return $this->getAllKeys(); 664 | } 665 | } 666 | 667 | /** 668 | * Flintstone exception 669 | */ 670 | class FlintstoneException extends Exception { } 671 | ?> -------------------------------------------------------------------------------- /index.php: -------------------------------------------------------------------------------- 1 | './', 'gzip' => false); 7 | $db['settings'] = Flintstone::load('db_settings', $options); 8 | 9 | ?> 10 | 11 | 12 | 13 | 14 | 15 | Find my iPhone - Location Tracking 16 | 97 | 98 | 99 |
100 |
101 | Loading 102 |
103 | 104 |
105 |
106 |
Lastcheck: get('lastcheck')); ?> — NextCheck: get('lastcheck')); ?> Sekunden
107 |
108 |
109 | 110 | 111 | 112 | 276 | 277 | 278 | -------------------------------------------------------------------------------- /location.json.php: -------------------------------------------------------------------------------- 1 | './', 'gzip' => false); 18 | $db['settings'] = Flintstone::load('db_settings', $options); 19 | $db['locations'] = Flintstone::load('db_locations', $options); 20 | 21 | function getCoordinates($deviceID = false) { 22 | global $db; 23 | $keys = $db['locations']->getKeys(); 24 | 25 | foreach ($keys as $location) { 26 | $entry = $db['locations']->get($location); 27 | if(!$deviceID || $entry['deviceID'] == $deviceID) { 28 | $result[] = $entry; 29 | } 30 | } 31 | return $result; 32 | } 33 | $locations = getCoordinates(); 34 | 35 | foreach ($locations as $entry) { 36 | $l[] = $entry; 37 | } 38 | 39 | echo json_encode($l); 40 | 41 | // CACHING 42 | file_put_contents($cache_filename, ob_get_contents()); 43 | ob_end_flush(); 44 | // CACHING 45 | ?> -------------------------------------------------------------------------------- /track.php: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sinky/php-location-tracking-icloud/1e241c922aea3b65f969e793bdcdb95b888a0957/track.php --------------------------------------------------------------------------------