Advertisement
vituong585

Googel Driver Upload

Sep 28th, 2016
250
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
PHP 11.57 KB | None | 0 0
  1. #!/usr/bin/php
  2. <?php
  3.  
  4. /*******************************************************************************
  5.  * file:        google_drive_upload.php
  6.  * author:      Vô Ưu
  7.  * description: script to upload files to Google Drive
  8. *******************************************************************************/
  9.  
  10. // configuration
  11. //   google oauth
  12. $client_id = "your client ID here" ;
  13. $client_secret = "your client secret here" ;
  14. $refresh_token = "your refresh token here" ;
  15. //   chunk size (this HAS to be a multiple of 256KB so only change the last integer)
  16. $chunk_size = 256 * 1024 * 400 ; // this will upload files 100MB at a time
  17. //   miscellaneous
  18. $verbose = true ;  
  19. $file_binary = "/usr/bin/file" ; // used for detecting mime type
  20. //   md5
  21. $check_md5_after_upload = true ;
  22. $md5sum_binary = "/usr/bin/md5sum" ;
  23.  
  24.  
  25. // todo: grant URL follow thingy if it hasn't been done already
  26.  
  27. if( count($argv)<2 || count($argv)>3 || in_array("-h", $argv) || in_array("--help", $argv) ) {
  28.     echo "usage: {$argv[0]} <file_name> [folder_id]\n\n    where <file_name> is the full path to the file that you want to upload to Google Drive.\n      and [folder_id] is the the folder where you want to upload the file (optional, defaults to root)\n\n" ;
  29.     exit( 1 ) ;
  30. }
  31.  
  32. $file_name = $argv[1] ;
  33. if( !file_exists($file_name) ) {
  34.     echo "ERROR: {$file_name} is not found on the filesystem\n" ;
  35.     exit( 1 ) ;
  36. }
  37.  
  38. $mime_type = get_mime_type( $file_name ) ;
  39. if( $verbose ) { echo " > mime type detected: {$mime_type}\n" ; }
  40.  
  41. $folder_id = "" ;
  42. if( count($argv)>2 ) {
  43.     $folder_id = $argv[2] ;
  44. }
  45.  
  46. // retrieving current access token
  47. $access_token = get_access_token() ;
  48.  
  49. // we create the file that we will be uploading to with google
  50. if( $verbose ) { echo "> creating file with Google\n" ; }
  51. $location = create_google_file( $file_name ) ;
  52.  
  53. $file_size = filesize( $file_name ) ;
  54. if( $verbose ) { echo "> uploading {$file_name} to {$location}\n" ; }
  55. if( $verbose ) { echo ">   file size: " . (string)($file_size / pow(1024, 2)) . "MB\n" ; }
  56. if( $verbose ) { echo ">   chunk size: " . (string)($chunk_size / pow(1024, 2)) . "MB\n\n" ; }
  57.  
  58. $last_response_code = false ;
  59. $final_output = null ;
  60. $last_range = false ;
  61. $transaction_counter = 0 ;
  62. $average_upload_speed = 0 ;
  63. $do_exponential_backoff = false ;
  64. $exponential_backoff_counter = 0 ;
  65. while( $last_response_code===false || $last_response_code=='308' ) {
  66.     $transaction_counter++ ;
  67.     if( $verbose ) { echo "> request {$transaction_counter}\n" ; }
  68.  
  69.     if( $do_exponential_backoff ) {
  70.         $sleep_for = pow( 2, $exponential_backoff_counter ) ;
  71.         if( $verbose ) { echo ">    exponential backoff kicked in, sleeping for {$sleep_for} and a bit\n" ; }
  72.         sleep( $sleep_for ) ;
  73.         usleep( rand(0, 1000) ) ;
  74.         $exponential_backoff_counter++ ;
  75.         if( $exponential_backoff_counter>5 ) {
  76.             // we've waited too long as per Google's instructions
  77.             echo "ERROR: reached time limit of exponential backoff\n" ;
  78.             exit( 1 ) ;
  79.         }
  80.     }
  81.  
  82.     // determining what range is next
  83.     $range_start = 0 ;
  84.     $range_end = min( $chunk_size, $file_size - 1 ) ;
  85.     if( $last_range!==false ) {
  86.         $last_range = explode( '-', $last_range ) ;
  87.         $range_start = (int)$last_range[1] + 1 ;
  88.         $range_end = min( $range_start + $chunk_size, $file_size - 1 ) ;
  89.     }
  90.     if( $verbose ) { echo ">   range {$range_start}-{$range_end}/{$file_size}\n" ; }
  91.  
  92.     $ch = curl_init() ;
  93.     curl_setopt( $ch, CURLOPT_URL, "{$location}" ) ;
  94.     curl_setopt( $ch, CURLOPT_PORT , 443 ) ;
  95.     curl_setopt( $ch, CURLOPT_CUSTOMREQUEST, "PUT" ) ;
  96.     curl_setopt( $ch, CURLOPT_BINARYTRANSFER, 1 ) ;
  97.     // grabbing the data to send
  98.     $to_send = file_get_contents( $file_name, false, NULL, $range_start, ($range_end - $range_start + 1) ) ;
  99.     curl_setopt( $ch, CURLOPT_POSTFIELDS, $to_send ) ;
  100.     curl_setopt( $ch, CURLOPT_RETURNTRANSFER, 1) ;
  101.     curl_setopt( $ch, CURLOPT_HEADER, true ) ;
  102.     curl_setopt( $ch, CURLOPT_HTTPHEADER, array("Authorization: Bearer {$access_token}",
  103.                                                 "Content-Length: " . (string)($range_end - $range_start + 1),
  104.                                                 "Content-Type: {$mime_type}",
  105.                                                 "Content-Range: bytes {$range_start}-{$range_end}/{$file_size}") ) ;
  106.     $response = parse_response( curl_exec($ch) ) ;
  107.     $post_transaction_info = curl_getinfo( $ch ) ;
  108.     curl_close( $ch ) ;
  109.  
  110.     $do_exponential_backoff = false ;
  111.     if( isset($response['code']) ) {
  112.         // checking for expired credentials
  113.         if( $response['code']=="401" ) { // todo: make sure that we also got an invalid credential response
  114.             if( $verbose ) { echo ">   access token expired, getting a new one\n" ; }
  115.             $access_token = get_access_token( true ) ;
  116.             $last_response_code = false ;
  117.         } else if( $response['code']=="308" ) {
  118.             $last_response_code = $response['code'] ;
  119.             $last_range = $response['headers']['range'] ;
  120.             // todo: verify x-range-md5 header to be sure, although I can't seem to find what x-range-md5 is a hash of exactly...
  121.             $exponential_backoff_counter = 0 ;
  122.         } else if( $response['code']=="503" ) { // Google's letting us know we should retry
  123.             $do_exponential_backoff = true ;
  124.             $last_response_code = false ;
  125.         } else if( $response['code']=="200" ) { // we are done!
  126.             $last_response_code = $response['code'] ;
  127.             $final_output = $response ;
  128.         } else {
  129.             echo "ERROR: I have no idea what to do so here's a variable dump & have fun figuring it out.\n" ;
  130.             echo "post_transaction_info\n" ;
  131.             print_r( $post_transaction_info ) ;
  132.             echo "response\n" ;
  133.             print_r( $response ) ;
  134.             exit( 1 ) ;
  135.         }
  136.  
  137.         $average_upload_speed += (int)$post_transaction_info['speed_upload'] ;
  138.         if( $verbose ) { echo ">   uploaded {$post_transaction_info['size_upload']}B\n" ; }
  139.  
  140.     } else {
  141.         $do_exponential_backoff = true ;
  142.         $last_response_code = false ;
  143.     }
  144. }
  145.  
  146. if( $last_response_code!="200" ) {
  147.     echo "ERROR: there's no way we should reach this point\n" ;
  148.     exit( 1 ) ;
  149. }
  150. if( $verbose ) { echo "\n> all done!\n" ; }
  151. $average_upload_speed /= $transaction_counter ;
  152. if( $verbose ) { echo "\n> average upload speed: " . (string)($average_upload_speed / pow(1024, 2)) . "MB/s\n" ; }
  153.  
  154. $final_output =json_decode( $final_output['body'] ) ;
  155.  
  156. if( $check_md5_after_upload ) {
  157.     if( $verbose ) { echo "> md5 hash verification " ; }
  158.     $result = exec( "{$md5sum_binary} {$file_name}" ) ;
  159.     $result = trim( $result ) ;
  160.     $result = explode( " ", $result ) ;
  161.     $result = $result[0] ;
  162.     if( $result!=$final_output->md5Checksum ) {
  163.         if( $verbose ) { echo "FAIL\n" ; }
  164.         echo "ERROR: md5 mismatch; local:{$result}, google:{$final_output->md5Checksum}\n" ;
  165.         exit( 1 ) ;
  166.     } else {
  167.         if( $verbose ) { echo "OK\n" ; }
  168.     }
  169. }
  170.  
  171. echo $final_output->selfLink, "\n" ;
  172.  
  173. // we made it
  174. exit( 0 )  ;
  175.  
  176.  
  177. function get_mime_type( $file_name ) {
  178.     global $file_binary ;
  179.  
  180.     $result = exec( "{$file_binary} -i -b {$file_name}" ) ;
  181.     $result = trim( $result ) ;
  182.     $result = explode( ";", $result ) ;
  183.     $result = $result[0] ;
  184.  
  185.     return $result ;
  186. }
  187.  
  188.  
  189. function parse_response( $raw_data ) {
  190.     $parsed_response = array( 'code'=>-1, 'headers'=>array(), 'body'=>"" ) ;
  191.  
  192.     $raw_data = explode( "\r\n", $raw_data ) ;
  193.  
  194.     $parsed_response['code'] = explode( " ", $raw_data[0] ) ;
  195.     $parsed_response['code'] = $parsed_response['code'][1] ;
  196.  
  197.     for( $i=1 ; $i<count($raw_data) ; $i++ ) {
  198.         $raw_datum = $raw_data[$i] ;
  199.  
  200.         $raw_datum = trim( $raw_datum ) ;
  201.         if( $raw_datum!="" ) {
  202.             if( substr_count($raw_datum, ':')>=1 ) {
  203.                 $raw_datum = explode( ':', $raw_datum, 2 ) ;
  204.                 $parsed_response['headers'][strtolower($raw_datum[0])] = trim( $raw_datum[1] ) ;
  205.             }  else {
  206.                 echo "ERROR: we're in the headers section of parsing an HTTP section and no colon was found for line: {$raw_datum}\n" ;
  207.                 exit( 1 ) ;
  208.             }
  209.         } else {
  210.             // we've moved to the body section
  211.             if( ($i+1)<count($raw_data) ) {
  212.                 for( $j=($i+1) ; $j<count($raw_data) ; $j++ ) {
  213.                     $parsed_response['body'] .= $raw_data[$j] . "\n" ;
  214.                 }
  215.             }
  216.  
  217.             // we don't need to continue the $i loop
  218.             break ;
  219.         }
  220.     }
  221.  
  222.     return $parsed_response ;
  223. }
  224.  
  225.  
  226. function get_access_token( $force_refresh=false ) {
  227.     global $client_id, $client_secret, $refresh_token, $verbose ;
  228.  
  229.     if( $verbose ) { echo "> retrieving access token\n" ; }
  230.  
  231.     $token_filename = "/tmp/access_token_" . md5( $client_id . $client_secret . $refresh_token ) ;
  232.     $access_token = "" ;
  233.     if( !file_exists($token_filename) || $force_refresh===true ) {
  234.         // no previous access token, let's get one
  235.         if( $verbose ) { echo ">   getting new one\n" ; }
  236.  
  237.         $ch = curl_init() ;
  238.         curl_setopt( $ch, CURLOPT_URL, "https://accounts.google.com/o/oauth2/token" ) ;
  239.         curl_setopt( $ch, CURLOPT_PORT , 443 ) ;
  240.         curl_setopt( $ch, CURLOPT_POST, 1 ) ;
  241.         curl_setopt( $ch, CURLOPT_POSTFIELDS, "client_id={$client_id}&client_secret={$client_secret}&refresh_token={$refresh_token}&grant_type=refresh_token" ) ;
  242.         curl_setopt( $ch, CURLOPT_RETURNTRANSFER, 1 ) ;
  243.         curl_setopt( $ch, CURLOPT_HEADER, true ) ;
  244.         curl_setopt( $ch, CURLOPT_HTTPHEADER, array('Content-Type: application/x-www-form-urlencoded') ) ;
  245.  
  246.         $response = curl_exec( $ch ) ;
  247.         $response = parse_response( $response ) ;
  248.  
  249.         // todo: make sure that we got a valid response before retrieving the access token from it
  250.  
  251.         $access_token = json_decode( $response['body'] ) ;
  252.         $access_token = $access_token->access_token ;
  253.         file_put_contents( $token_filename, $access_token ) ;
  254.     } else {
  255.         // we already have something cached, with some luck it's still valid
  256.         $access_token = file_get_contents( $token_filename ) ;
  257.         if( $verbose ) { echo ">   from cache\n" ; }
  258.     }
  259.  
  260.     if( $access_token=="" ) {
  261.         echo "ERROR: problems getting an access token\n" ;
  262.         exit( 1 ) ;
  263.     }
  264.  
  265.     return  $access_token ;
  266. }
  267.  
  268.  
  269. function create_google_file ( $file_name ) {
  270.     global $access_token, $folder_id, $mime_type ;
  271.  
  272.     // todo: make mimeType universal
  273.     if( $folder_id=="" ) {
  274.         $post_fields = "{\"title\":\"{$file_name}\",
  275.                          \"mimeType\":\"{$mime_type}\"}" ;
  276.     } else {
  277.         $post_fields = "{\"title\":\"{$file_name}\",
  278.                          \"mimeType\":\"{$mime_type}\",
  279.                          \"parents\": [{\"kind\":\"drive#fileLink\",\"id\":\"{$folder_id}\"}]}" ;
  280.     }
  281.  
  282.     $ch = curl_init() ;
  283.     curl_setopt( $ch, CURLOPT_URL, "https://www.googleapis.com/upload/drive/v2/files?uploadType=resumable" ) ;
  284.     curl_setopt( $ch, CURLOPT_PORT , 443 ) ;
  285.     curl_setopt( $ch, CURLOPT_POST, 1 ) ;
  286.     curl_setopt( $ch, CURLOPT_POSTFIELDS, $post_fields ) ;
  287.     curl_setopt( $ch, CURLOPT_RETURNTRANSFER, 1 ) ;
  288.     curl_setopt( $ch, CURLOPT_HEADER, true ) ;
  289.     curl_setopt( $ch, CURLOPT_HTTPHEADER, array("Authorization: Bearer {$access_token}",
  290.                                                 "Content-Length: " . strlen($post_fields),
  291.                                                 "X-Upload-Content-Type: {$mime_type}",
  292.                                                 "X-Upload-Content-Length: " . filesize($file_name),
  293.                                                 "Content-Type: application/json; charset=UTF-8") ) ;
  294.     $response = curl_exec( $ch ) ;
  295.     $response = parse_response( $response ) ;
  296.  
  297.     // access token expired, let's get a new one and try again
  298.     if( $response['code']=="401" ) { // todo: make sure that we also got an invalid credential response
  299.         $access_token = get_access_token( true ) ;
  300.         return create_google_file( $file_name ) ; // todo: implement recursion depth limit so we don't rescurse to hell
  301.     }
  302.  
  303.     // error checking
  304.     if( $response['code']!="200" ) {
  305.         echo "ERROR: could not create resumable file\n" ;
  306.         print_r( $response ) ;
  307.         exit( 1 ) ;
  308.     }
  309.     if( !isset($response['headers']['location']) ) {
  310.         echo "ERROR: not location header gotten back\n" ;
  311.         print_r( $response ) ;
  312.         exit( 1 ) ;
  313.     }
  314.    
  315.     return $response['headers']['location'] ;
  316. }
  317.  
  318. ?>
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement