11
Attenzione! Questo contenuto è vecchioQuesto articolo risale al 2013, quindi i contenuti e le operazioni qui consigliate potrebbero essere diventate obsolete nel corso del tempo.

Forse non tutti sanno che il vecchio e glorioso megaupload è stato rimpiazzato, da già un pò di tempo, da un nuovo servizio chiamato MEGA ( mega.co.nz ) sempre sviluppato da grossa parte del team che a suo tempo aveva sviluppato megaupload e megavideo.

Una bella funzionalità di megaupload era la sua estrema semplicità nell'integrazione con PHP, sia in download che in upload.. Infatti, con qualche linea di codice PHP si poteva tranquillamente scaricare un file ( anche senza account premium ), o uploadarne uno.

Il nuovo servizio MEGA, a differenza del suo predecessore, utilizza un ragionamento diverso per quanto riguarda lo storage interno degli upload che la gente invia. Infatti, effettuare un download via PHP è leggermente più complesso, a causa degli algoritmi di crittazione legati ai dati binari del file da scaricare.

Qui vi viene in aiuto la classe mf_php_megaconz_downloader, acronimo di Maurizio Fonte Mega.co.nz Php Downloader. Questa classe vi permette, con un paio di righe di codice, di effettuare un download dai serveri di mega.co.nz direttamente via PHP. Vediamo quindi il codice sorgente.

CODICE SORGENTE DELLA CLASSE MF PHP MEGA.CO.NZ DOWNLOADER ( richiede un server LAMP con PHP >= 4, e estensione mcrypt attiva e abilitata )

