diff --git a/CMakeLists.txt b/CMakeLists.txt index f86b762..e2f363a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -16,6 +16,7 @@ add_executable( grive src/protocol/HTTP.cc src/protocol/Json.cc src/protocol/OAuth2.cc + src/util/Crypt.cc src/util/DateTime.cc src/util/OS.cc ) diff --git a/src/drive/Drive.cc b/src/drive/Drive.cc index 37e4eb1..53f7829 100644 --- a/src/drive/Drive.cc +++ b/src/drive/Drive.cc @@ -22,19 +22,15 @@ #include "protocol/HTTP.hh" #include "protocol/Json.hh" #include "protocol/OAuth2.hh" +#include "util/Crypt.hh" #include "util/DateTime.hh" #include "util/OS.hh" -// dependent libraries -#include - // standard C++ library #include #include #include -#include #include -#include // for debugging only #include @@ -43,30 +39,6 @@ namespace gr { const std::string root_url = "https://docs.google.com/feeds/default/private/full" ; -std::string MD5( std::streambuf *file ) -{ - char buf[64 * 1024] ; - EVP_MD_CTX md ; - EVP_MD_CTX_init( &md ); - EVP_DigestInit_ex( &md, EVP_md5(), 0 ) ; - - std::size_t count = 0 ; - while ( (count = file->sgetn( buf, sizeof(buf) )) > 0 ) - { - EVP_DigestUpdate( &md, buf, count ) ; - } - - unsigned int md5_size = EVP_MAX_MD_SIZE ; - unsigned char md5[EVP_MAX_MD_SIZE] ; - EVP_DigestFinal_ex( &md, md5, &md5_size ) ; - - std::ostringstream ss ; - for ( unsigned int i = 0 ; i < md5_size ; i++ ) - ss << std::hex << std::setw(2) << std::setfill('0') << static_cast(md5[i]) ; - - return ss.str() ; -} - Drive::Drive( OAuth2& auth ) : m_auth( auth ), m_root( ".", "https://docs.google.com/feeds/default/private/full/folder%3Aroot" ) @@ -75,7 +47,7 @@ Drive::Drive( OAuth2& auth ) : m_http_hdr.push_back( "GData-Version: 3.0" ) ; - Json resp = Json::Parse(HttpGet( root_url + "?alt=json&showfolders=true", m_http_hdr )) ; + Json resp = Json::Parse( http::Get( root_url + "?alt=json&showfolders=true", m_http_hdr )) ; Json::Array entries = resp["feed"]["entry"].As() ; ConstructDirTree( entries ) ; @@ -182,8 +154,8 @@ void Drive::UpdateFile( const Json& entry ) if ( entry.Has( "docs$filename" ) ) { // use title as the filename - std::string filename = entry["docs$filename"]["$t"].As() ; - std::string url = entry["content"]["src"].As() ; + std::string filename = entry["docs$filename"]["$t"].Get() ; + std::string url = entry["content"]["src"].Get() ; std::string parent_href = Parent( entry ) ; bool changed = true ; @@ -204,7 +176,7 @@ void Drive::UpdateFile( const Json& entry ) if ( ifile && entry.Has("docs$md5Checksum") ) { std::string remote_md5 = entry["docs$md5Checksum"]["$t"].As() ; - std::string local_md5 = MD5( ifile.rdbuf() ) ; + std::string local_md5 = crypt::MD5( ifile.rdbuf() ) ; std::transform( remote_md5.begin(), remote_md5.end(), remote_md5.begin(), tolower ) ; if ( local_md5 == remote_md5 ) @@ -221,7 +193,7 @@ void Drive::UpdateFile( const Json& entry ) if ( remote > local ) { std::cout << "downloading " << path << std::endl ; - HttpGetFile( url, path, m_http_hdr ) ; + http::GetFile( url, path, m_http_hdr ) ; os::SetFileTime( path, remote ) ; } else @@ -241,7 +213,7 @@ void Drive::UploadFile( const Json& entry ) "http://schemas.google.com/g/2005#resumable-edit-media" )["href"] ; std::cout << resume_link.As() << std::endl ; - std::string resp = HttpPut( resume_link.As(), "" ) ; + std::string resp = http::Put( resume_link.Get(), "", m_http_hdr ) ; std::cout << "resp " << resp ; } diff --git a/src/protocol/HTTP.cc b/src/protocol/HTTP.cc index ed38cdb..452fddc 100644 --- a/src/protocol/HTTP.cc +++ b/src/protocol/HTTP.cc @@ -31,19 +31,9 @@ #include #include -namespace gr { +namespace { -HttpException::HttpException( int curl_code, int http_code, const char *err_buf ) - : runtime_error( Format( curl_code, http_code, err_buf ) ) -{ -} - -std::string HttpException::Format( int curl_code, int http_code, const char *err_buf ) -{ - std::ostringstream ss ; - ss << "CURL code = " << curl_code << " HTTP code = " << http_code << " (" << err_buf << ")" ; - return ss.str() ; -} +using namespace gr::http ; // libcurl callback to append to a string std::size_t WriteCallback( char *data, size_t size, size_t nmemb, std::string *resp ) @@ -109,16 +99,32 @@ void DoCurl( CURL *curl ) if ( curl_code != CURLE_OK ) { - throw HttpException( curl_code, http_code, error_buf ) ; + throw Exception( curl_code, http_code, error_buf ) ; } else if (http_code >= 400 ) { std::cout << "http error " << http_code << std::endl ; - throw HttpException( curl_code, http_code, error_buf ) ; + throw Exception( curl_code, http_code, error_buf ) ; } } -std::string HttpGet( const std::string& url, const Headers& hdr ) +} // end of local namespace + +namespace gr { namespace http { + +Exception::Exception( int curl_code, int http_code, const char *err_buf ) + : runtime_error( Format( curl_code, http_code, err_buf ) ) +{ +} + +std::string Exception::Format( int curl_code, int http_code, const char *err_buf ) +{ + std::ostringstream ss ; + ss << "CURL code = " << curl_code << " HTTP code = " << http_code << " (" << err_buf << ")" ; + return ss.str() ; +} + +std::string Get( const std::string& url, const Headers& hdr ) { std::string resp ; CURL *curl = InitCurl( url, &resp, hdr ) ; @@ -127,7 +133,7 @@ std::string HttpGet( const std::string& url, const Headers& hdr ) return resp; } -void HttpGetFile( +void GetFile( const std::string& url, const std::string& filename, const Headers& hdr ) @@ -141,7 +147,7 @@ void HttpGetFile( DoCurl( curl ) ; } -void HttpGetFile( +void GetFile( const std::string& url, const std::string& filename, std::string& md5sum, @@ -149,7 +155,7 @@ void HttpGetFile( { } -std::string HttpPostData( const std::string& url, const std::string& data, const Headers& hdr ) +std::string PostData( const std::string& url, const std::string& data, const Headers& hdr ) { std::string resp ; CURL *curl = InitCurl( url, &resp, hdr ) ; @@ -164,13 +170,13 @@ std::string HttpPostData( const std::string& url, const std::string& data, const return resp; } -std::string HttpPostFile( const std::string& url, const std::string& filename, const Headers& hdr ) +std::string PostFile( const std::string& url, const std::string& filename, const Headers& hdr ) { std::string resp ; return resp; } -std::string HttpPut( +std::string Put( const std::string& url, const std::string& data, const Headers& hdr ) @@ -216,4 +222,4 @@ std::string Unescape( const std::string& str ) return result ; } -} +} } // end of namespace diff --git a/src/protocol/HTTP.hh b/src/protocol/HTTP.hh index 1b7ab05..669d57c 100644 --- a/src/protocol/HTTP.hh +++ b/src/protocol/HTTP.hh @@ -23,44 +23,46 @@ #include #include -namespace gr +namespace gr { namespace http { typedef std::vector Headers ; - std::string HttpGet( const std::string& url, const Headers& hdr = Headers() ) ; - void HttpGetFile( + std::string Get( const std::string& url, const Headers& hdr = Headers() ) ; + void GetFile( const std::string& url, const std::string& filename, const Headers& hdr = Headers() ) ; - void HttpGetFile( + void GetFile( const std::string& url, const std::string& filename, std::string& md5sum, const Headers& hdr = Headers() ) ; - std::string HttpPostData( + std::string PostData( const std::string& url, const std::string& data, const Headers& hdr = Headers() ) ; - std::string HttpPostFile( + std::string PostFile( const std::string& url, const std::string& filename, const Headers& hdr = Headers() ) ; - std::string HttpPut( + std::string Put( const std::string& url, + const std::string& data, const Headers& hdr = Headers() ) ; std::string Escape( const std::string& str ) ; std::string Unescape( const std::string& str ) ; - class HttpException : public std::runtime_error + class Exception : public std::runtime_error { public : - HttpException( int curl_code, int http_code, const char *err_buf ) ; + Exception( int curl_code, int http_code, const char *err_buf ) ; private : static std::string Format( int curl_code, int http_code, const char *err_buf ) ; } ; -} \ No newline at end of file + +} } // end of namespace diff --git a/src/protocol/Json.cc b/src/protocol/Json.cc index 0fcc12a..8c691f4 100644 --- a/src/protocol/Json.cc +++ b/src/protocol/Json.cc @@ -138,9 +138,18 @@ void Json::Add( const std::string& key, const Json& json ) ::json_object_object_add( m_json, key.c_str(), json.m_json ) ; } -Json::Proxy Json::As() const +template <> +bool Json::As() const { - return Proxy( *this ) ; + assert( m_json != 0 ) ; + return ::json_object_get_boolean( m_json ) ; +} + +template <> +bool Json::Is() const +{ + assert( m_json != 0 ) ; + return ::json_object_is_type( m_json, json_type_boolean ) ; } template <> @@ -248,4 +257,34 @@ bool Json::FindInArray( const std::string& key, const std::string& value, Json& } } +Json::Proxy Json::Get() const +{ + return Proxy( *this ) ; +} + +Json::Proxy::operator bool() const +{ + return referring.As() ; +} + +Json::Proxy::operator int() const +{ + return referring.As() ; +} + +Json::Proxy::operator Object() const +{ + return referring.As() ; +} + +Json::Proxy::operator Array() const +{ + return referring.As() ; +} + +Json::Proxy::operator std::string() const +{ + return referring.As() ; +} + } diff --git a/src/protocol/Json.hh b/src/protocol/Json.hh index 5417526..3a9df2f 100644 --- a/src/protocol/Json.hh +++ b/src/protocol/Json.hh @@ -56,9 +56,13 @@ public : { const Json& referring ; explicit Proxy( const Json& j ) : referring( j ) { } - template operator T() const { return referring.As() ; } + operator bool() const ; + operator int() const ; + operator Object() const ; + operator Array() const ; + operator std::string() const ; } ; - Proxy As() const ; + Proxy Get() const ; template bool Is() const ; diff --git a/src/protocol/OAuth2.cc b/src/protocol/OAuth2.cc index 7a77283..0e86db9 100644 --- a/src/protocol/OAuth2.cc +++ b/src/protocol/OAuth2.cc @@ -57,7 +57,7 @@ void OAuth2::Auth( const std::string& auth_code ) "&redirect_uri=" + "urn:ietf:wg:oauth:2.0:oob" + "&grant_type=authorization_code" ; - Json resp = Json::Parse( HttpPostData( token_url, post ) ) ; + Json resp = Json::Parse( http::PostData( token_url, post ) ) ; m_access = resp["access_token"].As() ; m_refresh = resp["refresh_token"].As() ; } @@ -66,6 +66,8 @@ std::string OAuth2::MakeAuthURL( const std::string& client_id, const std::string& state ) { + using gr::http::Escape ; + return "https://accounts.google.com/o/oauth2/auth" "?scope=" + Escape( "https://www.googleapis.com/auth/userinfo.email" ) + "+" + @@ -86,7 +88,7 @@ void OAuth2::Refresh( ) "&client_secret=" + m_client_secret + "&grant_type=refresh_token" ; - Json resp = Json::Parse( HttpPostData( token_url, post ) ) ; + Json resp = Json::Parse( http::PostData( token_url, post ) ) ; m_access = resp["access_token"].As() ; } diff --git a/src/util/Crypt.cc b/src/util/Crypt.cc new file mode 100644 index 0000000..35deeb0 --- /dev/null +++ b/src/util/Crypt.cc @@ -0,0 +1,55 @@ +/* + grive: an GPL program to sync a local directory with Google Drive + Copyright (C) 2012 Wan Wai Ho + + 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 version 2 + of the License. + + 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. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + +#include "Crypt.hh" + +#include +#include + +// dependent libraries +#include + +namespace gr { namespace crypt { + +std::string MD5( std::streambuf *file ) +{ + char buf[64 * 1024] ; + EVP_MD_CTX md ; + EVP_MD_CTX_init( &md ); + EVP_DigestInit_ex( &md, EVP_md5(), 0 ) ; + + std::size_t count = 0 ; + while ( (count = file->sgetn( buf, sizeof(buf) )) > 0 ) + { + EVP_DigestUpdate( &md, buf, count ) ; + } + + unsigned int md5_size = EVP_MAX_MD_SIZE ; + unsigned char md5[EVP_MAX_MD_SIZE] ; + EVP_DigestFinal_ex( &md, md5, &md5_size ) ; + + // format the MD5 string + std::ostringstream ss ; + for ( unsigned int i = 0 ; i < md5_size ; i++ ) + ss << std::hex << std::setw(2) << std::setfill('0') << static_cast(md5[i]) ; + + return ss.str() ; +} + +} } // end of namespaces diff --git a/src/util/Crypt.hh b/src/util/Crypt.hh new file mode 100644 index 0000000..e7f0293 --- /dev/null +++ b/src/util/Crypt.hh @@ -0,0 +1,32 @@ +/* + grive: an GPL program to sync a local directory with Google Drive + Copyright (C) 2012 Wan Wai Ho + + 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 version 2 + of the License. + + 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. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + +#pragma once + +#include +#include + +namespace gr { + +namespace crypt +{ + std::string MD5( std::streambuf *file ) ; +} + +} // end of namespace gr