diff --git a/Net/samples/TwitterClient/src/TweetApp.cpp b/Net/samples/TwitterClient/src/TweetApp.cpp index b3a1d79af..0ab40214c 100644 --- a/Net/samples/TwitterClient/src/TweetApp.cpp +++ b/Net/samples/TwitterClient/src/TweetApp.cpp @@ -1,11 +1,11 @@ // // TwitterApp.cpp // -// $Id: //poco/1.4/Net/samples/TwitterClient/src/TweetApp.cpp#1 $ +// $Id: //poco/1.4/Net/samples/TwitterClient/src/TweetApp.cpp#2 $ // // A very simple command-line Twitter client. // -// Copyright (c) 2009, Applied Informatics Software Engineering GmbH. +// Copyright (c) 2009-2013, Applied Informatics Software Engineering GmbH. // and Contributors. // // Permission is hereby granted, free of charge, to any person or organization @@ -69,25 +69,39 @@ protected: .callback(OptionCallback(this, &TweetApp::handleHelp))); options.addOption( - Option("username", "u", "specify the Twitter user/account name") + Option("message", "m", "specify the status message to post") .required(true) .repeatable(false) - .argument("account") - .callback(OptionCallback(this, &TweetApp::handleUsername))); - - options.addOption( - Option("password", "p", "specify the Twitter password") - .required(true) - .repeatable(false) - .argument("password") - .callback(OptionCallback(this, &TweetApp::handlePassword))); - - options.addOption( - Option("message", "m", "specify the status message") - .required(false) - .repeatable(true) .argument("message") .callback(OptionCallback(this, &TweetApp::handleMessage))); + + options.addOption( + Option("ckey", "c", "specify the Twitter consumer key") + .required(true) + .repeatable(false) + .argument("consumer key") + .callback(OptionCallback(this, &TweetApp::handleConsumerKey))); + + options.addOption( + Option("csecret", "s", "specify the Twitter consumer secret") + .required(true) + .repeatable(false) + .argument("consumer secret") + .callback(OptionCallback(this, &TweetApp::handleConsumerSecret))); + + options.addOption( + Option("token", "t", "specify the Twitter access token") + .required(false) + .repeatable(true) + .argument("access token") + .callback(OptionCallback(this, &TweetApp::handleAccessToken))); + + options.addOption( + Option("tsecret", "S", "specify the Twitter access token secret") + .required(false) + .repeatable(true) + .argument("access token secret") + .callback(OptionCallback(this, &TweetApp::handleAccessTokenSecret))); } void handleHelp(const std::string& name, const std::string& value) @@ -96,21 +110,31 @@ protected: stopOptionsProcessing(); } - void handleUsername(const std::string& name, const std::string& value) + void handleConsumerKey(const std::string& name, const std::string& value) { - _username = value; + _consumerKey = value; } - void handlePassword(const std::string& name, const std::string& value) + void handleConsumerSecret(const std::string& name, const std::string& value) { - _password = value; + _consumerSecret = value; + } + + void handleAccessToken(const std::string& name, const std::string& value) + { + _accessToken = value; + } + + void handleAccessTokenSecret(const std::string& name, const std::string& value) + { + _accessTokenSecret = value; } void handleMessage(const std::string& name, const std::string& value) { try { - _twitter.login(_username, _password); + _twitter.login(_consumerKey, _consumerSecret, _accessToken, _accessTokenSecret); Poco::Int64 statusId = _twitter.update(value); std::cout << statusId << std::endl; } @@ -136,8 +160,10 @@ protected: } private: - std::string _username; - std::string _password; + std::string _consumerKey; + std::string _consumerSecret; + std::string _accessToken; + std::string _accessTokenSecret; Twitter _twitter; int _status; }; diff --git a/Net/samples/TwitterClient/src/Twitter.cpp b/Net/samples/TwitterClient/src/Twitter.cpp index 3eb98d400..27866df4b 100644 --- a/Net/samples/TwitterClient/src/Twitter.cpp +++ b/Net/samples/TwitterClient/src/Twitter.cpp @@ -1,11 +1,11 @@ // // Twitter.cpp // -// $Id: //poco/1.4/Net/samples/TwitterClient/src/Twitter.cpp#1 $ +// $Id: //poco/1.4/Net/samples/TwitterClient/src/Twitter.cpp#2 $ // // A C++ implementation of a Twitter client based on the POCO Net library. // -// Copyright (c) 2009, Applied Informatics Software Engineering GmbH. +// Copyright (c) 2009-2013, Applied Informatics Software Engineering GmbH. // and Contributors. // // Permission is hereby granted, free of charge, to any person or organization @@ -37,14 +37,22 @@ #include "Poco/Net/HTTPRequest.h" #include "Poco/Net/HTTPResponse.h" #include "Poco/Net/HTTPBasicCredentials.h" -#include "Poco/DOM/DOMParser.h" -#include "Poco/DOM/NodeList.h" -#include "Poco/SAX/InputSource.h" +#include "Poco/Util/JSONConfiguration.h" #include "Poco/URI.h" +#include "Poco/SHA1Engine.h" +#include "Poco/HMACEngine.h" +#include "Poco/Base64Encoder.h" +#include "Poco/RandomStream.h" +#include "Poco/Timestamp.h" #include "Poco/NumberParser.h" +#include "Poco/NumberFormatter.h" +#include "Poco/Format.h" +#include "Poco/StreamCopier.h" +#include +#include -const std::string Twitter::TWITTER_URI("http://twitter.com/statuses/"); +const std::string Twitter::TWITTER_URI("http://api.twitter.com/1.1/statuses/"); Twitter::Twitter(): @@ -64,10 +72,12 @@ Twitter::~Twitter() } -void Twitter::login(const std::string& username, const std::string& password) +void Twitter::login(const std::string& consumerKey, const std::string& consumerSecret, const std::string& token, const std::string& tokenSecret) { - _username = username; - _password = password; + _consumerKey = consumerKey; + _consumerSecret = consumerSecret; + _token = token; + _tokenSecret = tokenSecret; } @@ -75,29 +85,22 @@ Poco::Int64 Twitter::update(const std::string& status) { Poco::Net::HTMLForm form; form.set("status", status); - Poco::AutoPtr pResult = invoke(Poco::Net::HTTPRequest::HTTP_POST, "update", form); - Poco::AutoPtr pList = pResult->getElementsByTagName("id"); - if (pList->length() > 0) - { - return Poco::NumberParser::parse64(pList->item(0)->innerText()); - } - else return 0; + Poco::AutoPtr pResult = invoke(Poco::Net::HTTPRequest::HTTP_POST, "update", form); + return pResult->getInt64("id", 0); } -Poco::AutoPtr Twitter::invoke(const std::string& httpMethod, const std::string& twitterMethod, Poco::Net::HTMLForm& form) +Poco::AutoPtr Twitter::invoke(const std::string& httpMethod, const std::string& twitterMethod, Poco::Net::HTMLForm& form) { // Create the request URI. - // We use the XML version of the Twitter API. - Poco::URI uri(_uri + twitterMethod + ".xml"); - std::string path(uri.getPath()); + // We use the JSON version of the Twitter API. + Poco::URI uri(_uri + twitterMethod + ".json"); Poco::Net::HTTPClientSession session(uri.getHost(), uri.getPort()); - Poco::Net::HTTPRequest req(httpMethod, path, Poco::Net::HTTPMessage::HTTP_1_1); + Poco::Net::HTTPRequest req(httpMethod, uri.getPath(), Poco::Net::HTTPMessage::HTTP_1_1); - // Add username and password (HTTP basic authentication) to the request. - Poco::Net::HTTPBasicCredentials cred(_username, _password); - cred.authenticate(req); + // Sign request + sign(req, form, uri.toString()); // Send the request. form.prepareSubmit(req); @@ -108,28 +111,109 @@ Poco::AutoPtr Twitter::invoke(const std::string& httpMethod Poco::Net::HTTPResponse res; std::istream& rs = session.receiveResponse(res); - // Create a DOM document from the response. - Poco::XML::DOMParser parser; - parser.setFeature(Poco::XML::DOMParser::FEATURE_FILTER_WHITESPACE, true); - parser.setFeature(Poco::XML::XMLReader::FEATURE_NAMESPACES, false); - Poco::XML::InputSource source(rs); - Poco::AutoPtr pDoc = parser.parse(&source); - - // If everything went fine, return the XML document. - // Otherwise look for an error message in the XML document. + Poco::AutoPtr pResult = new Poco::Util::JSONConfiguration(rs); + + // If everything went fine, return the JSON document. + // Otherwise throw an exception. if (res.getStatus() == Poco::Net::HTTPResponse::HTTP_OK) { - return pDoc; + return pResult; } else { - std::string error(res.getReason()); - Poco::AutoPtr pList = pDoc->getElementsByTagName("error"); - if (pList->length() > 0) - { - error += ": "; - error += pList->item(0)->innerText(); - } - throw Poco::ApplicationException("Twitter Error", error); + throw Poco::ApplicationException("Twitter Error", pResult->getString("errors[0].message", "")); } } + + +void Twitter::sign(Poco::Net::HTTPRequest& request, const Poco::Net::HTMLForm& params, const std::string& uri) const +{ + std::string nonce(createNonce()); + std::string timestamp(Poco::NumberFormatter::format(Poco::Timestamp().epochTime())); + std::string signature(createSignature(request, params, uri, nonce, timestamp)); + std::string authorization( + Poco::format( + "OAuth" + " oauth_consumer_key=\"%s\"," + " oauth_nonce=\"%s\"," + " oauth_signature=\"%s\"," + " oauth_signature_method=\"HMAC-SHA1\"," + " oauth_timestamp=\"%s\"," + " oauth_token=\"%s\"," + " oauth_version=\"1.0\"", + percentEncode(_consumerKey), + percentEncode(nonce), + percentEncode(signature), + timestamp, + percentEncode(_token) + ) + ); + request.set("Authorization", authorization); +} + + +std::string Twitter::createNonce() const +{ + std::ostringstream base64Nonce; + Poco::Base64Encoder base64Encoder(base64Nonce); + Poco::RandomInputStream randomStream; + for (int i = 0; i < 32; i++) + { + base64Encoder.put(randomStream.get()); + } + std::string nonce = base64Nonce.str(); + return Poco::translate(nonce, "+/=", ""); +} + + +std::string Twitter::createSignature(Poco::Net::HTTPRequest& request, const Poco::Net::HTMLForm& params, const std::string& uri, const std::string& nonce, const std::string& timestamp) const +{ + std::map paramsMap; + paramsMap["oauth_consumer_key"] = percentEncode(_consumerKey); + paramsMap["oauth_nonce"] = percentEncode(nonce); + paramsMap["oauth_signature_method"] = "HMAC-SHA1"; + paramsMap["oauth_timestamp"] = timestamp; + paramsMap["oauth_token"] = percentEncode(_token); + paramsMap["oauth_version"] = "1.0"; + for (Poco::Net::HTMLForm::ConstIterator it = params.begin(); it != params.end(); ++it) + { + paramsMap[percentEncode(it->first)] = percentEncode(it->second); + } + + std::string paramsString; + for (std::map::const_iterator it = paramsMap.begin(); it != paramsMap.end(); ++it) + { + if (it != paramsMap.begin()) paramsString += '&'; + paramsString += it->first; + paramsString += "="; + paramsString += it->second; + } + + std::string signatureBase = request.getMethod(); + signatureBase += '&'; + signatureBase += percentEncode(uri); + signatureBase += '&'; + signatureBase += percentEncode(paramsString); + + std::string signingKey; + signingKey += percentEncode(_consumerSecret); + signingKey += '&'; + signingKey += percentEncode(_tokenSecret); + + Poco::HMACEngine hmacEngine(signingKey); + hmacEngine.update(signatureBase); + Poco::DigestEngine::Digest digest = hmacEngine.digest(); + std::ostringstream digestBase64; + Poco::Base64Encoder base64Encoder(digestBase64); + base64Encoder.write(reinterpret_cast(&digest[0]), digest.size()); + base64Encoder.close(); + return digestBase64.str(); +} + + +std::string Twitter::percentEncode(const std::string& str) +{ + std::string encoded; + Poco::URI::encode(str, "!?#/'\",;:$&()[]*+=@", encoded); + return encoded; +} diff --git a/Net/samples/TwitterClient/src/Twitter.h b/Net/samples/TwitterClient/src/Twitter.h index f5860a8e1..3021f1628 100644 --- a/Net/samples/TwitterClient/src/Twitter.h +++ b/Net/samples/TwitterClient/src/Twitter.h @@ -1,11 +1,11 @@ // // Twitter.h // -// $Id: //poco/1.4/Net/samples/TwitterClient/src/Twitter.h#1 $ +// $Id: //poco/1.4/Net/samples/TwitterClient/src/Twitter.h#2 $ // // A C++ implementation of a Twitter client based on the POCO Net library. // -// Copyright (c) 2009, Applied Informatics Software Engineering GmbH. +// Copyright (c) 2009-2013, Applied Informatics Software Engineering GmbH. // and Contributors. // // Permission is hereby granted, free of charge, to any person or organization @@ -38,54 +38,74 @@ #include "Poco/Poco.h" #include "Poco/Net/HTMLForm.h" -#include "Poco/DOM/Document.h" +#include "Poco/Util/AbstractConfiguration.h" #include "Poco/AutoPtr.h" class Twitter /// A simple implementation of a Twitter API client - /// (see for more information). + /// (see for more information). /// /// Currently, only the update message is supported. { public: Twitter(); /// Creates the Twitter object, using - /// the default Twitter API URI (). + /// the default Twitter API URI (). Twitter(const std::string& twitterURI); - /// Creates the Twitter object using the given URI. + /// Creates the Twitter object using the given Twitter API URI. ~Twitter(); /// Destroys the Twitter object. - void login(const std::string& username, const std::string& password); - /// Specifies the username and password used in all API calls that - /// require authentication. + void login(const std::string& consumerKey, const std::string& consumerSecret, const std::string& token, const std::string& tokenSecret); + /// Specifies the OAuth authentication information used in all API calls. Poco::Int64 update(const std::string& status); /// Updates the user's status. /// /// Returns the ID of the newly created status. - Poco::AutoPtr invoke(const std::string& httpMethod, const std::string& twitterMethod, Poco::Net::HTMLForm& params); + Poco::AutoPtr invoke(const std::string& httpMethod, const std::string& twitterMethod, Poco::Net::HTMLForm& params); /// Invokes the given method of the Twitter API, using the parameters /// given in the Poco::Net::HTMLForm object. httpMethod must be GET or POST, /// according to the Twitter API documentation. /// - /// Returns an XML DOM document containing the server's response if the + /// Returns a Poco::Util::JSONConfiguration with the server's response if the /// server's HTTP response status code is 200 (OK). /// Otherwise, throws a Poco::ApplicationException. static const std::string TWITTER_URI; +protected: + void sign(Poco::Net::HTTPRequest& request, const Poco::Net::HTMLForm& params, const std::string& uri) const; + /// Signs the given HTTP request according to OAuth 1.0a as used by the Twitter API. + /// + /// See and + /// for more information. + + std::string createNonce() const; + /// Creates a nonce, which is basically a Base64-encoded 32 character random + /// string, with non-alphanumeric characters removed. + + std::string createSignature(Poco::Net::HTTPRequest& request, const Poco::Net::HTMLForm& params, const std::string& uri, const std::string& nonce, const std::string& timestamp) const; + /// Creates a OAuth signature for the given request and its parameters, according + /// to . + + static std::string percentEncode(const std::string& str); + /// Percent-encodes the given string according to Twitter API's rules, + /// given in . + private: Twitter(const Twitter&); Twitter& operator = (const Twitter&); std::string _uri; - std::string _username; - std::string _password; + std::string _consumerKey; + std::string _consumerSecret; + std::string _token; + std::string _tokenSecret; };