<?php
/*
* File: class.mega.co.nz.php
* Author: Maurizio Fonte
* Copyright: 2013 Maurizio Fonte
* Date: 10/09/2013
* Link: http://www.mauriziofonte.it/blog/post/scaricare-file-mega-co-nz-con-php.html
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details:
* http://www.gnu.org/licenses/gpl.html
*
*/
	
	class mf_php_megaconz_downloader {
	
		private static $downloadErrors = Array ( 
													1 => 'decryption of the downloaded file was wrong ( CRC check failed )',
													2 => 'There was a problem in writing the downloaded file into your filesystem. Check permissions...',
													3 => 'couldn\'t get remote contents of the file from mega.co.nz',
													4 => 'no direct download url found... private file?',
													5 => 'couldn\'t ping mega.co.nz api url, or api istance params incorrect',
													6 => 'file key was wrong: check for typos in the mega.co.nz download url',
													7 => 'mega.co.nz download url was incorrect, couldn\'t start the download process...'
												);
													
		/* Public mega.co.nz download descriptor IDs */
		public $fileid = null;
		public $filekey = null;
		
		/* private download-related variables */
		private $seqno = null;
		private $sid = null;
		private $download_url_is_valid = false;
		
		/* public download-related variables */
		public $download_url = null;
		public $download_size = null;
		public $download_filename = null;
		public $download_attributes_object = null;
		public $download_path = null;
		
		/* public download error code and messages */
		public $download_error_code = null;
		public $download_error_message = null;
		
		
		public function mf_php_megaconz_downloader ( $url ) {
			
			if ( strpos ( $url, 'mega.co.nz' ) !== false ) {
				
				$url_parts = explode ( '!', end ( explode ( '#', $url ) ) );
				
				if ( is_array ( $url_parts ) && count ( array_keys ( $url_parts ) ) == 3 && empty ( $url_parts[0] ) && strlen ( $url_parts[1] ) > 0 && strlen ( $url_parts[2] ) > 0 ) {
					$this -> download_url_is_valid = true;
					$this -> seqno = rand ( 0, 0xFFFFFFFF );
					$this -> fileid = $url_parts[1];
					$this -> filekey = $url_parts[2];
				}
			}
			
		}
		
		public function download ( $destination_path = null, $destination_filename = null, $disable_crc_check = false ) {
			
			$download_error = null;
			
			if ( $this -> download_url_is_valid ) {
				
				$key = self::base64_to_a32 ( $this -> filekey );
				if ( is_array ( $key ) ) {
					
					$k = array ( $key[0] ^ $key[4], $key[1] ^ $key[5], $key[2] ^ $key[6], $key[3] ^ $key[7] );
					$iv = array_merge ( array_slice ( $key, 4, 2 ), array ( 0, 0 ) );
					$meta_mac = array_slice ( $key, 6, 2 );
					
					$file = $this -> api_req ( Array ( 'a' => 'g', 'g' => 1, 'p'=> $this -> fileid ) );
					if ( $file ) {
						$this -> download_url = $file -> g;
						$this -> download_size = $file -> s;
						$this -> download_attributes_object = self::dec_attr ( self::base64urldecode ( $file -> at ), $k );
						if ( $this -> download_attributes_object ) $this -> download_filename = $this -> download_attributes_object -> n;
						
						if ( $this -> download_url ) {
							
							$data_enc = self::curlpost ( $this -> download_url );
							if ( $data_enc !== false ) {
								
								$file_contents_decrypted = self::aes_ctr_decrypt ( $data_enc, self::a32_to_str ( $k ), self::a32_to_str ( $iv ) );
								
								$save_location = '';
								if ( ! empty ( $destination_path ) ) $save_location .= rtrim ( $destination_path, '/' ) . '/';
								if ( ! empty ( $destination_filename ) ) $save_location .= $destination_filename;
								else if ( ! empty ( $this -> download_filename ) ) $save_location .= $this -> download_filename;
								else $save_location .= '_downloaded_from_mega.co.nz_' . date ( 'Y-m-d.H.i.s' );
								
								if ( is_file ( $save_location ) ) unlink ( $save_location );
								touch ( $save_location );
								
								if ( ! $disable_crc_check ) {
									$file_mac = self::cbc_mac ( $file_contents_decrypted, $k, $iv );
									if ( array ( $file_mac[0] ^ $file_mac[1], $file_mac[2] ^ $file_mac[3] ) != $meta_mac ) $download_error = 1;
									else file_put_contents ( $save_location, $file_contents_decrypted );
								}
								else file_put_contents ( $save_location, $file_contents_decrypted );
								
								if ( ! is_file ( $save_location ) || ! filesize ( $save_location ) > 0 ) {
									@unlink ( $save_location );	// maybe the file was touched, but no content was written due to lack of space in the hdd
									$download_error = 2;
								}
							}
							else $download_error = 3;
							
						}
						else $download_error = 4;
						
					}
					else $download_error = 5;
					
				}
				else $download_error = 6;
				
			}
			else $download_error = 7;
			
			if ( $download_error !== null ) {
				$this -> download_error_code = $download_error;
				$this -> download_error_message = self::$downloadErrors[$download_error];
				return false;
			}
			
			$this -> download_path = $save_location;
			return $save_location;
		}
		
		public function getCleanFilename () {
			
			if ( ! empty ( $this -> download_filename ) && strlen ( $this -> download_filename ) > 0 ) {
				$clean = strtolower ( $this -> download_filename );
				$clean = preg_replace ( '/[^a-z0-9\.]/', '.', $clean );
				$clean = preg_replace ( '/\.\.+/', '.', $clean );
				return trim ( $clean, '.' );
			}
			
			return null;
			
		}
		
		private function api_req ( $req ) {
			$resp = self::curlpost ( 'https://g.api.mega.co.nz/cs?id=' .  ( $this -> seqno++ ) .  ( $this -> sid ? '&sid=' . $this -> sid : ''), json_encode ( array ( $req ) ) );
			$resp = json_decode ( $resp );
			return $resp[0];
		}
		
		private static function base64urldecode ( $data ) {
			$data .= substr ( '==', ( 2 - strlen ( $data ) * 3 ) % 4 );
			$data = str_replace ( array ( '-', '_', ',' ), array ( '+', '/', '' ), $data );
			return base64_decode ( $data );  
		}
		
		private static function str_to_a32 ( $b ) {
			$b = str_pad ( $b, 4 * ceil ( strlen ( $b ) / 4 ), '\0' );
			return array_values ( unpack ( 'N*', $b ) );
		}
		
		private static function a32_to_str ( $hex ) {
			return call_user_func_array ( 'pack', array_merge ( array ( 'N*' ), $hex ) );
		}

		private static function base64_to_a32 ( $s ) {
			return self::str_to_a32 ( self::base64urldecode ( $s ) );
		}
		
		private static function dec_attr ( $attr, $key ) {
			$attr = trim ( self::aes_cbc_decrypt ( $attr, self::a32_to_str ( $key ) ) );
			if ( substr ( $attr, 0, 6 ) != 'MEGA{"' ) return false;
			return json_decode ( substr ( $attr, 4 ) );
		}
		
		private static function aes_cbc_decrypt ( $data, $key ) {
			return @mcrypt_decrypt ( MCRYPT_RIJNDAEL_128, $key, $data, MCRYPT_MODE_CBC, '\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0' );
		}
		
		private static function aes_cbc_encrypt ( $data, $key ) {
			return @mcrypt_encrypt ( MCRYPT_RIJNDAEL_128, $key, $data, MCRYPT_MODE_CBC, '\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0' );
		}
		
		private static function aes_ctr_decrypt ( $data, $key, $iv ) {
			return @mcrypt_decrypt ( MCRYPT_RIJNDAEL_128, $key, $data, 'ctr', $iv );
		}
		
		private static function aes_cbc_encrypt_a32 ( $data, $key ) {
			return self::str_to_a32 ( self::aes_cbc_encrypt ( self::a32_to_str ( $data ), self::a32_to_str ( $key ) ) );
		}
		
		private static function cbc_mac ( $data, $k, $n ) {
			$padding_size = ( strlen ( $data ) % 16 ) == 0 ? 0 : 16 - strlen ( $data ) % 16;
			$data .= str_repeat ( '\0', $padding_size );

			$chunks = self::get_chunks ( strlen ( $data ) );
			$file_mac = array ( 0, 0, 0, 0 );

			foreach ( $chunks as $pos => $size ) {
				$chunk_mac = array ( $n[0], $n[1], $n[0], $n[1] );
				for ( $i = $pos; $i < $pos + $size; $i += 16 ) {
				  $block = self::str_to_a32 ( substr ( $data, $i, 16 ) );
				  $chunk_mac = array ( $chunk_mac[0] ^ $block[0], $chunk_mac[1] ^ $block[1], $chunk_mac[2] ^ $block[2], $chunk_mac[3] ^ $block[3] );
				  $chunk_mac = self::aes_cbc_encrypt_a32 ( $chunk_mac, $k );
				}
				$file_mac = array($file_mac[0] ^ $chunk_mac[0], $file_mac[1] ^ $chunk_mac[1], $file_mac[2] ^ $chunk_mac[2], $file_mac[3] ^ $chunk_mac[3]);
				$file_mac = self::aes_cbc_encrypt_a32 ( $file_mac, $k );
			}

			return $file_mac;
		}
		
		private static function get_chunks ( $size ) {
			$chunks = array();
			$p = $pp = 0;

			for ( $i = 1; $i <= 8 && $p < $size - $i * 0x20000; $i++ ) {
				$chunks[$p] = $i * 0x20000;
				$pp = $p;
				$p += $chunks[$p];
			}

			while ( $p < $size ) {
				$chunks[$p] = 0x100000;
				$pp = $p;
				$p += $chunks[$p];
			}

			$chunks[$pp] = ($size - $pp);
			if ( ! $chunks[$pp] ) unset ( $chunks[$pp] );

			return $chunks;
		}

		private static function curlpost ( $url, $data = null ) {
			$ch = curl_init ( $url );
			curl_setopt ( $ch, CURLOPT_RETURNTRANSFER, true );
			if ( ! empty ( $data ) ) {
				curl_setopt ( $ch, CURLOPT_POST, true );
				curl_setopt ( $ch, CURLOPT_POSTFIELDS, $data );
			}
			$resp = curl_exec ( $ch );
			curl_close ( $ch );
			
			if ( ! empty ( $resp ) ) return $resp;
			return false;
		}
		
	}
?>

UTILIZZO DELLA CLASSE ( esempio applicativo )

<?php
	require_once 'class.megaconz.php';									// la classe MF Php Mega.co.nz Downloader
	
	$temp_filename = md5 ( microtime () . rand ( 111111, 999999 ) );	// un nome file a caso
	$working_directory = '/directory/where/to/save/the/file/';			// la directory dove verra' salvato il file scaricato ( il nome del file e' stato calcolato sopra )
	$md = new mf_php_megaconz_downloader ( '##URL-MEGACONZ##' );		// istanza della classe per il download, passando l'url del file da scaricare
	if ( ( $saved_filename = $md -> download ( $working_directory, $temp_filename, true ) ) !== false && ( $save_as = $md -> getCleanFilename () ) !== false ) {
		// se il download e' corretto ( esiste, quindi, il file "$saved_filename", lo salviamo sempre nella directory "$working_directory", ma chiamandolo con il nome REALE del file su mega.co.nz con il metodo $md -> getCleanFilename ()
		rename ( $saved_filename, $working_directory . $save_as );
	}
	else echo $md -> download_error_message;	// stampiamo l'errore se qualcosa e' andato storto
	
	// DEBUG: stampiamo il contenuto della istanza della classe mf_php_megaconz_downloader
	print_r ( $md );
?>

Come avete potuto notare, con pochissime righe si può scaricare un file da mega.co.nz e salvarlo in locale, semplicemente partendo da una url pubblica di download. Ovviamente, la classe funziona solo e soltanto con i download pubblici, ovvero quelli che non sono stati resi "privati" dall'uploader originale di quel file.

Nota importante: Qualsiasi download da un servizio di hosting di file è "adattabile" ad una piccola classe di interfaccia per il download. Basta cercare in internet, o farmi un fischio tramite il modulo contattami per richiedermi una analisi :)