├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── build └── CMakeLists.txt ├── demo ├── simple_auth.cpp └── simple_request.cpp ├── include └── liboauthcpp │ └── liboauthcpp.h ├── src ├── HMAC_SHA1.cpp ├── HMAC_SHA1.h ├── SHA1.cpp ├── SHA1.h ├── base64.cpp ├── base64.h ├── liboauthcpp.cpp ├── urlencode.cpp └── urlencode.h └── tests ├── fast_request_test.h ├── long_request_test.h ├── main.cpp ├── parsekeyvaluepairs_test.h ├── request_test.h ├── testutil.cpp ├── testutil.h └── urlencode_test.h /.gitignore: -------------------------------------------------------------------------------- 1 | # Build (by)products 2 | build/CMakeCache.txt 3 | build/CMakeFiles 4 | build/Makefile 5 | build/cmake_install.cmake 6 | build/liboauthcpp.a 7 | build/simple_auth 8 | build/simple_request 9 | build/tests 10 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: cpp 2 | 3 | cache: ccache 4 | sudo: false 5 | matrix: 6 | include: 7 | - os: linux 8 | dist: precise 9 | compiler: gcc 10 | - os: linux 11 | dist: trusty 12 | compiler: gcc 13 | - os: linux 14 | dist: precise 15 | compiler: clang 16 | - os: linux 17 | dist: trusty 18 | compiler: clang 19 | - os: osx 20 | compiler: gcc 21 | - os: osx 22 | compiler: clang 23 | 24 | script: 25 | - cd build 26 | - cmake . 27 | - make 28 | - make test 29 | 30 | # Only build master, and only build PR branches once 31 | branches: 32 | only: 33 | - master -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2011 Stanford University (liboauthcpp) 2 | Copyright (C) 2011 by swatkat (swatkat.thinkdigitATgmailDOTcom) (libtwitcurl) 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining 5 | a copy of this software and associated documentation files (the 6 | "Software"), to deal in the Software without restriction, including 7 | without limitation the rights to use, copy, modify, merge, publish, 8 | distribute, sublicense, and/or sell copies of the Software, and to 9 | permit persons to whom the Software is furnished to do so, subject to 10 | the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be 13 | included in all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 16 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 17 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 18 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 19 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 20 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 21 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | liboauthcpp 2 | ----------- 3 | 4 | liboauthcpp is a pure C++ library for performing OAuth requests. It 5 | doesn't contain any networking code -- you provide for performing HTTP 6 | requests yourself, however you like -- instead focusing on performing 7 | OAuth-specific functionality and providing a nice interface for it. 8 | If you already have infrastructure for making HTTP requests and are 9 | looking to add OAuth support, liboauthcpp is for you. 10 | 11 | liboauthcpp currently implements OAuth 1.0a (see 12 | http://tools.ietf.org/html/rfc5849). 13 | 14 | Buildbot 15 | -------- 16 | [![Build Status](https://secure.travis-ci.org/sirikata/liboauthcpp.png)](http://travis-ci.org/sirikata/liboauthcpp) 17 | 18 | Requirements 19 | ------------ 20 | 21 | You should only need: 22 | 23 | * CMake 24 | * A C++ compiler for your platform (e.g. g++, Microsoft Visual C++) 25 | 26 | Compiling 27 | --------- 28 | 29 | The build process is simple: 30 | 31 | cd liboauthcpp 32 | cd build 33 | cmake . 34 | make # or open Visual Studio and build the solution 35 | 36 | If your own project uses CMake you can also include 37 | build/CMakeLists.txt directly into your project and reference the 38 | target "oauthcpp", a static library, in your project. 39 | 40 | Percent (URL) Encoding 41 | ---------------------- 42 | 43 | To get correct results, you need to pass your URL properly encoded to 44 | liboauthcpp. If you are not at all familiar, you should probably start 45 | by reading the [URI Spec](http://tools.ietf.org/html/rfc3986), especially 46 | Section 2. Alternatively, 47 | [this article](http://blog.lunatech.com/2009/02/03/what-every-web-developer-must-know-about-url-encoding) 48 | gives a more readable overview. 49 | 50 | The basic idea is that there are 3 classes of characters: reserved, 51 | unreserved, and other. Reserved characters are special characters that 52 | are used in the URI syntax itself, e.g. ':' (after the scheme), '/' 53 | (the hierarchical path separator), and '?' (prefixing the query 54 | string). Unreserved characters are characters that are always safe to 55 | include unencoded, e.g. the alphanumerics. Other characters must 56 | always be encoded, mainly covering special characters like ' ', '<' or 57 | '>', and '{' or '}'. 58 | 59 | The basic rule is that reserved characters must be encoded if they 60 | appear in any part of the URI when not being used as a 61 | separator. Unreserved characters are always safe. And the other 62 | characters they didn't know if they would be safe or not so they must 63 | always be encoded. 64 | 65 | Unfortunately, the reserved set is a bit more complicated. They are 66 | broken down into 'general delimiters' and 'sub delimiters'. The ones 67 | already mentioned, like ':', can appear in many forms of URIs (say, 68 | http, ftp, about, gopher, mailto, etc. Those are called general 69 | delimiters. Others (e.g. '(', ')', '!', '$', '+', ',', '=', and more) 70 | are called subdelimiters because their use depends on the URI 71 | scheme. Worse, their use depends on the *part of the URI*. Depending 72 | on the particular URI scheme, these may or may not have to be encoded, 73 | and it might also depend on where they appear. (As an example, an '&' 74 | in an http URI isn't an issue if it appears in the path -- before the 75 | query string -- i.e. before a '?' appears. Worse, '=' can appear unencoded in 76 | the path, or in a query parameter value, but not in a query parameter key since 77 | it would be interpreted as the end of the key.) 78 | 79 | *Additionally*, in many cases it is permitted to encode a character 80 | unnecessarily and the result is supposed to be the same. This means 81 | that it's possible to percent encode some URLs in multiple ways 82 | (e.g. encoding the unreserved set unnecessarily). It is possible, but not 83 | guaranteed, that if you pass *exactly* the same URI to liboauthcpp and the 84 | OAuth server, it will handle it regardless of the variant of encoding, so long 85 | as it is a valid encoding. 86 | 87 | The short version: percent encoding a URL properly is non-trivial and 88 | you can even encode the same URL multiple ways, but has to be done 89 | correctly so that the OAuth signature can be computed. Sadly, 90 | "correctly" in this case really means "in whatever way the server your 91 | interacting with wants it encoded". 92 | 93 | Internally, liboauthcpp needs to do another step of percent encoding, 94 | but the OAuth spec is very precise about how that works (none of these 95 | scheme-dependent issues). liboauth applies this percent encoding, but 96 | assumes that you have encoded your URLs properly. This assumption 97 | makes sense since the actual request is made separately, and the URI 98 | has to be specified in it, so you should already have a form which the 99 | server will accept. 100 | 101 | However, in order to aid you, a very simple percent encoding API is exposed. It 102 | should help you encode URLs minimally and in a way that many services accept. In 103 | most cases you should use `HttpPercentEncodePath()`, 104 | `HttpPercentEncodeQueryKey()`, and `HttpPercentEncodeQueryValue()` to encode 105 | those parts of your http URL, then combine them and pass them to liboauthcpp for 106 | signing. 107 | 108 | 109 | Thread Safety 110 | ------------- 111 | 112 | liboauthcpp doesn't provide any thread safety guarantees. That said, there is 113 | very little shared state, and some classes (e.g. Consumer) are naturally 114 | immutable and therefore thread safe. Similarly, nearly the entire library uses 115 | no static/shared state, so as long as you create separate objects for separate 116 | threads, you should be safe. 117 | 118 | The one exception is nonces: the Client class needs to generate a nonce for 119 | authorization. To do so, the random number generator needs to be seeded. We do 120 | this with the current time, but fast, repeated use of the Client class from 121 | different threads could result in the same nonce. To avoid requiring an entire 122 | thread library just for this one case, you can call Client::initialize() 123 | explicitly before using the Client from multiple threads. For single-threaded 124 | use, you are not required to call it. 125 | 126 | Demos 127 | ----- 128 | There are two demos included in the demos/ directory, and they are built by 129 | default with the instructions above. In both, you enter key/secret information 130 | and it generates URLs for you to visit (in a browser) and copy data back into 131 | the program. 132 | 133 | simple_auth should be executed first. It starts with only a consumer key and 134 | secret and performs 3-legged auth: you enter in consumer keys, it generates URLs 135 | to authenticate the user and generate access tokens. It requires 3 steps: 136 | request_token, authorize, and access_token (which correspond the URLs 137 | accessed). At the end of this process, you'll be provided an access key/secret 138 | pair which you can use to access actual resources. 139 | 140 | simple_request actually does something useful now that your application is 141 | authorized. Enter your consumer key/secret and the access key/secret from 142 | simple_auth (or which you've generated elsewhere) and it will generate a URL you 143 | can use to access your home timeline in JSON format. It adds a parameter to ask 144 | for only 5 entries (demonstrating that signing works properly over additional 145 | query parameters). This is a one-step process -- it just gives you the URL and 146 | you get the results in your browser. 147 | 148 | In both, the URLs accessed are specified at the top of the demo 149 | files. simple_auth requires URLs for request_token, authorize_url, and 150 | access_token. Some providers require additional parameters (notably an 151 | oauth_callback for Twitter, even if its out of band, or oob), which you can also 152 | specify in that location. simple_request only needs the URL of the resource 153 | being accessed (i.e. the URL for the home_timeline JSON data used by default in 154 | the demo), with optional parameters stored as a query string. 155 | 156 | Both demos only use GET requests with query strings, but all HTTP methods 157 | (e.g. POST, PUT, DELETE) and approaches to sending parameters (e.g. HTTP 158 | headers, url-encoded body) should be supported in the API. 159 | 160 | License 161 | ------- 162 | 163 | liboauthcpp is MIT licensed. See the LICENSE file for more details. 164 | 165 | liboauthcpp is mostly taken from libtwitcurl 166 | (https://github.com/swatkat/twitcurl), which is similarly licensed. It 167 | mostly serves to isolate the OAuth code from libtwitcurl's Twitter and 168 | cURL specific code. 169 | 170 | libtwitcurl also borrowed code from other projects: 171 | twitcurl uses HMAC_SHA1 from http://www.codeproject.com/KB/recipes/HMACSHA1class.aspx 172 | twitcurl uses base64 from http://www.adp-gmbh.ch/cpp/common/base64.html 173 | -------------------------------------------------------------------------------- /build/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2011 Stanford University. All rights reserved. 2 | # Use of this source code is governed by a MIT-style license that can 3 | # be found in the LICENSE file. 4 | 5 | CMAKE_MINIMUM_REQUIRED(VERSION 2.4) 6 | SET(CMAKE_VERBOSE_MAKEFILE OFF) 7 | SET(CMAKE_ALLOW_LOOSE_LOOP_CONSTRUCTS TRUE) 8 | 9 | IF(COMMAND CMAKE_POLICY) 10 | # Use new policy for absolute path libraries. We have no reason to depend on the 11 | # old version which adds library search paths unnecessarily 12 | CMAKE_POLICY(SET CMP0003 NEW) 13 | ENDIF(COMMAND CMAKE_POLICY) 14 | 15 | IF(APPLE) 16 | IF(IS_DIRECTORY /Developer/SDKs/MacOSX10.5.sdk) 17 | SET(CMAKE_CXX_COMPILER g++-4.0) 18 | SET(CMAKE_C_COMPILER gcc-4.0) 19 | ELSE() 20 | IF(EXISTS /usr/bin/g++-4.2) 21 | SET(CMAKE_CXX_COMPILER g++-4.2) 22 | SET(CMAKE_C_COMPILER gcc-4.2) 23 | ELSE() 24 | # Don't try to override. This is probably a newer setup 25 | # using XCode 4.2+ which doesn't provide versioned gcc's 26 | # since it uses llvm. 27 | #SET(CMAKE_CXX_COMPILER g++) 28 | #SET(CMAKE_C_COMPILER gcc) 29 | ENDIF() 30 | ENDIF() 31 | ENDIF() 32 | 33 | PROJECT(liboauthcpp C CXX) 34 | ENABLE_LANGUAGE(C) 35 | 36 | # Disable some annoying, unnecessary Windows warnings. Enable unwinding for exception handlers. 37 | IF(MSVC) 38 | ADD_DEFINITIONS(-D_CRT_SECURE_NO_WARNINGS -D_SCL_SECURE_NO_WARNINGS -EHsc) 39 | SET(CMAKE_CXX_FLAGS "-D_CRT_SECURE_NO_WARNINGS -D_SCL_SECURE_NO_WARNINGS") 40 | ENDIF() 41 | 42 | # Force a default build type 43 | IF(NOT CMAKE_BUILD_TYPE) 44 | SET(CMAKE_BUILD_TYPE Release CACHE STRING 45 | "Build types include: Default Debug Release None RelWithDebInfo." FORCE 46 | ) 47 | ENDIF() 48 | MESSAGE(STATUS "Build type: ${CMAKE_BUILD_TYPE}") 49 | # And add a definition so code can tell 50 | IF(CMAKE_BUILD_TYPE EQUAL "Debug") 51 | ADD_DEFINITIONS(-DLIBOAUTHCPP_DEBUG) 52 | ENDIF() 53 | 54 | # Define a few helpful directories 55 | IF(NOT LIBOAUTHCPP_TOP_LEVEL) 56 | SET(LIBOAUTHCPP_TOP_LEVEL ${CMAKE_CURRENT_SOURCE_DIR}/..) 57 | ENDIF() 58 | GET_FILENAME_COMPONENT(LIBOAUTHCPP_TOP_LEVEL ${LIBOAUTHCPP_TOP_LEVEL} ABSOLUTE) 59 | MESSAGE(STATUS "Base directory: ${LIBOAUTHCPP_TOP_LEVEL}") 60 | SET(LIBOAUTHCPP_INCLUDE ${LIBOAUTHCPP_TOP_LEVEL}/include) 61 | SET(LIBOAUTHCPP_SRC ${LIBOAUTHCPP_TOP_LEVEL}/src) 62 | SET(LIBOAUTHCPP_TEST ${LIBOAUTHCPP_TOP_LEVEL}/tests) 63 | SET(LIBOAUTHCPP_DEMO ${LIBOAUTHCPP_TOP_LEVEL}/demo) 64 | 65 | # CMake doesn't seem to allow adding include directories for specific 66 | # projects... 67 | INCLUDE_DIRECTORIES(${LIBOAUTHCPP_INCLUDE}) 68 | 69 | # Handle configuration options from parent projects 70 | IF(LIBOAUTHCPP_ADDED_DEFINITIONS) 71 | ADD_DEFINITIONS(${LIBOAUTHCPP_ADDED_DEFINITIONS}) 72 | ENDIF() 73 | 74 | # The main library 75 | SET(LIBOAUTHCPP_LIB_SOURCES 76 | ${LIBOAUTHCPP_SRC}/base64.cpp 77 | ${LIBOAUTHCPP_SRC}/HMAC_SHA1.cpp 78 | ${LIBOAUTHCPP_SRC}/liboauthcpp.cpp 79 | ${LIBOAUTHCPP_SRC}/SHA1.cpp 80 | ${LIBOAUTHCPP_SRC}/urlencode.cpp 81 | ) 82 | ADD_LIBRARY(oauthcpp STATIC ${LIBOAUTHCPP_LIB_SOURCES}) 83 | INSTALL(TARGETS oauthcpp 84 | ARCHIVE DESTINATION lib 85 | LIBRARY DESTINATION lib 86 | RUNTIME DESTINATION lib 87 | ) 88 | INSTALL(DIRECTORY 89 | ${LIBOAUTHCPP_INCLUDE}/ DESTINATION include 90 | ) 91 | 92 | # Allow disabling of tests 93 | IF(NOT DEFINED LIBOAUTHCPP_BUILD_TESTS) 94 | SET(LIBOAUTHCPP_BUILD_TESTS TRUE CACHE BOOL "Whether to build tests") 95 | ELSE() 96 | SET(LIBOAUTHCPP_BUILD_TESTS LIBOAUTHCPP_BUILD_TESTS CACHE BOOL "Whether to build tests") 97 | ENDIF() 98 | 99 | IF(LIBOAUTHCPP_BUILD_TESTS) 100 | 101 | # A simple demo of authentication -- getting from consumer 102 | # key/secret all the way to an access token for a user.The user 103 | # manually performs HTTP steps in a browser. 104 | SET(LIBOATHCPP_TEST_SOURCES 105 | ${LIBOAUTHCPP_TEST}/main.cpp 106 | ${LIBOAUTHCPP_TEST}/testutil.cpp 107 | ) 108 | ADD_EXECUTABLE(tests ${LIBOATHCPP_TEST_SOURCES}) 109 | TARGET_LINK_LIBRARIES(tests oauthcpp) 110 | 111 | # Target for running the tests 112 | ADD_CUSTOM_TARGET(test 113 | tests 114 | ) 115 | ENDIF() #LIBOAUTHCPP_BUILD_TESTS 116 | 117 | # Allow disabling of the demos 118 | IF(NOT DEFINED LIBOAUTHCPP_BUILD_DEMOS) 119 | SET(LIBOAUTHCPP_BUILD_DEMOS TRUE CACHE BOOL "If enabled, builds demos if their dependencies are available.") 120 | ELSE() 121 | SET(LIBOAUTHCPP_BUILD_DEMOS LIBOAUTHCPP_BUILD_DEMOS CACHE BOOL "If enabled, builds demos if their dependencies are available.") 122 | ENDIF() 123 | 124 | IF(LIBOAUTHCPP_BUILD_DEMOS) 125 | 126 | # A simple demo of authentication -- getting from consumer 127 | # key/secret all the way to an access token for a user.The user 128 | # manually performs HTTP steps in a browser. 129 | SET(LIBOATHCPP_SIMPLEAUTHDEMO_SOURCES 130 | ${LIBOAUTHCPP_DEMO}/simple_auth.cpp 131 | ) 132 | ADD_EXECUTABLE(simple_auth ${LIBOATHCPP_SIMPLEAUTHDEMO_SOURCES}) 133 | TARGET_LINK_LIBRARIES(simple_auth oauthcpp) 134 | 135 | # A simple demo of performing a request -- after acquiring an access 136 | # token for a user, sets up the request to access a resource. The 137 | # user manually performs HTTP steps in a browser. 138 | SET(LIBOATHCPP_SIMPLEREQUESTDEMO_SOURCES 139 | ${LIBOAUTHCPP_DEMO}/simple_request.cpp 140 | ) 141 | ADD_EXECUTABLE(simple_request ${LIBOATHCPP_SIMPLEREQUESTDEMO_SOURCES}) 142 | TARGET_LINK_LIBRARIES(simple_request oauthcpp) 143 | 144 | ENDIF() #LIBOAUTHCPP_BUILD_DEMOS 145 | -------------------------------------------------------------------------------- /demo/simple_auth.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | /* These are input settings that make this demo actually work -- you need to get 6 | * these, e.g. by referring to the Twitter documentation and by registering an 7 | * application with them. Here we have examples from Twitter. If you 8 | * don't enter any, you'll be prompted to enter them at runtime. 9 | */ 10 | std::string consumer_key = ""; // Key from Twitter 11 | std::string consumer_secret = ""; // Secret from Twitter 12 | std::string request_token_url = "https://api.twitter.com/oauth/request_token"; 13 | std::string request_token_query_args = "oauth_callback=oob"; 14 | std::string authorize_url = "https://api.twitter.com/oauth/authorize"; 15 | std::string access_token_url = "https://api.twitter.com/oauth/access_token"; 16 | 17 | 18 | std::string getUserString(std::string prompt) { 19 | std::cout << prompt << " "; 20 | 21 | std::string res; 22 | std::cin >> res; 23 | std::cout << std::endl; 24 | return res; 25 | } 26 | 27 | int main(int argc, char** argv) { 28 | if (argc > 1 && std::string(argv[1]) == std::string("--debug")) 29 | OAuth::SetLogLevel(OAuth::LogLevelDebug); 30 | 31 | // Initialization 32 | if (consumer_key.empty()) consumer_key = getUserString("Enter consumer key:"); 33 | if (consumer_secret.empty()) consumer_secret = getUserString("Enter consumer secret:"); 34 | OAuth::Consumer consumer(consumer_key, consumer_secret); 35 | OAuth::Client oauth(&consumer); 36 | 37 | // Step 1: Get a request token. This is a temporary token that is used for 38 | // having the user authorize an access token and to sign the request to 39 | // obtain said access token. 40 | std::string base_request_token_url = request_token_url + (request_token_query_args.empty() ? std::string("") : (std::string("?")+request_token_query_args) ); 41 | std::string oAuthQueryString = 42 | oauth.getURLQueryString( OAuth::Http::Get, base_request_token_url); 43 | 44 | std::cout << "Enter the following in your browser to get the request token: " << std::endl; 45 | // Note that getting the query string includes the arguments we 46 | // passed in, so we don't need to include request_token_query_args 47 | // again. 48 | std::cout << request_token_url << "?" << oAuthQueryString << std::endl; 49 | std::cout << std::endl; 50 | 51 | // Extract the token and token_secret from the response 52 | std::string request_token_resp = getUserString("Enter the response:"); 53 | // This time we pass the response directly and have the library do the 54 | // parsing (see next extractToken call for alternative) 55 | OAuth::Token request_token = OAuth::Token::extract( request_token_resp ); 56 | 57 | // Get access token and secret from OAuth object 58 | std::cout << "Request Token:" << std::endl; 59 | std::cout << " - oauth_token = " << request_token.key() << std::endl; 60 | std::cout << " - oauth_token_secret = " << request_token.secret() << std::endl; 61 | std::cout << std::endl; 62 | 63 | // Step 2: Redirect to the provider. Since this is a CLI script we 64 | // do not redirect. In a web application you would redirect the 65 | // user to the URL below. 66 | std::cout << "Go to the following link in your browser to authorize this application on a user's account:" << std::endl; 67 | std::cout << authorize_url << "?oauth_token=" << request_token.key() << std::endl; 68 | 69 | // After the user has granted access to you, the consumer, the 70 | // provider will redirect you to whatever URL you have told them 71 | // to redirect to. You can usually define this in the 72 | // oauth_callback argument as well. 73 | std::string pin = getUserString("What is the PIN?"); 74 | request_token.setPin(pin); 75 | 76 | // Step 3: Once the consumer has redirected the user back to the 77 | // oauth_callback URL you can request the access token the user 78 | // has approved. You use the request token to sign this 79 | // request. After this is done you throw away the request token 80 | // and use the access token returned. You should store the oauth 81 | // token and token secret somewhere safe, like a database, for 82 | // future use. 83 | oauth = OAuth::Client(&consumer, &request_token); 84 | // Note that we explicitly specify an empty body here (it's a GET) so we can 85 | // also specify to include the oauth_verifier parameter 86 | oAuthQueryString = oauth.getURLQueryString( OAuth::Http::Get, access_token_url, std::string( "" ), true ); 87 | std::cout << "Enter the following in your browser to get the final access token & secret: " << std::endl; 88 | std::cout << access_token_url << "?" << oAuthQueryString; 89 | std::cout << std::endl; 90 | 91 | // Once they've come back from the browser, extract the token and token_secret from the response 92 | std::string access_token_resp = getUserString("Enter the response:"); 93 | // On this extractToken, we do the parsing ourselves (via the library) so we 94 | // can extract additional keys that are sent back, in the case of twitter, 95 | // the screen_name 96 | OAuth::KeyValuePairs access_token_resp_data = OAuth::ParseKeyValuePairs(access_token_resp); 97 | OAuth::Token access_token = OAuth::Token::extract( access_token_resp_data ); 98 | 99 | std::cout << "Access token:" << std::endl; 100 | std::cout << " - oauth_token = " << access_token.key() << std::endl; 101 | std::cout << " - oauth_token_secret = " << access_token.secret() << std::endl; 102 | std::cout << std::endl; 103 | std::cout << "You may now access protected resources using the access tokens above." << std::endl; 104 | std::cout << std::endl; 105 | 106 | std::pair screen_name_its = access_token_resp_data.equal_range("screen_name"); 107 | for(OAuth::KeyValuePairs::iterator it = screen_name_its.first; it != screen_name_its.second; it++) 108 | std::cout << "Also extracted screen name from access token response: " << it->second << std::endl; 109 | 110 | // E.g., to use the access token, you'd create a new OAuth using 111 | // it, discarding the request_token: 112 | // oauth = OAuth::Client(&consumer, &access_token); 113 | 114 | return 0; 115 | } 116 | -------------------------------------------------------------------------------- /demo/simple_request.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | /* These are input settings that make this demo actually work -- you need to get 6 | * these, e.g. by referring to the Twitter documentation and by registering an 7 | * application with them. Here we have examples from Twitter. If you 8 | * don't enter any, you'll be prompted to enter them at runtime. 9 | */ 10 | std::string consumer_key = ""; // Key from Twitter 11 | std::string consumer_secret = ""; // Secret from Twitter 12 | std::string oauth_token = ""; // User-specific token acquired by auth process 13 | std::string oauth_token_secret = ""; // User-specific secret acquired by auth process. 14 | // An example resource to be accessed, along with parameters for the request 15 | std::string oauth_protected_resource = "https://api.twitter.com/1.1/statuses/home_timeline.json"; 16 | std::string oauth_protected_resource_params = "count=5"; 17 | 18 | std::string getUserString(std::string prompt) { 19 | std::cout << prompt << " "; 20 | 21 | std::string res; 22 | std::cin >> res; 23 | std::cout << std::endl; 24 | return res; 25 | } 26 | 27 | int main(int argc, char** argv) { 28 | if (argc > 1 && std::string(argv[1]) == std::string("--debug")) 29 | OAuth::SetLogLevel(OAuth::LogLevelDebug); 30 | 31 | // Initialization 32 | if (consumer_key.empty()) consumer_key = getUserString("Enter consumer key:"); 33 | if (consumer_secret.empty()) consumer_secret = getUserString("Enter consumer secret:"); 34 | // We assume you have gotten the access token. You may have e.g., used 35 | // simple_auth to get it. 36 | if (oauth_token.empty()) oauth_token = getUserString("Enter access token:"); 37 | if (oauth_token_secret.empty()) oauth_token_secret = getUserString("Enter access token secret:"); 38 | OAuth::Consumer consumer(consumer_key, consumer_secret); 39 | OAuth::Token token(oauth_token, oauth_token_secret); 40 | OAuth::Client oauth(&consumer, &token); 41 | 42 | // Get the query string. Note that we pass in the URL as if we were 43 | // accessing the resource, but *the output query string includes the 44 | // parameters you passed in*. Below, we append the result only to the base 45 | // URL, not the entire URL we passed in here. 46 | std::string oAuthQueryString = 47 | oauth.getURLQueryString(OAuth::Http::Get, oauth_protected_resource + "?" + oauth_protected_resource_params); 48 | 49 | std::cout << "Enter the following in your browser to access the resource: " << std::endl; 50 | std::cout << oauth_protected_resource << "?" << oAuthQueryString << std::endl; 51 | std::cout << std::endl; 52 | 53 | return 0; 54 | } 55 | -------------------------------------------------------------------------------- /include/liboauthcpp/liboauthcpp.h: -------------------------------------------------------------------------------- 1 | #ifndef __LIBOAUTHCPP_LIBOAUTHCPP_H__ 2 | #define __LIBOAUTHCPP_LIBOAUTHCPP_H__ 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | namespace OAuth { 11 | 12 | namespace Http { 13 | typedef enum _RequestType 14 | { 15 | Invalid = 0, 16 | Head, 17 | Get, 18 | Post, 19 | Delete, 20 | Put 21 | } RequestType; 22 | } // namespace Http 23 | 24 | typedef std::list KeyValueList; 25 | typedef std::multimap KeyValuePairs; 26 | 27 | typedef enum _LogLevel 28 | { 29 | LogLevelNone = 0, 30 | LogLevelDebug = 1 31 | } LogLevel; 32 | 33 | /** Set the log level. Log messages are sent to stderr. Currently, and for the 34 | * foreseeable future, logging only consists of debug messages to help track 35 | * down protocol implementation issues. 36 | */ 37 | void SetLogLevel(LogLevel lvl); 38 | 39 | /** Deprecated. Complete percent encoding of URLs. Equivalent to 40 | * PercentEncode. 41 | */ 42 | std::string URLEncode(const std::string& decoded); 43 | 44 | /** Percent encode a string value. This version is *thorough* about 45 | * encoding: it encodes all reserved characters (even those safe in 46 | * http URLs) and "other" characters not specified by the URI 47 | * spec. If you're looking to encode http:// URLs, see the 48 | * HttpEncode* functions. 49 | */ 50 | std::string PercentEncode(const std::string& decoded); 51 | 52 | /** Percent encodes the path portion of an http URL (i.e. the /foo/bar 53 | * in http://foo/bar?a=1&b=2). This encodes minimally, so reserved 54 | * subdelimiters that have no meaning in the path are *not* encoded. 55 | */ 56 | std::string HttpEncodePath(const std::string& decoded); 57 | 58 | /** Percent encodes a query string key in an http URL (i.e. 'a', 'b' in 59 | * http://foo/bar?a=1&b=2). This encodes minimally, so reserved subdelimiters 60 | * that have no meaning in the query string are *not* encoded. 61 | */ 62 | std::string HttpEncodeQueryKey(const std::string& decoded); 63 | 64 | /** Percent encodes a query string value in an http URL (i.e. '1', '2' in 65 | * http://foo/bar?a=1&b=2). This encodes minimally, so reserved subdelimiters 66 | * that have no meaning in the query string are *not* encoded. 67 | */ 68 | std::string HttpEncodeQueryValue(const std::string& decoded); 69 | 70 | /** Parses key value pairs into a map. 71 | * \param encoded the encoded key value pairs, i.e. the url encoded parameters 72 | * \returns a map of string keys to string values 73 | * \throws ParseError if the encoded data cannot be decoded 74 | */ 75 | KeyValuePairs ParseKeyValuePairs(const std::string& encoded); 76 | 77 | class ParseError : public std::runtime_error { 78 | public: 79 | ParseError(const std::string msg) 80 | : std::runtime_error(msg) 81 | {} 82 | }; 83 | 84 | class MissingKeyError : public std::runtime_error { 85 | public: 86 | MissingKeyError(const std::string msg) 87 | : std::runtime_error(msg) 88 | {} 89 | }; 90 | 91 | /** A consumer of OAuth-protected services. It is the client to an 92 | * OAuth service provider and is usually registered with the service 93 | * provider, resulting in a consumer *key* and *secret* used to 94 | * identify the consumer. The key is included in all requests and the 95 | * secret is used to *sign* all requests. Signed requests allow the 96 | * consumer to securely perform operations, including kicking off 97 | * three-legged authentication to enable performing operations on 98 | * behalf of a user of the service provider. 99 | */ 100 | class Consumer { 101 | public: 102 | Consumer(const std::string& key, const std::string& secret); 103 | 104 | const std::string& key() const { return mKey; } 105 | const std::string& secret() const { return mSecret; } 106 | 107 | private: 108 | const std::string mKey; 109 | const std::string mSecret; 110 | }; 111 | 112 | /** An OAuth credential used to request authorization or a protected 113 | * resource. 114 | * 115 | * Tokens in OAuth comprise a *key* and a *secret*. The key is 116 | * included in requests to identify the token being used, but the 117 | * secret is used only in the signature, to prove that the requester 118 | * is who the server gave the token to. 119 | * 120 | * When first negotiating the authorization, the consumer asks for a 121 | * *request token* that the live user authorizes with the service 122 | * provider. The consumer then exchanges the request token for an 123 | * *access token* that can be used to access protected resources. 124 | */ 125 | class Token { 126 | public: 127 | Token(const std::string& key, const std::string& secret); 128 | Token(const std::string& key, const std::string& secret, const std::string& pin); 129 | 130 | /** Construct a token, extracting the key and secret from a set of 131 | * key-value pairs (e.g. those parsed from an request or access 132 | * token request). 133 | */ 134 | static Token extract(const KeyValuePairs& response); 135 | /** Construct a token, extracting the key and secret from a raw, 136 | * encoded response. 137 | */ 138 | static Token extract(const std::string& requestTokenResponse); 139 | 140 | const std::string& key() const { return mKey; } 141 | const std::string& secret() const { return mSecret; } 142 | 143 | const std::string& pin() const { return mPin; } 144 | void setPin(const std::string& pin_) { mPin = pin_; } 145 | 146 | private: 147 | 148 | const std::string mKey; 149 | const std::string mSecret; 150 | std::string mPin; 151 | }; 152 | 153 | class Client { 154 | public: 155 | /** Perform static initialization. This will be called automatically, but 156 | * you can call it explicitly to ensure thread safety. If you do not call 157 | * this explicitly before using the Client class, the same nonce may be 158 | * generated twice. 159 | */ 160 | static void initialize(); 161 | /** Alternative initialize method which lets you specify the seed and 162 | * control the timestamp used in generating signatures. This only exists 163 | * for testing purposes and should not be used in practice. 164 | */ 165 | static void initialize(int nonce, time_t timestamp); 166 | 167 | /** Exposed for testing only. 168 | */ 169 | static void __resetInitialize(); 170 | 171 | /** Construct an OAuth Client using only a consumer key and 172 | * secret. You can use this to start a three-legged 173 | * authentication (to acquire an access token for a user) or for 174 | * simple two-legged authentication (signing with empty access 175 | * token info). 176 | * 177 | * \param consumer Consumer information. The caller must ensure 178 | * it remains valid during the lifetime of this object 179 | */ 180 | Client(const Consumer* consumer); 181 | /** Construct an OAuth Client with consumer key and secret (yours) 182 | * and access token key and secret (acquired and stored during 183 | * three-legged authentication). 184 | * 185 | * \param consumer Consumer information. The caller must ensure 186 | * it remains valid during the lifetime of this object 187 | * \param token Access token information. The caller must ensure 188 | * it remains valid during the lifetime of this object 189 | */ 190 | Client(const Consumer* consumer, const Token* token); 191 | 192 | ~Client(); 193 | 194 | /** Build an OAuth HTTP header for the given request. This version provides 195 | * only the field value. 196 | * 197 | * \param eType the HTTP request type, e.g. GET or POST 198 | * \param rawUrl the raw request URL (should include query parameters) 199 | * \param rawData the raw HTTP request data (can be empty) 200 | * \param includeOAuthVerifierPin if true, adds oauth_verifier parameter 201 | * \returns a string containing the HTTP header 202 | */ 203 | std::string getHttpHeader(const Http::RequestType eType, 204 | const std::string& rawUrl, 205 | const std::string& rawData = "", 206 | const bool includeOAuthVerifierPin = false) const; 207 | /** Build an OAuth HTTP header for the given request. This version gives a 208 | * fully formatted header, i.e. including the header field name. 209 | * 210 | * \param eType the HTTP request type, e.g. GET or POST 211 | * \param rawUrl the raw request URL (should include query parameters) 212 | * \param rawData the raw HTTP request data (can be empty) 213 | * \param includeOAuthVerifierPin if true, adds oauth_verifier parameter 214 | * \returns a string containing the HTTP header 215 | */ 216 | std::string getFormattedHttpHeader(const Http::RequestType eType, 217 | const std::string& rawUrl, 218 | const std::string& rawData = "", 219 | const bool includeOAuthVerifierPin = false) const; 220 | /** Build an OAuth HTTP header for the given request. 221 | * 222 | * \param eType the HTTP request type, e.g. GET or POST 223 | * \param rawUrl the raw request URL (should include query parameters) 224 | * \param rawData the raw HTTP request data (can be empty) 225 | * \param includeOAuthVerifierPin if true, adds oauth_verifier parameter 226 | * \returns a string containing the query string, including the query 227 | * parameters in the rawUrl 228 | */ 229 | std::string getURLQueryString(const Http::RequestType eType, 230 | const std::string& rawUrl, 231 | const std::string& rawData = "", 232 | const bool includeOAuthVerifierPin = false) const; 233 | private: 234 | /** Disable default constructur -- must provide consumer 235 | * information. 236 | */ 237 | Client(); 238 | 239 | static bool initialized; 240 | static int testingNonce; 241 | static time_t testingTimestamp; 242 | 243 | /* OAuth data */ 244 | const Consumer* mConsumer; 245 | const Token* mToken; 246 | 247 | /* OAuth related utility methods */ 248 | bool buildOAuthTokenKeyValuePairs( const bool includeOAuthVerifierPin, /* in */ 249 | const std::string& rawData, /* in */ 250 | const std::string& oauthSignature, /* in */ 251 | KeyValuePairs& keyValueMap /* out */, 252 | const bool urlEncodeValues /* in */, 253 | const std::string& nonce /* in */, 254 | const std::string& timeStamp /* in */) const; 255 | 256 | bool getStringFromOAuthKeyValuePairs( const KeyValuePairs& rawParamMap, /* in */ 257 | std::string& rawParams, /* out */ 258 | const std::string& paramsSeperator /* in */ ) const; 259 | 260 | typedef enum _ParameterStringType { 261 | QueryStringString, 262 | AuthorizationHeaderString 263 | } ParameterStringType; 264 | // Utility for building OAuth HTTP header or query string. The string type 265 | // controls the separator and also filters parameters: for query strings, 266 | // all parameters are included. For HTTP headers, only auth parameters are 267 | // included. 268 | std::string buildOAuthParameterString( 269 | ParameterStringType string_type, 270 | const Http::RequestType eType, 271 | const std::string& rawUrl, 272 | const std::string& rawData, 273 | const bool includeOAuthVerifierPin) const; 274 | 275 | bool getSignature( const Http::RequestType eType, /* in */ 276 | const std::string& rawUrl, /* in */ 277 | const KeyValuePairs& rawKeyValuePairs, /* in */ 278 | std::string& oAuthSignature /* out */ ) const; 279 | 280 | void generateNonceTimeStamp(std::string& nonce, std::string& timeStamp) const; 281 | }; 282 | 283 | } // namespace OAuth 284 | 285 | #endif // __LIBOAUTHCPP_LIBOAUTHCPP_H__ 286 | -------------------------------------------------------------------------------- /src/HMAC_SHA1.cpp: -------------------------------------------------------------------------------- 1 | //****************************************************************************** 2 | //* HMAC_SHA1.cpp : Implementation of HMAC SHA1 algorithm 3 | //* Comfort to RFC 2104 4 | //* 5 | //****************************************************************************** 6 | #include "HMAC_SHA1.h" 7 | #include 8 | #include 9 | 10 | 11 | void CHMAC_SHA1::HMAC_SHA1(BYTE *text, int text_len, BYTE *key, int key_len, BYTE *digest) 12 | { 13 | memset(SHA1_Key, 0, SHA1_BLOCK_SIZE); 14 | 15 | /* repeated 64 times for values in ipad and opad */ 16 | memset(m_ipad, 0x36, sizeof(m_ipad)); 17 | memset(m_opad, 0x5c, sizeof(m_opad)); 18 | 19 | /* STEP 1 */ 20 | if (key_len > SHA1_BLOCK_SIZE) 21 | { 22 | CSHA1::Reset(); 23 | CSHA1::Update((UINT_8 *)key, key_len); 24 | CSHA1::Final(); 25 | 26 | CSHA1::GetHash((UINT_8 *)SHA1_Key); 27 | } 28 | else 29 | memcpy(SHA1_Key, key, key_len); 30 | 31 | /* STEP 2 */ 32 | for (int i=0; i<(int)sizeof(m_ipad); i++) 33 | { 34 | m_ipad[i] ^= SHA1_Key[i]; 35 | } 36 | 37 | /* STEP 4 */ 38 | CSHA1::Reset(); 39 | CSHA1::Update((UINT_8 *)m_ipad, sizeof(m_ipad)); 40 | CSHA1::Update((UINT_8 *)text, text_len); 41 | CSHA1::Final(); 42 | 43 | char szReport[SHA1_DIGEST_LENGTH]; 44 | CSHA1::GetHash((UINT_8 *)szReport); 45 | 46 | /* STEP 5 */ 47 | for (int j=0; j<(int)sizeof(m_opad); j++) 48 | { 49 | m_opad[j] ^= SHA1_Key[j]; 50 | } 51 | 52 | /*STEP 7 */ 53 | CSHA1::Reset(); 54 | CSHA1::Update((UINT_8 *)m_opad, sizeof(m_opad)); 55 | CSHA1::Update((UINT_8 *)szReport, SHA1_DIGEST_LENGTH); 56 | CSHA1::Final(); 57 | 58 | CSHA1::GetHash((UINT_8 *)digest); 59 | } 60 | -------------------------------------------------------------------------------- /src/HMAC_SHA1.h: -------------------------------------------------------------------------------- 1 | /* 2 | 100% free public domain implementation of the HMAC-SHA1 algorithm 3 | by Chien-Chung, Chung (Jim Chung) 4 | */ 5 | 6 | 7 | #ifndef __HMAC_SHA1_H__ 8 | #define __HMAC_SHA1_H__ 9 | 10 | #include "SHA1.h" 11 | 12 | typedef unsigned char BYTE ; 13 | 14 | class CHMAC_SHA1 : public CSHA1 15 | { 16 | public: 17 | 18 | enum { 19 | SHA1_DIGEST_LENGTH = 20, 20 | SHA1_BLOCK_SIZE = 64 21 | } ; 22 | 23 | private: 24 | BYTE m_ipad[SHA1_BLOCK_SIZE]; 25 | BYTE m_opad[SHA1_BLOCK_SIZE]; 26 | 27 | // This holds one SHA1 block's worth of data, zero padded if necessary. 28 | char SHA1_Key[SHA1_BLOCK_SIZE]; 29 | 30 | public: 31 | CHMAC_SHA1() {} 32 | 33 | void HMAC_SHA1(BYTE *text, int text_len, BYTE *key, int key_len, BYTE *digest); 34 | }; 35 | 36 | 37 | #endif /* __HMAC_SHA1_H__ */ 38 | -------------------------------------------------------------------------------- /src/SHA1.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | 100% free public domain implementation of the SHA-1 algorithm 3 | by Dominik Reichl 4 | Web: http://www.dominik-reichl.de/ 5 | 6 | Version 1.6 - 2005-02-07 (thanks to Howard Kapustein for patches) 7 | - You can set the endianness in your files, no need to modify the 8 | header file of the CSHA1 class any more 9 | - Aligned data support 10 | - Made support/compilation of the utility functions (ReportHash 11 | and HashFile) optional (useful, if bytes count, for example in 12 | embedded environments) 13 | 14 | Version 1.5 - 2005-01-01 15 | - 64-bit compiler compatibility added 16 | - Made variable wiping optional (define SHA1_WIPE_VARIABLES) 17 | - Removed unnecessary variable initializations 18 | - ROL32 improvement for the Microsoft compiler (using _rotl) 19 | 20 | ======== Test Vectors (from FIPS PUB 180-1) ======== 21 | 22 | SHA1("abc") = 23 | A9993E36 4706816A BA3E2571 7850C26C 9CD0D89D 24 | 25 | SHA1("abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq") = 26 | 84983E44 1C3BD26E BAAE4AA1 F95129E5 E54670F1 27 | 28 | SHA1(A million repetitions of "a") = 29 | 34AA973C D4C4DAA4 F61EEB2B DBAD2731 6534016F 30 | */ 31 | 32 | #include "SHA1.h" 33 | #include 34 | 35 | #ifdef SHA1_UTILITY_FUNCTIONS 36 | #define SHA1_MAX_FILE_BUFFER 8000 37 | #endif 38 | 39 | // Rotate x bits to the left 40 | #ifndef ROL32 41 | #ifdef _MSC_VER 42 | #define ROL32(_val32, _nBits) _rotl(_val32, _nBits) 43 | #else 44 | #define ROL32(_val32, _nBits) (((_val32)<<(_nBits))|((_val32)>>(32-(_nBits)))) 45 | #endif 46 | #endif 47 | 48 | #ifdef SHA1_LITTLE_ENDIAN 49 | #define SHABLK0(i) (m_block->l[i] = \ 50 | (ROL32(m_block->l[i],24) & 0xFF00FF00) | (ROL32(m_block->l[i],8) & 0x00FF00FF)) 51 | #else 52 | #define SHABLK0(i) (m_block->l[i]) 53 | #endif 54 | 55 | #define SHABLK(i) (m_block->l[i&15] = ROL32(m_block->l[(i+13)&15] ^ m_block->l[(i+8)&15] \ 56 | ^ m_block->l[(i+2)&15] ^ m_block->l[i&15],1)) 57 | 58 | // SHA-1 rounds 59 | #define _R0(v,w,x,y,z,i) { z+=((w&(x^y))^y)+SHABLK0(i)+0x5A827999+ROL32(v,5); w=ROL32(w,30); } 60 | #define _R1(v,w,x,y,z,i) { z+=((w&(x^y))^y)+SHABLK(i)+0x5A827999+ROL32(v,5); w=ROL32(w,30); } 61 | #define _R2(v,w,x,y,z,i) { z+=(w^x^y)+SHABLK(i)+0x6ED9EBA1+ROL32(v,5); w=ROL32(w,30); } 62 | #define _R3(v,w,x,y,z,i) { z+=(((w|x)&y)|(w&x))+SHABLK(i)+0x8F1BBCDC+ROL32(v,5); w=ROL32(w,30); } 63 | #define _R4(v,w,x,y,z,i) { z+=(w^x^y)+SHABLK(i)+0xCA62C1D6+ROL32(v,5); w=ROL32(w,30); } 64 | 65 | CSHA1::CSHA1() 66 | { 67 | m_block = (SHA1_WORKSPACE_BLOCK *)m_workspace; 68 | 69 | Reset(); 70 | } 71 | 72 | CSHA1::~CSHA1() 73 | { 74 | Reset(); 75 | } 76 | 77 | void CSHA1::Reset() 78 | { 79 | // SHA1 initialization constants 80 | m_state[0] = 0x67452301; 81 | m_state[1] = 0xEFCDAB89; 82 | m_state[2] = 0x98BADCFE; 83 | m_state[3] = 0x10325476; 84 | m_state[4] = 0xC3D2E1F0; 85 | 86 | m_count[0] = 0; 87 | m_count[1] = 0; 88 | } 89 | 90 | void CSHA1::Transform(UINT_32 *state, UINT_8 *buffer) 91 | { 92 | // Copy state[] to working vars 93 | UINT_32 a = state[0], b = state[1], c = state[2], d = state[3], e = state[4]; 94 | 95 | memcpy(m_block, buffer, 64); 96 | 97 | // 4 rounds of 20 operations each. Loop unrolled. 98 | _R0(a,b,c,d,e, 0); _R0(e,a,b,c,d, 1); _R0(d,e,a,b,c, 2); _R0(c,d,e,a,b, 3); 99 | _R0(b,c,d,e,a, 4); _R0(a,b,c,d,e, 5); _R0(e,a,b,c,d, 6); _R0(d,e,a,b,c, 7); 100 | _R0(c,d,e,a,b, 8); _R0(b,c,d,e,a, 9); _R0(a,b,c,d,e,10); _R0(e,a,b,c,d,11); 101 | _R0(d,e,a,b,c,12); _R0(c,d,e,a,b,13); _R0(b,c,d,e,a,14); _R0(a,b,c,d,e,15); 102 | _R1(e,a,b,c,d,16); _R1(d,e,a,b,c,17); _R1(c,d,e,a,b,18); _R1(b,c,d,e,a,19); 103 | _R2(a,b,c,d,e,20); _R2(e,a,b,c,d,21); _R2(d,e,a,b,c,22); _R2(c,d,e,a,b,23); 104 | _R2(b,c,d,e,a,24); _R2(a,b,c,d,e,25); _R2(e,a,b,c,d,26); _R2(d,e,a,b,c,27); 105 | _R2(c,d,e,a,b,28); _R2(b,c,d,e,a,29); _R2(a,b,c,d,e,30); _R2(e,a,b,c,d,31); 106 | _R2(d,e,a,b,c,32); _R2(c,d,e,a,b,33); _R2(b,c,d,e,a,34); _R2(a,b,c,d,e,35); 107 | _R2(e,a,b,c,d,36); _R2(d,e,a,b,c,37); _R2(c,d,e,a,b,38); _R2(b,c,d,e,a,39); 108 | _R3(a,b,c,d,e,40); _R3(e,a,b,c,d,41); _R3(d,e,a,b,c,42); _R3(c,d,e,a,b,43); 109 | _R3(b,c,d,e,a,44); _R3(a,b,c,d,e,45); _R3(e,a,b,c,d,46); _R3(d,e,a,b,c,47); 110 | _R3(c,d,e,a,b,48); _R3(b,c,d,e,a,49); _R3(a,b,c,d,e,50); _R3(e,a,b,c,d,51); 111 | _R3(d,e,a,b,c,52); _R3(c,d,e,a,b,53); _R3(b,c,d,e,a,54); _R3(a,b,c,d,e,55); 112 | _R3(e,a,b,c,d,56); _R3(d,e,a,b,c,57); _R3(c,d,e,a,b,58); _R3(b,c,d,e,a,59); 113 | _R4(a,b,c,d,e,60); _R4(e,a,b,c,d,61); _R4(d,e,a,b,c,62); _R4(c,d,e,a,b,63); 114 | _R4(b,c,d,e,a,64); _R4(a,b,c,d,e,65); _R4(e,a,b,c,d,66); _R4(d,e,a,b,c,67); 115 | _R4(c,d,e,a,b,68); _R4(b,c,d,e,a,69); _R4(a,b,c,d,e,70); _R4(e,a,b,c,d,71); 116 | _R4(d,e,a,b,c,72); _R4(c,d,e,a,b,73); _R4(b,c,d,e,a,74); _R4(a,b,c,d,e,75); 117 | _R4(e,a,b,c,d,76); _R4(d,e,a,b,c,77); _R4(c,d,e,a,b,78); _R4(b,c,d,e,a,79); 118 | 119 | // Add the working vars back into state 120 | state[0] += a; 121 | state[1] += b; 122 | state[2] += c; 123 | state[3] += d; 124 | state[4] += e; 125 | 126 | // Wipe variables 127 | #ifdef SHA1_WIPE_VARIABLES 128 | a = b = c = d = e = 0; 129 | #endif 130 | } 131 | 132 | // Use this function to hash in binary data and strings 133 | void CSHA1::Update(UINT_8 *data, UINT_32 len) 134 | { 135 | UINT_32 i, j; 136 | 137 | j = (m_count[0] >> 3) & 63; 138 | 139 | if((m_count[0] += len << 3) < (len << 3)) m_count[1]++; 140 | 141 | m_count[1] += (len >> 29); 142 | 143 | if((j + len) > 63) 144 | { 145 | i = 64 - j; 146 | memcpy(&m_buffer[j], data, i); 147 | Transform(m_state, m_buffer); 148 | 149 | for(; i + 63 < len; i += 64) Transform(m_state, &data[i]); 150 | 151 | j = 0; 152 | } 153 | else i = 0; 154 | 155 | memcpy(&m_buffer[j], &data[i], len - i); 156 | } 157 | 158 | #ifdef SHA1_UTILITY_FUNCTIONS 159 | // Hash in file contents 160 | bool CSHA1::HashFile(char *szFileName) 161 | { 162 | unsigned long ulFileSize, ulRest, ulBlocks; 163 | unsigned long i; 164 | UINT_8 uData[SHA1_MAX_FILE_BUFFER]; 165 | FILE *fIn; 166 | 167 | if(szFileName == NULL) return false; 168 | 169 | fIn = fopen(szFileName, "rb"); 170 | if(fIn == NULL) return false; 171 | 172 | fseek(fIn, 0, SEEK_END); 173 | ulFileSize = (unsigned long)ftell(fIn); 174 | fseek(fIn, 0, SEEK_SET); 175 | 176 | if(ulFileSize != 0) 177 | { 178 | ulBlocks = ulFileSize / SHA1_MAX_FILE_BUFFER; 179 | ulRest = ulFileSize % SHA1_MAX_FILE_BUFFER; 180 | } 181 | else 182 | { 183 | ulBlocks = 0; 184 | ulRest = 0; 185 | } 186 | 187 | for(i = 0; i < ulBlocks; i++) 188 | { 189 | size_t nread = fread(uData, 1, SHA1_MAX_FILE_BUFFER, fIn); 190 | assert(nread == SHA1_MAX_FILE_BUFFER); 191 | Update((UINT_8 *)uData, SHA1_MAX_FILE_BUFFER); 192 | } 193 | 194 | if(ulRest != 0) 195 | { 196 | size_t nread = fread(uData, 1, ulRest, fIn); 197 | assert(nread == ulRest); 198 | Update((UINT_8 *)uData, ulRest); 199 | } 200 | 201 | fclose(fIn); fIn = NULL; 202 | return true; 203 | } 204 | #endif 205 | 206 | void CSHA1::Final() 207 | { 208 | UINT_32 i; 209 | UINT_8 finalcount[8]; 210 | 211 | for(i = 0; i < 8; i++) 212 | finalcount[i] = (UINT_8)((m_count[((i >= 4) ? 0 : 1)] 213 | >> ((3 - (i & 3)) * 8) ) & 255); // Endian independent 214 | 215 | Update((UINT_8 *)"\200", 1); 216 | 217 | while ((m_count[0] & 504) != 448) 218 | Update((UINT_8 *)"\0", 1); 219 | 220 | Update(finalcount, 8); // Cause a SHA1Transform() 221 | 222 | for(i = 0; i < 20; i++) 223 | { 224 | m_digest[i] = (UINT_8)((m_state[i >> 2] >> ((3 - (i & 3)) * 8) ) & 255); 225 | } 226 | 227 | // Wipe variables for security reasons 228 | #ifdef SHA1_WIPE_VARIABLES 229 | i = 0; 230 | memset(m_buffer, 0, 64); 231 | memset(m_state, 0, 20); 232 | memset(m_count, 0, 8); 233 | memset(finalcount, 0, 8); 234 | Transform(m_state, m_buffer); 235 | #endif 236 | } 237 | 238 | #ifdef SHA1_UTILITY_FUNCTIONS 239 | // Get the final hash as a pre-formatted string 240 | void CSHA1::ReportHash(char *szReport, unsigned char uReportType) 241 | { 242 | unsigned char i; 243 | char szTemp[16]; 244 | 245 | if(szReport == NULL) return; 246 | 247 | if(uReportType == REPORT_HEX) 248 | { 249 | sprintf(szTemp, "%02X", m_digest[0]); 250 | strcat(szReport, szTemp); 251 | 252 | for(i = 1; i < 20; i++) 253 | { 254 | sprintf(szTemp, " %02X", m_digest[i]); 255 | strcat(szReport, szTemp); 256 | } 257 | } 258 | else if(uReportType == REPORT_DIGIT) 259 | { 260 | sprintf(szTemp, "%u", m_digest[0]); 261 | strcat(szReport, szTemp); 262 | 263 | for(i = 1; i < 20; i++) 264 | { 265 | sprintf(szTemp, " %u", m_digest[i]); 266 | strcat(szReport, szTemp); 267 | } 268 | } 269 | else strcpy(szReport, "Error: Unknown report type!"); 270 | } 271 | #endif 272 | 273 | // Get the raw message digest 274 | void CSHA1::GetHash(UINT_8 *puDest) 275 | { 276 | memcpy(puDest, m_digest, 20); 277 | } 278 | -------------------------------------------------------------------------------- /src/SHA1.h: -------------------------------------------------------------------------------- 1 | /* 2 | 100% free public domain implementation of the SHA-1 algorithm 3 | by Dominik Reichl 4 | Web: http://www.dominik-reichl.de/ 5 | 6 | Version 1.6 - 2005-02-07 (thanks to Howard Kapustein for patches) 7 | - You can set the endianness in your files, no need to modify the 8 | header file of the CSHA1 class any more 9 | - Aligned data support 10 | - Made support/compilation of the utility functions (ReportHash 11 | and HashFile) optional (useful, if bytes count, for example in 12 | embedded environments) 13 | 14 | Version 1.5 - 2005-01-01 15 | - 64-bit compiler compatibility added 16 | - Made variable wiping optional (define SHA1_WIPE_VARIABLES) 17 | - Removed unnecessary variable initializations 18 | - ROL32 improvement for the Microsoft compiler (using _rotl) 19 | 20 | ======== Test Vectors (from FIPS PUB 180-1) ======== 21 | 22 | SHA1("abc") = 23 | A9993E36 4706816A BA3E2571 7850C26C 9CD0D89D 24 | 25 | SHA1("abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq") = 26 | 84983E44 1C3BD26E BAAE4AA1 F95129E5 E54670F1 27 | 28 | SHA1(A million repetitions of "a") = 29 | 34AA973C D4C4DAA4 F61EEB2B DBAD2731 6534016F 30 | */ 31 | 32 | #ifndef ___SHA1_HDR___ 33 | #define ___SHA1_HDR___ 34 | 35 | #if !defined(SHA1_UTILITY_FUNCTIONS) && !defined(SHA1_NO_UTILITY_FUNCTIONS) 36 | #define SHA1_UTILITY_FUNCTIONS 37 | #endif 38 | 39 | #include // Needed for memset and memcpy 40 | 41 | #ifdef SHA1_UTILITY_FUNCTIONS 42 | #include // Needed for file access and sprintf 43 | #include // Needed for strcat and strcpy 44 | #endif 45 | 46 | #ifdef _MSC_VER 47 | #include 48 | #endif 49 | 50 | // You can define the endian mode in your files, without modifying the SHA1 51 | // source files. Just #define SHA1_LITTLE_ENDIAN or #define SHA1_BIG_ENDIAN 52 | // in your files, before including the SHA1.h header file. If you don't 53 | // define anything, the class defaults to little endian. 54 | 55 | #if !defined(SHA1_LITTLE_ENDIAN) && !defined(SHA1_BIG_ENDIAN) 56 | #define SHA1_LITTLE_ENDIAN 57 | #endif 58 | 59 | // Same here. If you want variable wiping, #define SHA1_WIPE_VARIABLES, if 60 | // not, #define SHA1_NO_WIPE_VARIABLES. If you don't define anything, it 61 | // defaults to wiping. 62 | 63 | #if !defined(SHA1_WIPE_VARIABLES) && !defined(SHA1_NO_WIPE_VARIABLES) 64 | #define SHA1_WIPE_VARIABLES 65 | #endif 66 | 67 | ///////////////////////////////////////////////////////////////////////////// 68 | // Define 8- and 32-bit variables 69 | 70 | #ifndef UINT_32 71 | 72 | #ifdef _MSC_VER 73 | 74 | #define UINT_8 unsigned __int8 75 | #define UINT_32 unsigned __int32 76 | 77 | #else 78 | 79 | #define UINT_8 unsigned char 80 | 81 | #if (ULONG_MAX == 0xFFFFFFFF) 82 | #define UINT_32 unsigned long 83 | #else 84 | #define UINT_32 unsigned int 85 | #endif 86 | 87 | #endif 88 | #endif 89 | 90 | ///////////////////////////////////////////////////////////////////////////// 91 | // Declare SHA1 workspace 92 | 93 | typedef union 94 | { 95 | UINT_8 c[64]; 96 | UINT_32 l[16]; 97 | } SHA1_WORKSPACE_BLOCK; 98 | 99 | class CSHA1 100 | { 101 | public: 102 | #ifdef SHA1_UTILITY_FUNCTIONS 103 | // Two different formats for ReportHash(...) 104 | enum 105 | { 106 | REPORT_HEX = 0, 107 | REPORT_DIGIT = 1 108 | }; 109 | #endif 110 | 111 | // Constructor and Destructor 112 | CSHA1(); 113 | ~CSHA1(); 114 | 115 | UINT_32 m_state[5]; 116 | UINT_32 m_count[2]; 117 | UINT_32 __reserved1[1]; 118 | UINT_8 m_buffer[64]; 119 | UINT_8 m_digest[20]; 120 | UINT_32 __reserved2[3]; 121 | 122 | void Reset(); 123 | 124 | // Update the hash value 125 | void Update(UINT_8 *data, UINT_32 len); 126 | #ifdef SHA1_UTILITY_FUNCTIONS 127 | bool HashFile(char *szFileName); 128 | #endif 129 | 130 | // Finalize hash and report 131 | void Final(); 132 | 133 | // Report functions: as pre-formatted and raw data 134 | #ifdef SHA1_UTILITY_FUNCTIONS 135 | void ReportHash(char *szReport, unsigned char uReportType = REPORT_HEX); 136 | #endif 137 | void GetHash(UINT_8 *puDest); 138 | 139 | private: 140 | // Private SHA-1 transformation 141 | void Transform(UINT_32 *state, UINT_8 *buffer); 142 | 143 | // Member variables 144 | UINT_8 m_workspace[64]; 145 | SHA1_WORKSPACE_BLOCK *m_block; // SHA1 pointer to the byte array above 146 | }; 147 | 148 | #endif 149 | -------------------------------------------------------------------------------- /src/base64.cpp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sirikata/liboauthcpp/2893f1bf42e1dfd5acf0dd849519cf27ac9c7395/src/base64.cpp -------------------------------------------------------------------------------- /src/base64.h: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | std::string base64_encode(unsigned char const* , unsigned int len); 4 | std::string base64_decode(std::string const& s); -------------------------------------------------------------------------------- /src/liboauthcpp.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "HMAC_SHA1.h" 3 | #include "base64.h" 4 | #include "urlencode.h" 5 | #include 6 | #include 7 | #include 8 | 9 | namespace OAuth { 10 | 11 | namespace Defaults 12 | { 13 | /* Constants */ 14 | const int BUFFSIZE = 1024; 15 | const int BUFFSIZE_LARGE = 1024; 16 | const std::string CONSUMERKEY_KEY = "oauth_consumer_key"; 17 | const std::string CALLBACK_KEY = "oauth_callback"; 18 | const std::string VERSION_KEY = "oauth_version"; 19 | const std::string SIGNATUREMETHOD_KEY = "oauth_signature_method"; 20 | const std::string SIGNATURE_KEY = "oauth_signature"; 21 | const std::string TIMESTAMP_KEY = "oauth_timestamp"; 22 | const std::string NONCE_KEY = "oauth_nonce"; 23 | const std::string TOKEN_KEY = "oauth_token"; 24 | const std::string TOKENSECRET_KEY = "oauth_token_secret"; 25 | const std::string VERIFIER_KEY = "oauth_verifier"; 26 | 27 | const std::string AUTHHEADER_FIELD = "Authorization: "; 28 | const std::string AUTHHEADER_PREFIX = "OAuth "; 29 | }; 30 | 31 | /** std::string -> std::string conversion function */ 32 | typedef std::string(*StringConvertFunction)(const std::string&); 33 | 34 | LogLevel gLogLevel = LogLevelNone; 35 | 36 | void SetLogLevel(LogLevel lvl) { 37 | gLogLevel = lvl; 38 | } 39 | #define LOG(lvl, msg) \ 40 | do { \ 41 | if (lvl <= gLogLevel) std::cerr << "OAUTH: " << msg << std::endl; \ 42 | } while(0) 43 | 44 | std::string PercentEncode(const std::string& decoded) { 45 | return urlencode(decoded, URLEncode_Everything); 46 | } 47 | 48 | std::string URLEncode(const std::string& decoded) { 49 | return PercentEncode(decoded); 50 | } 51 | 52 | std::string HttpEncodePath(const std::string& decoded) { 53 | return urlencode(decoded, URLEncode_Path); 54 | } 55 | 56 | std::string HttpEncodeQueryKey(const std::string& decoded) { 57 | return urlencode(decoded, URLEncode_QueryKey); 58 | } 59 | 60 | std::string HttpEncodeQueryValue(const std::string& decoded) { 61 | return urlencode(decoded, URLEncode_QueryValue); 62 | } 63 | 64 | namespace { 65 | std::string PassThrough(const std::string& decoded) { 66 | return decoded; 67 | } 68 | 69 | std::string RequestTypeString(const Http::RequestType rt) { 70 | switch(rt) { 71 | case Http::Invalid: return "Invalid Request Type"; break; 72 | case Http::Head: return "HEAD"; break; 73 | case Http::Get: return "GET"; break; 74 | case Http::Post: return "POST"; break; 75 | case Http::Delete: return "DELETE"; break; 76 | case Http::Put: return "PUT"; break; 77 | default: return "Unknown Request Type"; break; 78 | } 79 | return ""; 80 | } 81 | } 82 | 83 | // Parse a single key-value pair 84 | static std::pair ParseKeyValuePair(const std::string& encoded) { 85 | std::size_t eq_pos = encoded.find("="); 86 | if (eq_pos == std::string::npos) 87 | throw ParseError("Failed to find '=' in key-value pair."); 88 | return std::pair( 89 | encoded.substr(0, eq_pos), 90 | encoded.substr(eq_pos+1) 91 | ); 92 | } 93 | 94 | KeyValuePairs ParseKeyValuePairs(const std::string& encoded) { 95 | KeyValuePairs result; 96 | 97 | if (encoded.length() == 0) return result; 98 | 99 | // Split by & 100 | std::size_t last_amp = 0; 101 | // We can bail when the last one "found" was the end of the string 102 | while(true) { 103 | std::size_t next_amp = encoded.find('&', last_amp+1); 104 | std::string keyval = 105 | (next_amp == std::string::npos) ? 106 | encoded.substr(last_amp) : 107 | encoded.substr(last_amp, next_amp-last_amp); 108 | result.insert(ParseKeyValuePair(keyval)); 109 | // Track spot after the & so the first iteration works without dealing 110 | // with -1 index 111 | last_amp = next_amp+1; 112 | 113 | // Exit condition 114 | if (next_amp == std::string::npos) break; 115 | } 116 | return result; 117 | } 118 | 119 | // Helper for parameters in key-value pair lists that should only appear 120 | // once. Either replaces an existing entry or adds a new entry. 121 | static void ReplaceOrInsertKeyValuePair(KeyValuePairs& kvp, const std::string& key, const std::string& value) { 122 | assert(kvp.count(key) <= 1); 123 | KeyValuePairs::iterator it = kvp.find(key); 124 | if (it != kvp.end()) 125 | it->second = value; 126 | else 127 | kvp.insert(KeyValuePairs::value_type(key, value)); 128 | } 129 | 130 | Consumer::Consumer(const std::string& key, const std::string& secret) 131 | : mKey(key), mSecret(secret) 132 | { 133 | } 134 | 135 | 136 | 137 | Token::Token(const std::string& key, const std::string& secret) 138 | : mKey(key), mSecret(secret) 139 | { 140 | } 141 | 142 | Token::Token(const std::string& key, const std::string& secret, const std::string& pin) 143 | : mKey(key), mSecret(secret), mPin(pin) 144 | { 145 | } 146 | 147 | Token Token::extract(const std::string& response) { 148 | return Token::extract(ParseKeyValuePairs(response)); 149 | } 150 | 151 | Token Token::extract(const KeyValuePairs& response) { 152 | std::string token_key, token_secret; 153 | 154 | KeyValuePairs::const_iterator it = response.find(Defaults::TOKEN_KEY); 155 | if (it == response.end()) 156 | throw MissingKeyError("Couldn't find oauth_token in response"); 157 | token_key = it->second; 158 | 159 | it = response.find(Defaults::TOKENSECRET_KEY); 160 | if (it == response.end()) 161 | throw MissingKeyError("Couldn't find oauth_token_secret in response"); 162 | token_secret = it->second; 163 | 164 | return Token(token_key, token_secret); 165 | } 166 | 167 | 168 | bool Client::initialized = false; 169 | int Client::testingNonce = 0; 170 | time_t Client::testingTimestamp = 0; 171 | 172 | void Client::initialize() { 173 | if(!initialized) { 174 | srand( time( NULL ) ); 175 | initialized = true; 176 | } 177 | } 178 | 179 | void Client::initialize(int nonce, time_t timestamp) { 180 | if(!initialized) { 181 | testingNonce = nonce; 182 | testingTimestamp = timestamp; 183 | initialized = true; 184 | } 185 | } 186 | 187 | void Client::__resetInitialize() { 188 | testingNonce = 0; 189 | testingTimestamp = 0; 190 | initialized = false; 191 | } 192 | 193 | Client::Client(const Consumer* consumer) 194 | : mConsumer(consumer), 195 | mToken(NULL) 196 | { 197 | } 198 | 199 | Client::Client(const Consumer* consumer, const Token* token) 200 | : mConsumer(consumer), 201 | mToken(token) 202 | { 203 | } 204 | 205 | 206 | Client::~Client() 207 | { 208 | } 209 | 210 | 211 | 212 | /*++ 213 | * @method: Client::generateNonceTimeStamp 214 | * 215 | * @description: this method generates nonce and timestamp for OAuth header 216 | * 217 | * @input: none 218 | * 219 | * @output: nonce - OAuth header nonce 220 | * timeStamp - timestamp when nonce was generated 221 | * 222 | * @remarks: internal method 223 | * 224 | *--*/ 225 | void Client::generateNonceTimeStamp(std::string& nonce, std::string& timeStamp) const 226 | { 227 | // Make sure the random seed has been initialized 228 | Client::initialize(); 229 | 230 | char szTime[Defaults::BUFFSIZE]; 231 | char szRand[Defaults::BUFFSIZE]; 232 | memset( szTime, 0, Defaults::BUFFSIZE ); 233 | memset( szRand, 0, Defaults::BUFFSIZE ); 234 | 235 | // Any non-zero timestamp triggers testing mode with fixed values. Fixing 236 | // both values makes life easier because generating a signature is 237 | // idempotent -- otherwise using macros can cause double evaluation and 238 | // incorrect results because of repeated calls to rand(). 239 | sprintf( szRand, "%x", ((testingTimestamp != 0) ? testingNonce : rand()) ); 240 | sprintf( szTime, "%ld", ((testingTimestamp != 0) ? testingTimestamp : time( NULL )) ); 241 | 242 | nonce.assign( szTime ); 243 | nonce.append( szRand ); 244 | 245 | timeStamp.assign( szTime ); 246 | } 247 | 248 | /*++ 249 | * @method: Client::buildOAuthTokenKeyValuePairs 250 | * 251 | * @description: this method prepares key-value pairs required for OAuth header 252 | * and signature generation. 253 | * 254 | * @input: includeOAuthVerifierPin - flag to indicate whether oauth_verifer key-value 255 | * pair needs to be included. oauth_verifer is only 256 | * used during exchanging request token with access token. 257 | * rawData - url encoded data. this is used during signature generation. 258 | * oauthSignature - base64 and url encoded OAuth signature. 259 | * nonce - OAuth nonce to use 260 | * timeStamp - timestamp when nonce was generated 261 | * 262 | * @input: urlEncodeValues - if true, URLEncode the values inserted into the 263 | * output keyValueMap 264 | * @output: keyValueMap - map in which key-value pairs are populated 265 | * 266 | * @remarks: internal method 267 | * 268 | *--*/ 269 | bool Client::buildOAuthTokenKeyValuePairs( const bool includeOAuthVerifierPin, 270 | const std::string& rawData, 271 | const std::string& oauthSignature, 272 | KeyValuePairs& keyValueMap, 273 | const bool urlEncodeValues, 274 | const std::string& nonce, 275 | const std::string& timeStamp) const 276 | { 277 | // Encodes value part of key-value pairs depending on type of output (query 278 | // string vs. HTTP headers. 279 | StringConvertFunction value_encoder = (urlEncodeValues ? HttpEncodeQueryValue : PassThrough); 280 | 281 | /* Consumer key and its value */ 282 | ReplaceOrInsertKeyValuePair(keyValueMap, Defaults::CONSUMERKEY_KEY, value_encoder(mConsumer->key())); 283 | 284 | /* Nonce key and its value */ 285 | ReplaceOrInsertKeyValuePair(keyValueMap, Defaults::NONCE_KEY, value_encoder(nonce)); 286 | 287 | /* Signature if supplied */ 288 | if( oauthSignature.length() ) 289 | { 290 | // Signature is exempt from encoding. The procedure for 291 | // computing it already percent-encodes it as required by the 292 | // spec for both query string and Auth header 293 | // methods. Therefore, it's pass-through in both cases. 294 | ReplaceOrInsertKeyValuePair(keyValueMap, Defaults::SIGNATURE_KEY, oauthSignature); 295 | } 296 | 297 | /* Signature method, only HMAC-SHA1 as of now */ 298 | ReplaceOrInsertKeyValuePair(keyValueMap, Defaults::SIGNATUREMETHOD_KEY, std::string( "HMAC-SHA1" )); 299 | 300 | /* Timestamp */ 301 | ReplaceOrInsertKeyValuePair(keyValueMap, Defaults::TIMESTAMP_KEY, value_encoder(timeStamp)); 302 | 303 | /* Token */ 304 | if( mToken && mToken->key().length() ) 305 | { 306 | ReplaceOrInsertKeyValuePair(keyValueMap, Defaults::TOKEN_KEY, value_encoder(mToken->key())); 307 | } 308 | 309 | /* Verifier */ 310 | if( includeOAuthVerifierPin && mToken && mToken->pin().length() ) 311 | { 312 | ReplaceOrInsertKeyValuePair(keyValueMap, Defaults::VERIFIER_KEY, value_encoder(mToken->pin())); 313 | } 314 | 315 | /* Version */ 316 | ReplaceOrInsertKeyValuePair(keyValueMap, Defaults::VERSION_KEY, std::string( "1.0" )); 317 | 318 | /* Data if it's present */ 319 | if( rawData.length() ) 320 | { 321 | KeyValuePairs encodedPairs = ParseKeyValuePairs(rawData); 322 | keyValueMap.insert(encodedPairs.begin(), encodedPairs.end()); 323 | } 324 | 325 | return ( keyValueMap.size() ) ? true : false; 326 | } 327 | 328 | /*++ 329 | * @method: Client::getSignature 330 | * 331 | * @description: this method calculates HMAC-SHA1 signature of OAuth header 332 | * 333 | * @input: eType - HTTP request type 334 | * rawUrl - raw url of the HTTP request 335 | * rawKeyValuePairs - key-value pairs containing OAuth headers and HTTP data 336 | * 337 | * @output: oAuthSignature - base64 and url encoded signature 338 | * 339 | * @remarks: internal method 340 | * 341 | *--*/ 342 | bool Client::getSignature( const Http::RequestType eType, 343 | const std::string& rawUrl, 344 | const KeyValuePairs& rawKeyValuePairs, 345 | std::string& oAuthSignature ) const 346 | { 347 | std::string rawParams; 348 | std::string paramsSeperator; 349 | std::string sigBase; 350 | 351 | /* Initially empty signature */ 352 | oAuthSignature.assign( "" ); 353 | 354 | /* Build a string using key-value pairs */ 355 | paramsSeperator = "&"; 356 | getStringFromOAuthKeyValuePairs( rawKeyValuePairs, rawParams, paramsSeperator ); 357 | LOG(LogLevelDebug, "Normalized parameters: " << rawParams); 358 | 359 | /* Start constructing base signature string. Refer http://dev.twitter.com/auth#intro */ 360 | switch( eType ) 361 | { 362 | case Http::Head: 363 | { 364 | sigBase.assign( "HEAD&" ); 365 | } 366 | break; 367 | 368 | case Http::Get: 369 | { 370 | sigBase.assign( "GET&" ); 371 | } 372 | break; 373 | 374 | case Http::Post: 375 | { 376 | sigBase.assign( "POST&" ); 377 | } 378 | break; 379 | 380 | case Http::Delete: 381 | { 382 | sigBase.assign( "DELETE&" ); 383 | } 384 | break; 385 | 386 | case Http::Put: 387 | { 388 | sigBase.assign( "PUT&" ); 389 | } 390 | break; 391 | 392 | default: 393 | { 394 | return false; 395 | } 396 | break; 397 | } 398 | sigBase.append( PercentEncode( rawUrl ) ); 399 | sigBase.append( "&" ); 400 | sigBase.append( PercentEncode( rawParams ) ); 401 | LOG(LogLevelDebug, "Signature base string: " << sigBase); 402 | 403 | /* Now, hash the signature base string using HMAC_SHA1 class */ 404 | CHMAC_SHA1 objHMACSHA1; 405 | std::string secretSigningKey; 406 | unsigned char strDigest[Defaults::BUFFSIZE_LARGE]; 407 | 408 | memset( strDigest, 0, Defaults::BUFFSIZE_LARGE ); 409 | 410 | /* Signing key is composed of consumer_secret&token_secret */ 411 | secretSigningKey.assign( PercentEncode(mConsumer->secret()) ); 412 | secretSigningKey.append( "&" ); 413 | if( mToken && mToken->secret().length() ) 414 | { 415 | secretSigningKey.append( PercentEncode(mToken->secret()) ); 416 | } 417 | 418 | objHMACSHA1.HMAC_SHA1( (unsigned char*)sigBase.c_str(), 419 | sigBase.length(), 420 | (unsigned char*)secretSigningKey.c_str(), 421 | secretSigningKey.length(), 422 | strDigest ); 423 | 424 | /* Do a base64 encode of signature */ 425 | std::string base64Str = base64_encode( strDigest, 20 /* SHA 1 digest is 160 bits */ ); 426 | LOG(LogLevelDebug, "Signature: " << base64Str); 427 | 428 | /* Do an url encode */ 429 | oAuthSignature = PercentEncode( base64Str ); 430 | LOG(LogLevelDebug, "Percent-encoded Signature: " << oAuthSignature); 431 | 432 | return ( oAuthSignature.length() ) ? true : false; 433 | } 434 | 435 | std::string Client::getHttpHeader(const Http::RequestType eType, 436 | const std::string& rawUrl, 437 | const std::string& rawData, 438 | const bool includeOAuthVerifierPin) const 439 | { 440 | return Defaults::AUTHHEADER_PREFIX + buildOAuthParameterString(AuthorizationHeaderString, eType, rawUrl, rawData, includeOAuthVerifierPin); 441 | } 442 | 443 | std::string Client::getFormattedHttpHeader(const Http::RequestType eType, 444 | const std::string& rawUrl, 445 | const std::string& rawData, 446 | const bool includeOAuthVerifierPin) const 447 | { 448 | return Defaults::AUTHHEADER_FIELD + Defaults::AUTHHEADER_PREFIX + buildOAuthParameterString(AuthorizationHeaderString, eType, rawUrl, rawData, includeOAuthVerifierPin); 449 | } 450 | 451 | std::string Client::getURLQueryString(const Http::RequestType eType, 452 | const std::string& rawUrl, 453 | const std::string& rawData, 454 | const bool includeOAuthVerifierPin) const 455 | { 456 | return buildOAuthParameterString(QueryStringString, eType, rawUrl, rawData, includeOAuthVerifierPin); 457 | } 458 | 459 | std::string Client::buildOAuthParameterString( 460 | ParameterStringType string_type, 461 | const Http::RequestType eType, 462 | const std::string& rawUrl, 463 | const std::string& rawData, 464 | const bool includeOAuthVerifierPin) const 465 | { 466 | KeyValuePairs rawKeyValuePairs; 467 | std::string rawParams; 468 | std::string oauthSignature; 469 | std::string paramsSeperator; 470 | std::string pureUrl( rawUrl ); 471 | 472 | LOG(LogLevelDebug, "Signing request " << RequestTypeString(eType) << " " << rawUrl << " " << rawData); 473 | 474 | std::string separator; 475 | bool do_urlencode; 476 | if (string_type == AuthorizationHeaderString) { 477 | separator = ","; 478 | do_urlencode = false; 479 | } 480 | else { // QueryStringString 481 | separator = "&"; 482 | do_urlencode = true; 483 | } 484 | 485 | /* Clear header string initially */ 486 | rawKeyValuePairs.clear(); 487 | 488 | /* If URL itself contains ?key=value, then extract and put them in map */ 489 | size_t nPos = rawUrl.find_first_of( "?" ); 490 | if( std::string::npos != nPos ) 491 | { 492 | /* Get only URL */ 493 | pureUrl = rawUrl.substr( 0, nPos ); 494 | 495 | /* Get only key=value data part */ 496 | std::string dataPart = rawUrl.substr( nPos + 1 ); 497 | rawKeyValuePairs = ParseKeyValuePairs(dataPart); 498 | } 499 | 500 | // NOTE: We always request URL encoding on the first pass so that the 501 | // signature generation works properly. This *relies* on 502 | // buildOAuthTokenKeyValuePairs overwriting values when we do the second 503 | // pass to get the values in the form we actually want. The signature and 504 | // rawdata are the only things that change, but the signature is only used 505 | // in the second pass and the rawdata is already encoded, regardless of 506 | // request type. 507 | std::string nonce; 508 | std::string timeStamp; 509 | 510 | generateNonceTimeStamp(nonce, timeStamp); 511 | 512 | /* Build key-value pairs needed for OAuth request token, without signature */ 513 | buildOAuthTokenKeyValuePairs( includeOAuthVerifierPin, rawData, std::string( "" ), rawKeyValuePairs, true, nonce, timeStamp ); 514 | 515 | /* Get url encoded base64 signature using request type, url and parameters */ 516 | getSignature( eType, pureUrl, rawKeyValuePairs, oauthSignature ); 517 | 518 | /* Now, again build key-value pairs with signature this time */ 519 | buildOAuthTokenKeyValuePairs( includeOAuthVerifierPin, std::string( "" ), oauthSignature, rawKeyValuePairs, do_urlencode, nonce, timeStamp ); 520 | 521 | /* Get OAuth header in string format. If we're getting the Authorization 522 | * header, we need to filter out other parameters. 523 | */ 524 | if (string_type == AuthorizationHeaderString) { 525 | KeyValuePairs oauthKeyValuePairs; 526 | std::vector oauth_keys; 527 | oauth_keys.push_back(Defaults::CONSUMERKEY_KEY); 528 | oauth_keys.push_back(Defaults::NONCE_KEY); 529 | oauth_keys.push_back(Defaults::SIGNATURE_KEY); 530 | oauth_keys.push_back(Defaults::SIGNATUREMETHOD_KEY); 531 | oauth_keys.push_back(Defaults::TIMESTAMP_KEY); 532 | oauth_keys.push_back(Defaults::TOKEN_KEY); 533 | oauth_keys.push_back(Defaults::VERIFIER_KEY); 534 | oauth_keys.push_back(Defaults::VERSION_KEY); 535 | 536 | for(size_t i = 0; i < oauth_keys.size(); i++) { 537 | assert(rawKeyValuePairs.count(oauth_keys[i]) <= 1); 538 | KeyValuePairs::iterator oauth_key_it = rawKeyValuePairs.find(oauth_keys[i]); 539 | if (oauth_key_it != rawKeyValuePairs.end()) 540 | ReplaceOrInsertKeyValuePair(oauthKeyValuePairs, oauth_keys[i], oauth_key_it->second); 541 | } 542 | getStringFromOAuthKeyValuePairs( oauthKeyValuePairs, rawParams, separator ); 543 | } 544 | else if (string_type == QueryStringString) { 545 | getStringFromOAuthKeyValuePairs( rawKeyValuePairs, rawParams, separator ); 546 | } 547 | 548 | /* Build authorization header */ 549 | return rawParams; 550 | } 551 | 552 | /*++ 553 | * @method: Client::getStringFromOAuthKeyValuePairs 554 | * 555 | * @description: this method builds a sorted string from key-value pairs 556 | * 557 | * @input: rawParamMap - key-value pairs map 558 | * paramsSeperator - sepearator, either & or , 559 | * 560 | * @output: rawParams - sorted string of OAuth parameters 561 | * 562 | * @remarks: internal method 563 | * 564 | *--*/ 565 | bool Client::getStringFromOAuthKeyValuePairs( const KeyValuePairs& rawParamMap, 566 | std::string& rawParams, 567 | const std::string& paramsSeperator ) const 568 | { 569 | rawParams.assign( "" ); 570 | if( rawParamMap.size() ) 571 | { 572 | KeyValueList keyValueList; 573 | std::string dummyStr; 574 | 575 | /* Push key-value pairs to a list of strings */ 576 | keyValueList.clear(); 577 | KeyValuePairs::const_iterator itMap = rawParamMap.begin(); 578 | for( ; itMap != rawParamMap.end(); itMap++ ) 579 | { 580 | dummyStr.assign( itMap->first ); 581 | dummyStr.append( "=" ); 582 | if( paramsSeperator == "," ) 583 | { 584 | dummyStr.append( "\"" ); 585 | } 586 | dummyStr.append( itMap->second ); 587 | if( paramsSeperator == "," ) 588 | { 589 | dummyStr.append( "\"" ); 590 | } 591 | keyValueList.push_back( dummyStr ); 592 | } 593 | 594 | /* Sort key-value pairs based on key name */ 595 | keyValueList.sort(); 596 | 597 | /* Now, form a string */ 598 | dummyStr.assign( "" ); 599 | KeyValueList::iterator itKeyValue = keyValueList.begin(); 600 | for( ; itKeyValue != keyValueList.end(); itKeyValue++ ) 601 | { 602 | if( dummyStr.length() ) 603 | { 604 | dummyStr.append( paramsSeperator ); 605 | } 606 | dummyStr.append( itKeyValue->c_str() ); 607 | } 608 | rawParams.assign( dummyStr ); 609 | } 610 | return ( rawParams.length() ) ? true : false; 611 | } 612 | 613 | 614 | } // namespace OAuth 615 | -------------------------------------------------------------------------------- /src/urlencode.cpp: -------------------------------------------------------------------------------- 1 | #include "urlencode.h" 2 | #include 3 | #include 4 | #include 5 | 6 | inline bool isUnreserved(char c) 7 | { 8 | switch (c) 9 | { 10 | case '0': case '1': case '2': case '3': case '4': 11 | case '5': case '6': case '7': case '8': case '9': 12 | 13 | case 'A': case 'B': case 'C': case 'D': case 'E': 14 | case 'F': case 'G': case 'H': case 'I': case 'J': 15 | case 'K': case 'L': case 'M': case 'N': case 'O': 16 | case 'P': case 'Q': case 'R': case 'S': case 'T': 17 | case 'U': case 'V': case 'W': case 'X': case 'Y': 18 | case 'Z': 19 | 20 | case 'a': case 'b': case 'c': case 'd': case 'e': 21 | case 'f': case 'g': case 'h': case 'i': case 'j': 22 | case 'k': case 'l': case 'm': case 'n': case 'o': 23 | case 'p': case 'q': case 'r': case 's': case 't': 24 | case 'u': case 'v': case 'w': case 'x': case 'y': 25 | case 'z': 26 | 27 | case '-': case '.': case '_': case '~': 28 | return true; 29 | 30 | default: 31 | return false; 32 | } 33 | } 34 | 35 | inline bool isSubDelim(char c) 36 | { 37 | switch (c) 38 | { 39 | case '!': case '$': case '&': case '\'': case '(': 40 | case ')': case '*': case '+': case ',': case ';': 41 | case '=': 42 | return true; 43 | 44 | default: 45 | return false; 46 | } 47 | } 48 | 49 | std::string char2hex( char dec ) 50 | { 51 | char dig1 = (dec&0xF0)>>4; 52 | char dig2 = (dec&0x0F); 53 | if ( 0<= dig1 && dig1<= 9) dig1+=48; //0,48 in ascii 54 | if (10<= dig1 && dig1<=15) dig1+=65-10; //A,65 in ascii 55 | if ( 0<= dig2 && dig2<= 9) dig2+=48; 56 | if (10<= dig2 && dig2<=15) dig2+=65-10; 57 | 58 | std::string r; 59 | r.append( &dig1, 1); 60 | r.append( &dig2, 1); 61 | return r; 62 | } 63 | 64 | std::string urlencode( const std::string &s, URLEncodeType enctype) 65 | { 66 | std::stringstream escaped; 67 | 68 | std::string::const_iterator itStr = s.begin(); 69 | for (; itStr != s.end(); ++itStr) 70 | { 71 | char c = *itStr; 72 | 73 | // Unreserved chars - never percent-encoded 74 | if (isUnreserved(c)) 75 | { 76 | escaped << c; 77 | continue; 78 | } 79 | 80 | // Further on, the encoding depends on the context (where in the 81 | // URI we are, what type of URI, and which character). 82 | switch (enctype) 83 | { 84 | case URLEncode_Path: 85 | if (isSubDelim(c)) 86 | { 87 | escaped << c; 88 | continue; 89 | } 90 | /* fall-through */ 91 | 92 | case URLEncode_Everything: 93 | escaped << '%' << char2hex(c); 94 | break; 95 | 96 | default: 97 | assert(false && "Unknown urlencode type"); 98 | break; 99 | } 100 | } 101 | 102 | return escaped.str(); 103 | } 104 | -------------------------------------------------------------------------------- /src/urlencode.h: -------------------------------------------------------------------------------- 1 | #ifndef __URLENCODE_H__ 2 | #define __URLENCODE_H__ 3 | 4 | #include 5 | #include 6 | 7 | std::string char2hex( char dec ); 8 | enum URLEncodeType { 9 | URLEncode_Everything, 10 | URLEncode_Path, 11 | URLEncode_QueryKey = URLEncode_Everything, /* bkwds compatibility */ 12 | URLEncode_QueryValue = URLEncode_Everything, 13 | }; 14 | std::string urlencode( const std::string &s, URLEncodeType enctype ); 15 | 16 | #endif // __URLENCODE_H__ 17 | -------------------------------------------------------------------------------- /tests/fast_request_test.h: -------------------------------------------------------------------------------- 1 | #ifndef __LIBOAUTHCPP_FAST_REQUEST_TEST_H__ 2 | #define __LIBOAUTHCPP_FAST_REQUEST_TEST_H__ 3 | 4 | #include "testutil.h" 5 | #include 6 | 7 | using namespace OAuth; 8 | 9 | namespace OAuthTest { 10 | 11 | /** Tests possible issues with fast, repeated use. 12 | **/ 13 | class FastRequestTest { 14 | public: 15 | static void run() { 16 | repeated_nonce_test(); 17 | } 18 | 19 | /** Tries to check that nonces will not be reused. This tries to make sure 20 | * we that we don't rely only on timestamps to generate unique. 21 | */ 22 | static void repeated_nonce_test() { 23 | std::string consumer_key = "wwwwxxxxyyyyzzzz"; 24 | std::string consumer_secret = "zzzzyyyyxxxxwwww"; 25 | OAuth::Consumer consumer(consumer_key, consumer_secret); 26 | 27 | std::string oauth_token = "aaaabbbbccccdddd"; 28 | std::string oauth_token_secret = "ddddccccbbbbaaaa"; 29 | OAuth::Token token(oauth_token, oauth_token_secret); 30 | 31 | // We can't set the values here or else we'll always use the same 32 | // values. Instead, we initialize normally and run a large number of 33 | // identical requests. We save the info from each, then later extract 34 | // the nonces and verify that we don't have any repeats. 35 | Client::__resetInitialize(); 36 | Client::initialize(); 37 | OAuth::Client oauth(&consumer, &token); 38 | 39 | std::string resource_arg = "arg=foo"; 40 | std::string resource = "resource?" + resource_arg; 41 | 42 | std::vector queries; 43 | // Just save the output here so we can generate as many as quickly as we 44 | // can, hopefully increasing the likelihood of duplicates. 45 | int ntests = 1000; 46 | for(int i = 0; i < ntests; i++) { 47 | queries.push_back(oauth.getURLQueryString(OAuth::Http::Head, resource)); 48 | } 49 | 50 | // Then process them all, extracting and counting the nonces. 51 | std::map nonces; 52 | for(int i = 0; i < ntests; i++) { 53 | OAuth::KeyValuePairs kv = OAuth::ParseKeyValuePairs(queries[i]); 54 | ASSERT_EQUAL(kv.count("oauth_nonce"), 1, "oauth_nonce should appear exactly once in a generated query string"); 55 | std::string nonce_val = kv.find("oauth_nonce")->second; 56 | if (nonces.find(nonce_val) == nonces.end()) 57 | nonces[nonce_val] = 1; 58 | else 59 | nonces[nonce_val]++; 60 | } 61 | for(std::map::iterator it = nonces.begin(); it != nonces.end(); it++) { 62 | ASSERT_EQUAL(1, it->second, "Found repeated nonce"); 63 | } 64 | } 65 | }; 66 | 67 | } 68 | 69 | #endif 70 | -------------------------------------------------------------------------------- /tests/long_request_test.h: -------------------------------------------------------------------------------- 1 | #ifndef __LIBOAUTHCPP_LONG_REQUEST_TEST_H__ 2 | #define __LIBOAUTHCPP_LONG_REQUEST_TEST_H__ 3 | 4 | #include "testutil.h" 5 | #include 6 | 7 | using namespace OAuth; 8 | 9 | namespace OAuthTest { 10 | 11 | /** Tests long requests -- longer than a normal request such that they can cause 12 | * problems if buffers are not big enough, etc. 13 | **/ 14 | class LongRequestTest { 15 | public: 16 | static void run() { 17 | std::string consumer_key = "wwwwxxxxyyyyzzzz"; 18 | std::string consumer_secret = "zzzzyyyyxxxxwwww"; 19 | OAuth::Consumer consumer(consumer_key, consumer_secret); 20 | 21 | std::string oauth_token = "aaaabbbbccccdddd"; 22 | std::string oauth_token_secret = "ddddccccbbbbaaaa"; 23 | OAuth::Token token(oauth_token, oauth_token_secret); 24 | 25 | // This sets up the client class to generate reproducible results. 26 | Client::__resetInitialize(); 27 | Client::initialize(100, 1390268986); 28 | OAuth::Client oauth(&consumer, &token); 29 | 30 | // Generate a very long resource by adding very long parameters 31 | std::string resource_arg = "arg=" + 32 | std::string(16384, 'x') // 16K long 33 | ; 34 | std::string resource = "resource?" + resource_arg; 35 | 36 | // Test all request types, simple, unreserved chars in resource name, no parameters 37 | ASSERT_EQUAL( 38 | oauth.getURLQueryString(OAuth::Http::Head, resource), 39 | resource_arg + "&oauth_consumer_key=wwwwxxxxyyyyzzzz&oauth_nonce=139026898664&oauth_signature=YnNugcEr0E4TDgkzR4ZFMFoHEgU%3D&oauth_signature_method=HMAC-SHA1&oauth_timestamp=1390268986&oauth_token=aaaabbbbccccdddd&oauth_version=1.0", 40 | "Validate long HEAD request signature" 41 | ); 42 | ASSERT_EQUAL( 43 | oauth.getURLQueryString(OAuth::Http::Get, resource), 44 | resource_arg + "&oauth_consumer_key=wwwwxxxxyyyyzzzz&oauth_nonce=139026898664&oauth_signature=5weTspQ0eMH5dFMDdsrGZlNrfPk%3D&oauth_signature_method=HMAC-SHA1&oauth_timestamp=1390268986&oauth_token=aaaabbbbccccdddd&oauth_version=1.0", 45 | "Validate long GET request signature" 46 | ); 47 | ASSERT_EQUAL( 48 | oauth.getURLQueryString(OAuth::Http::Post, resource), 49 | resource_arg + "&oauth_consumer_key=wwwwxxxxyyyyzzzz&oauth_nonce=139026898664&oauth_signature=thJwo%2ByzdRtxwrBqDXRCo2a1mcY%3D&oauth_signature_method=HMAC-SHA1&oauth_timestamp=1390268986&oauth_token=aaaabbbbccccdddd&oauth_version=1.0", 50 | "Validate long POST request signature" 51 | ); 52 | ASSERT_EQUAL( 53 | oauth.getURLQueryString(OAuth::Http::Delete, resource), 54 | resource_arg + "&oauth_consumer_key=wwwwxxxxyyyyzzzz&oauth_nonce=139026898664&oauth_signature=ONjZansHtzGD57pZ9S65s0a6aXs%3D&oauth_signature_method=HMAC-SHA1&oauth_timestamp=1390268986&oauth_token=aaaabbbbccccdddd&oauth_version=1.0", 55 | "Validate long DELETE request signature" 56 | ); 57 | ASSERT_EQUAL( 58 | oauth.getURLQueryString(OAuth::Http::Put, resource), 59 | resource_arg + "&oauth_consumer_key=wwwwxxxxyyyyzzzz&oauth_nonce=139026898664&oauth_signature=6FFgNsTsCl8ABh9i93rRN1m3csE%3D&oauth_signature_method=HMAC-SHA1&oauth_timestamp=1390268986&oauth_token=aaaabbbbccccdddd&oauth_version=1.0", 60 | "Validate long PUT request signature" 61 | ); 62 | } 63 | }; 64 | 65 | } 66 | 67 | #endif 68 | -------------------------------------------------------------------------------- /tests/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "testutil.h" 3 | #include "urlencode_test.h" 4 | #include "parsekeyvaluepairs_test.h" 5 | #include "request_test.h" 6 | #include "request_test.h" 7 | #include "long_request_test.h" 8 | #include "fast_request_test.h" 9 | 10 | using namespace OAuthTest; 11 | 12 | int main(int argc, char** argv) { 13 | URLEncodeTest::run(); 14 | ParseKeyValuePairsTest::run(); 15 | RequestTest::run(); 16 | LongRequestTest::run(); 17 | FastRequestTest::run(); 18 | 19 | return TestUtil::summary(); 20 | } 21 | -------------------------------------------------------------------------------- /tests/parsekeyvaluepairs_test.h: -------------------------------------------------------------------------------- 1 | #ifndef __LIBOAUTHCPP_PARSEKEYVALUEPAIRS_TEST_H__ 2 | #define __LIBOAUTHCPP_PARSEKEYVALUEPAIRS_TEST_H__ 3 | 4 | #include "testutil.h" 5 | #include 6 | 7 | using namespace OAuth; 8 | 9 | namespace OAuthTest { 10 | 11 | /** Tests parsing of KeyValuePairs into *encoded* parts. 12 | **/ 13 | class ParseKeyValuePairsTest { 14 | public: 15 | static void run() { 16 | test_valid(); 17 | test_invalid(); 18 | } 19 | 20 | static void test_valid() { 21 | // Empty 22 | KeyValuePairs pairs = ParseKeyValuePairs(""); 23 | ASSERT_EQUAL(pairs.size(), 0, "Empty string should result in empty key-value pairs"); 24 | 25 | // 1 parameter 26 | pairs = ParseKeyValuePairs("foo=bar"); 27 | ASSERT_EQUAL(pairs.size(), 1, "Single parameter key-value pair should result in one entry"); 28 | ASSERT_IN("foo", pairs, "foo should be in dictionary"); 29 | ASSERT_EQUAL(pairs.find("foo")->second, "bar", "foo should equal bar"); 30 | 31 | // 2 parameters 32 | pairs = ParseKeyValuePairs("foo=bar&baz=bam"); 33 | ASSERT_EQUAL(pairs.size(), 2, "Two parameters key-value pair should result in two entries"); 34 | ASSERT_IN("foo", pairs, "foo should be in dictionary"); 35 | ASSERT_EQUAL(pairs.find("foo")->second, "bar", "foo should equal bar"); 36 | ASSERT_IN("baz", pairs, "baz should be in dictionary"); 37 | ASSERT_EQUAL(pairs.find("baz")->second, "bam", "baz should equal bam"); 38 | 39 | // 3 parameters, 1 repeated twice 40 | pairs = ParseKeyValuePairs("foo=bar&baz=bam&foo=tar"); 41 | ASSERT_EQUAL(pairs.size(), 3, "Three parameters key-value pair should result in three entries"); 42 | ASSERT_IN("foo", pairs, "foo should be in dictionary"); 43 | ASSERT_EQUAL(pairs.find("foo")->second, "bar", "foo should equal bar"); 44 | ASSERT_EQUAL((++pairs.find("foo"))->first, "foo", "foo should occur twice"); 45 | ASSERT_EQUAL((++pairs.find("foo"))->second, "tar", "second foo should equal tar"); 46 | ASSERT_IN("baz", pairs, "baz should be in dictionary"); 47 | ASSERT_EQUAL(pairs.find("baz")->second, "bam", "baz should equal bam"); 48 | 49 | // Single parameter, special chars 50 | pairs = ParseKeyValuePairs("foo=bar%20%3D"); 51 | ASSERT_EQUAL(pairs.size(), 1, "Single parameter key-value pair should result in one entry"); 52 | ASSERT_IN("foo", pairs, "foo should be in dictionary"); 53 | ASSERT_EQUAL(pairs.find("foo")->second, "bar%20%3D", "foo should equal bar%20%3D"); 54 | } 55 | 56 | static void test_invalid() { 57 | ASSERT_THROWS(ParseKeyValuePairs("foo=bar&"), ParseError, "Ampersand followed by nothing should cause parse error"); 58 | ASSERT_THROWS(ParseKeyValuePairs("foo=bar&baz"), ParseError, "Ampersand followed by only key should cause parse error"); 59 | } 60 | }; 61 | 62 | } 63 | 64 | #endif 65 | -------------------------------------------------------------------------------- /tests/request_test.h: -------------------------------------------------------------------------------- 1 | #ifndef __LIBOAUTHCPP_REQUEST_TEST_H__ 2 | #define __LIBOAUTHCPP_REQUEST_TEST_H__ 3 | 4 | #include "testutil.h" 5 | #include 6 | 7 | using namespace OAuth; 8 | 9 | namespace OAuthTest { 10 | 11 | /** Tests simple requests given you already have an access key/token. 12 | **/ 13 | class RequestTest { 14 | public: 15 | static void run() { 16 | std::string consumer_key = "wwwwxxxxyyyyzzzz"; 17 | std::string consumer_secret = "zzzzyyyyxxxxwwww"; 18 | OAuth::Consumer consumer(consumer_key, consumer_secret); 19 | 20 | std::string oauth_token = "aaaabbbbccccdddd"; 21 | std::string oauth_token_secret = "ddddccccbbbbaaaa"; 22 | OAuth::Token token(oauth_token, oauth_token_secret); 23 | 24 | // This sets up the client class to generate reproducible results. 25 | Client::__resetInitialize(); 26 | Client::initialize(100, 1390268986); 27 | OAuth::Client oauth(&consumer, &token); 28 | 29 | // Test all request types, simple, unreserved chars in resource name, no parameters 30 | ASSERT_EQUAL( 31 | oauth.getURLQueryString(OAuth::Http::Head, "resource"), 32 | "oauth_consumer_key=wwwwxxxxyyyyzzzz&oauth_nonce=139026898664&oauth_signature=4J4T69RentaaJJ6R5nzlnidLzSY%3D&oauth_signature_method=HMAC-SHA1&oauth_timestamp=1390268986&oauth_token=aaaabbbbccccdddd&oauth_version=1.0", 33 | "Validate simple HEAD request signature" 34 | ); 35 | ASSERT_EQUAL( 36 | oauth.getURLQueryString(OAuth::Http::Get, "resource"), 37 | "oauth_consumer_key=wwwwxxxxyyyyzzzz&oauth_nonce=139026898664&oauth_signature=Wo8bolvRL6CC2MyJEP7zkjtMvvs%3D&oauth_signature_method=HMAC-SHA1&oauth_timestamp=1390268986&oauth_token=aaaabbbbccccdddd&oauth_version=1.0", 38 | "Validate simple GET request signature" 39 | ); 40 | ASSERT_EQUAL( 41 | oauth.getURLQueryString(OAuth::Http::Post, "resource"), 42 | "oauth_consumer_key=wwwwxxxxyyyyzzzz&oauth_nonce=139026898664&oauth_signature=0rv2KvH0O6DWbCrIb5Ra3QCH6lk%3D&oauth_signature_method=HMAC-SHA1&oauth_timestamp=1390268986&oauth_token=aaaabbbbccccdddd&oauth_version=1.0", 43 | "Validate simple POST request signature" 44 | ); 45 | ASSERT_EQUAL( 46 | oauth.getURLQueryString(OAuth::Http::Delete, "resource"), 47 | "oauth_consumer_key=wwwwxxxxyyyyzzzz&oauth_nonce=139026898664&oauth_signature=CcAXXkwNhyEW4Y6QpovPxq56kkc%3D&oauth_signature_method=HMAC-SHA1&oauth_timestamp=1390268986&oauth_token=aaaabbbbccccdddd&oauth_version=1.0", 48 | "Validate simple DELETE request signature" 49 | ); 50 | ASSERT_EQUAL( 51 | oauth.getURLQueryString(OAuth::Http::Put, "resource"), 52 | "oauth_consumer_key=wwwwxxxxyyyyzzzz&oauth_nonce=139026898664&oauth_signature=U%2BbprftQwfylqEU9IR%2Bps5KUnZc%3D&oauth_signature_method=HMAC-SHA1&oauth_timestamp=1390268986&oauth_token=aaaabbbbccccdddd&oauth_version=1.0", 53 | "Validate simple PUT request signature" 54 | ); 55 | 56 | 57 | // Test all request types with simple, unreserved chars in resource 58 | // name, a few parameters, and make them out of sorted order 59 | ASSERT_EQUAL( 60 | oauth.getURLQueryString(OAuth::Http::Head, "resource?z=1&a=2&d=baz"), 61 | "a=2&d=baz&oauth_consumer_key=wwwwxxxxyyyyzzzz&oauth_nonce=139026898664&oauth_signature=AN6kLZkXmqJRKtSgPQipjp8cYaM%3D&oauth_signature_method=HMAC-SHA1&oauth_timestamp=1390268986&oauth_token=aaaabbbbccccdddd&oauth_version=1.0&z=1", 62 | "Validate simple HEAD request with unordered parameters signature" 63 | ); 64 | ASSERT_EQUAL( 65 | oauth.getURLQueryString(OAuth::Http::Get, "resource?z=1&a=2&d=baz"), 66 | "a=2&d=baz&oauth_consumer_key=wwwwxxxxyyyyzzzz&oauth_nonce=139026898664&oauth_signature=UCWLEts18HC8Uq9GsO1gO4YBops%3D&oauth_signature_method=HMAC-SHA1&oauth_timestamp=1390268986&oauth_token=aaaabbbbccccdddd&oauth_version=1.0&z=1", 67 | "Validate simple GET request with unordered parameters signature" 68 | ); 69 | ASSERT_EQUAL( 70 | oauth.getURLQueryString(OAuth::Http::Post, "resource?z=1&a=2&d=baz"), 71 | "a=2&d=baz&oauth_consumer_key=wwwwxxxxyyyyzzzz&oauth_nonce=139026898664&oauth_signature=VFd7tS7nnYIgU0g5biRA0%2BWhaRs%3D&oauth_signature_method=HMAC-SHA1&oauth_timestamp=1390268986&oauth_token=aaaabbbbccccdddd&oauth_version=1.0&z=1", 72 | "Validate simple POST request with unordered parameters signature" 73 | ); 74 | ASSERT_EQUAL( 75 | oauth.getURLQueryString(OAuth::Http::Delete, "resource?z=1&a=2&d=baz"), 76 | "a=2&d=baz&oauth_consumer_key=wwwwxxxxyyyyzzzz&oauth_nonce=139026898664&oauth_signature=PNHvv3P5WF%2BGCg6EOzwz7561tw8%3D&oauth_signature_method=HMAC-SHA1&oauth_timestamp=1390268986&oauth_token=aaaabbbbccccdddd&oauth_version=1.0&z=1", 77 | "Validate simple DELETE request with unordered parameters signature" 78 | ); 79 | ASSERT_EQUAL( 80 | oauth.getURLQueryString(OAuth::Http::Put, "resource?z=1&a=2&d=baz"), 81 | "a=2&d=baz&oauth_consumer_key=wwwwxxxxyyyyzzzz&oauth_nonce=139026898664&oauth_signature=yqbgvxoqF0eyFqNGUaivRRRld8U%3D&oauth_signature_method=HMAC-SHA1&oauth_timestamp=1390268986&oauth_token=aaaabbbbccccdddd&oauth_version=1.0&z=1", 82 | "Validate simple PUT request with unordered parameters signature" 83 | ); 84 | ASSERT_EQUAL( 85 | oauth.getURLQueryString(OAuth::Http::Post, "resource?z=1&a=2&l=-74%2C40%2C-73%2C41"), 86 | "a=2&l=-74%2C40%2C-73%2C41&oauth_consumer_key=wwwwxxxxyyyyzzzz&oauth_nonce=139026898664&oauth_signature=QXyCmnX%2FL3OR6wQ9HpZGrTk7ZG0%3D&oauth_signature_method=HMAC-SHA1&oauth_timestamp=1390268986&oauth_token=aaaabbbbccccdddd&oauth_version=1.0&z=1", 87 | "Validate simple POST request with sub-delimiter character ','" 88 | ); 89 | } 90 | }; 91 | 92 | } 93 | 94 | #endif 95 | -------------------------------------------------------------------------------- /tests/testutil.cpp: -------------------------------------------------------------------------------- 1 | #include "testutil.h" 2 | 3 | namespace OAuthTest { 4 | 5 | int TestUtil::passed = 0; 6 | int TestUtil::failed = 0; 7 | std::vector TestUtil::failed_details; 8 | 9 | } 10 | -------------------------------------------------------------------------------- /tests/testutil.h: -------------------------------------------------------------------------------- 1 | #ifndef __LIBOAUTHCPP_TESTUTIL_H__ 2 | #define __LIBOAUTHCPP_TESTUTIL_H__ 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | namespace OAuthTest { 9 | 10 | class TestUtil { 11 | private: 12 | struct Details { 13 | std::string cond_str; 14 | std::string cond_report; 15 | 16 | Details(const std::string& cond, const std::string& report) 17 | : cond_str(cond), cond_report(report) 18 | {} 19 | }; 20 | 21 | public: 22 | static void assert_true(bool is_true, const std::string& cond_str, const std::string& cond_report) { 23 | if (!is_true) { 24 | std::cout << "TEST: " << cond_report << std::endl; 25 | std::cout << "FAIL\t" << cond_str << std::endl; 26 | failed++; 27 | // Save info about failures for summary 28 | failed_details.push_back(Details(cond_report, cond_str)); 29 | } 30 | else { 31 | #ifdef LIBOAUTHCPP_DEBUG 32 | std::cout << "TEST:" << cond_report << std::endl; 33 | std::cout << "PASS\t" << cond_str << std::endl; 34 | #endif 35 | passed++; 36 | } 37 | } 38 | 39 | /** Summarize the results and return the return code for */ 40 | static int summary() { 41 | std::cout << std::endl << std::endl; 42 | std::cout << "Summary:" << std::endl; 43 | std::cout << "PASSED: " << passed << std::endl; 44 | std::cout << "FAILED: " << failed << std::endl; 45 | if (failed_details.size() > 0) { 46 | std::cout << "\tDetails: " << std::endl; 47 | for(std::size_t i = 0; i < failed_details.size(); i++) 48 | std::cout << "\t\t" << failed_details[i].cond_str << ": " << failed_details[i].cond_report << std::endl; 49 | } 50 | 51 | std::cout << std::endl << std::endl; 52 | // Returns 0 upon success, positive number (error) if anything fails. 53 | return failed; 54 | } 55 | 56 | private: 57 | static int passed; 58 | static int failed; 59 | static std::vector
failed_details; 60 | }; 61 | 62 | #define ASSERT_BASE(cond, cond_str, msg) \ 63 | do { \ 64 | std::stringstream line_sstr; \ 65 | line_sstr << __LINE__; \ 66 | OAuthTest::TestUtil::assert_true(cond, std::string(cond_str) + std::string(" (" __FILE__) + std::string(":") + line_sstr.str() + std::string(")"), msg); \ 67 | } while(0) 68 | 69 | #define ASSERT_TRUE(cond, msg) \ 70 | ASSERT_BASE(cond, #cond " == true", msg) 71 | 72 | #define ASSERT_FALSE(cond, msg) \ 73 | ASSERT_BASE(!cond, #cond " == false", msg) 74 | 75 | #define ASSERT_EQUAL(a, b, msg) \ 76 | do { \ 77 | std::stringstream as_eq_sstr; \ 78 | as_eq_sstr << #a " == " #b << " (" << a << " == " << b << ")"; \ 79 | ASSERT_BASE(a == b, as_eq_sstr.str(), msg); \ 80 | } while(0) 81 | 82 | #define ASSERT_NOTEQUAL(a, b, msg) \ 83 | do { \ 84 | std::stringstream as_neq_sstr; \ 85 | as_neq_sstr << #a " != " #b << "(" << a << " != " << b << ")"; \ 86 | ASSERT_BASE(a != b, as_neq_sstr.str(), msg); \ 87 | } while(0) 88 | 89 | #define ASSERT_IN(key, collection, msg) \ 90 | ASSERT_BASE(collection.find(key) != collection.end(), #key " in " #collection, msg) 91 | 92 | #define ASSERT_THROWS(stmts, ExceptionType, msg) \ 93 | do { \ 94 | try { \ 95 | stmts; \ 96 | ASSERT_BASE(false, "throws " #ExceptionType, msg); \ 97 | } \ 98 | catch (ExceptionType e) { \ 99 | ASSERT_BASE(true, "throws " #ExceptionType, msg); \ 100 | } \ 101 | } while(0) 102 | 103 | } // namespace OAuthTest 104 | 105 | #endif 106 | -------------------------------------------------------------------------------- /tests/urlencode_test.h: -------------------------------------------------------------------------------- 1 | #ifndef __LIBOAUTHCPP_URLENCODE_TEST_H__ 2 | #define __LIBOAUTHCPP_URLENCODE_TEST_H__ 3 | 4 | #include "testutil.h" 5 | #include 6 | 7 | using namespace OAuth; 8 | 9 | namespace OAuthTest { 10 | 11 | /** Tests URLEncode function. See http://tools.ietf.org/html/rfc3986 for 12 | * details, especially Section 2. This is all based on OAuth 1.0a, 13 | * which says[http://oauth.net/core/1.0a/] that all unreserved 14 | * (approximately normal alphanumerics) should *not* be encoded, but 15 | * *everything else* should be. 16 | **/ 17 | class URLEncodeTest { 18 | public: 19 | static void run() { 20 | complete_encode_test(); 21 | http_path_encode_test(); 22 | http_query_key_encode_test(); 23 | http_query_value_encode_test(); 24 | } 25 | 26 | 27 | // Tests the API for doing complete encoding. Maintained for 28 | // backward compatibility, but also exercises the internal API 29 | // which is needed to encode the base signature string for the 30 | // OAuth protocol. 31 | static void complete_encode_test() { 32 | // Unreserved set *MUST NOT* be encoded 33 | ASSERT_EQUAL( 34 | PercentEncode("abcdefghijklmnopqrstuvwxyz0123456789-._~"), 35 | std::string("abcdefghijklmnopqrstuvwxyz0123456789-._~"), 36 | "URLEncoding unreserved characters (normal alphanumerics) should be a nop (complete encoding)" 37 | ); 38 | 39 | // Everything else must be encoded 40 | 41 | // Reserved gen-delims 42 | ASSERT_EQUAL(PercentEncode(":"), "%3A", "Reserved gen-delim ':' should be percent encoded (complete encoding)"); 43 | ASSERT_EQUAL(PercentEncode("/"), "%2F", "Reserved gen-delim '/' should be percent encoded (complete encoding)"); 44 | ASSERT_EQUAL(PercentEncode("?"), "%3F", "Reserved gen-delim '?' should be percent encoded (complete encoding)"); 45 | ASSERT_EQUAL(PercentEncode("#"), "%23", "Reserved gen-delim '#' should be percent encoded (complete encoding)"); 46 | ASSERT_EQUAL(PercentEncode("["), "%5B", "Reserved gen-delim ']' should be percent encoded (complete encoding)"); 47 | ASSERT_EQUAL(PercentEncode("]"), "%5D", "Reserved gen-delim '[' should be percent encoded (complete encoding)"); 48 | ASSERT_EQUAL(PercentEncode("@"), "%40", "Reserved gen-delim '@' should be percent encoded (complete encoding)"); 49 | // Reserved sub-delims 50 | ASSERT_EQUAL(PercentEncode("!"), "%21", "Reserved sub-delim '!' should be percent encoded (complete encoding)"); 51 | ASSERT_EQUAL(PercentEncode("$"), "%24", "Reserved sub-delim '$' should be percent encoded (complete encoding)"); 52 | ASSERT_EQUAL(PercentEncode("%"), "%25", "Reserved sub-delim '%' should be percent encoded (complete encoding)"); 53 | ASSERT_EQUAL(PercentEncode("&"), "%26", "Reserved sub-delim '&' should be percent encoded (complete encoding)"); 54 | ASSERT_EQUAL(PercentEncode("'"), "%27", "Reserved sub-delim ''' should be percent encoded (complete encoding)"); 55 | ASSERT_EQUAL(PercentEncode("("), "%28", "Reserved sub-delim '(' should be percent encoded (complete encoding)"); 56 | ASSERT_EQUAL(PercentEncode(")"), "%29", "Reserved sub-delim ')' should be percent encoded (complete encoding)"); 57 | ASSERT_EQUAL(PercentEncode("*"), "%2A", "Reserved sub-delim '*' should be percent encoded (complete encoding)"); 58 | ASSERT_EQUAL(PercentEncode("+"), "%2B", "Reserved sub-delim '+' should be percent encoded (complete encoding)"); 59 | ASSERT_EQUAL(PercentEncode(","), "%2C", "Reserved sub-delim ',' should be percent encoded (complete encoding)"); 60 | ASSERT_EQUAL(PercentEncode(";"), "%3B", "Reserved sub-delim ';' should be percent encoded (complete encoding)"); 61 | ASSERT_EQUAL(PercentEncode("="), "%3D", "Reserved sub-delim '=' should be percent encoded (complete encoding)"); 62 | 63 | 64 | // Try to cover a reasonable set of non-unreserved 65 | // characters to make sure we're encoding what we should. We 66 | // can add more here as necessary if we find errors. 67 | ASSERT_EQUAL(PercentEncode(" "), "%20", "Non-unreserved character ' ' should be percent encoded (complete encoding)"); 68 | ASSERT_EQUAL(PercentEncode("\""), "%22", "Non-unreserved character '\"' should be percent encoded (complete encoding)"); 69 | ASSERT_EQUAL(PercentEncode("<"), "%3C", "Non-unreserved character '<' should be percent encoded (complete encoding)"); 70 | ASSERT_EQUAL(PercentEncode(">"), "%3E", "Non-unreserved character '>' should be percent encoded (complete encoding)"); 71 | ASSERT_EQUAL(PercentEncode("\\"), "%5C", "Non-unreserved character '\\' should be percent encoded (complete encoding)"); 72 | ASSERT_EQUAL(PercentEncode("^"), "%5E", "Non-unreserved character '^' should be percent encoded (complete encoding)"); 73 | ASSERT_EQUAL(PercentEncode("`"), "%60", "Non-unreserved character '`' should be percent encoded (complete encoding)"); 74 | ASSERT_EQUAL(PercentEncode("{"), "%7B", "Non-unreserved character '{' should be percent encoded (complete encoding)"); 75 | ASSERT_EQUAL(PercentEncode("|"), "%7C", "Non-unreserved character '|' should be percent encoded (complete encoding)"); 76 | ASSERT_EQUAL(PercentEncode("}"), "%7D", "Non-unreserved character '}' should be percent encoded (complete encoding)"); 77 | } 78 | 79 | 80 | /** Tests encoding of the path of an HTTP URI, allowing some 81 | * reserved subdelimiters to be used unencoded. 82 | */ 83 | static void http_path_encode_test() { 84 | // Unreserved set *MUST NOT* be encoded 85 | ASSERT_EQUAL( 86 | HttpEncodePath("abcdefghijklmnopqrstuvwxyz0123456789-._~"), 87 | std::string("abcdefghijklmnopqrstuvwxyz0123456789-._~"), 88 | "URLEncoding unreserved characters (normal alphanumerics) should be a nop (http path encoding)" 89 | ); 90 | 91 | // Reserved gen-delims 92 | ASSERT_EQUAL(HttpEncodePath(":"), "%3A", "Reserved gen-delim ':' should be percent encoded (http path encoding)"); 93 | ASSERT_EQUAL(HttpEncodePath("/"), "%2F", "Reserved gen-delim '/' should be percent encoded (http path encoding)"); 94 | ASSERT_EQUAL(HttpEncodePath("?"), "%3F", "Reserved gen-delim '?' should be percent encoded (http path encoding)"); 95 | ASSERT_EQUAL(HttpEncodePath("#"), "%23", "Reserved gen-delim '#' should be percent encoded (http path encoding)"); 96 | ASSERT_EQUAL(HttpEncodePath("["), "%5B", "Reserved gen-delim ']' should be percent encoded (http path encoding)"); 97 | ASSERT_EQUAL(HttpEncodePath("]"), "%5D", "Reserved gen-delim '[' should be percent encoded (http path encoding)"); 98 | ASSERT_EQUAL(HttpEncodePath("@"), "%40", "Reserved gen-delim '@' should be percent encoded (http path encoding)"); 99 | // Reserved sub-delims 100 | ASSERT_EQUAL(HttpEncodePath("!"), "!", "Reserved sub-delim '!' should be a nop (http path encoding)"); 101 | ASSERT_EQUAL(HttpEncodePath("$"), "$", "Reserved sub-delim '$' should be a nop (http path encoding)"); 102 | ASSERT_EQUAL(HttpEncodePath("%"), "%25", "Reserved sub-delim '%' should be percent encoded (http path encoding)"); 103 | ASSERT_EQUAL(HttpEncodePath("&"), "&", "Reserved sub-delim '&' should be a nop (http path encoding)"); 104 | ASSERT_EQUAL(HttpEncodePath("'"), "'", "Reserved sub-delim ''' should be a nop (http path encoding)"); 105 | ASSERT_EQUAL(HttpEncodePath("("), "(", "Reserved sub-delim '(' should be a nop (http path encoding)"); 106 | ASSERT_EQUAL(HttpEncodePath(")"), ")", "Reserved sub-delim ')' should be a nop (http path encoding)"); 107 | ASSERT_EQUAL(HttpEncodePath("*"), "*", "Reserved sub-delim '*' should be a nop (http path encoding)"); 108 | ASSERT_EQUAL(HttpEncodePath("+"), "+", "Reserved sub-delim '+' should be a nop (http path encoding)"); 109 | ASSERT_EQUAL(HttpEncodePath(","), ",", "Reserved sub-delim ',' should be a nop (http path encoding)"); 110 | // - ';' and '=' can be used for "matrix" parameters 111 | ASSERT_EQUAL(HttpEncodePath(";"), ";", "Reserved sub-delim ';' should be a nop (http path encoding)"); 112 | ASSERT_EQUAL(HttpEncodePath("="), "=", "Reserved sub-delim '=' should be a nop (http path encoding)"); 113 | 114 | 115 | // Try to cover a reasonable set of non-unreserved 116 | // characters to make sure we're encoding what we should. We 117 | // can add more here as necessary if we find errors. 118 | ASSERT_EQUAL(HttpEncodePath(" "), "%20", "Non-unreserved character ' ' should be percent encoded (http path encoding)"); 119 | ASSERT_EQUAL(HttpEncodePath("\""), "%22", "Non-unreserved character '\"' should be percent encoded (http path encoding)"); 120 | ASSERT_EQUAL(HttpEncodePath("<"), "%3C", "Non-unreserved character '<' should be percent encoded (http path encoding)"); 121 | ASSERT_EQUAL(HttpEncodePath(">"), "%3E", "Non-unreserved character '>' should be percent encoded (http path encoding)"); 122 | ASSERT_EQUAL(HttpEncodePath("\\"), "%5C", "Non-unreserved character '\\' should be percent encoded (http path encoding)"); 123 | ASSERT_EQUAL(HttpEncodePath("^"), "%5E", "Non-unreserved character '^' should be percent encoded (http path encoding)"); 124 | ASSERT_EQUAL(HttpEncodePath("`"), "%60", "Non-unreserved character '`' should be percent encoded (http path encoding)"); 125 | ASSERT_EQUAL(HttpEncodePath("{"), "%7B", "Non-unreserved character '{' should be percent encoded (http path encoding)"); 126 | ASSERT_EQUAL(HttpEncodePath("|"), "%7C", "Non-unreserved character '|' should be percent encoded (http path encoding)"); 127 | ASSERT_EQUAL(HttpEncodePath("}"), "%7D", "Non-unreserved character '}' should be percent encoded (http path encoding)"); 128 | } 129 | 130 | 131 | 132 | /** Tests encoding of a query string key component of an HTTP URI, 133 | * allowing a different set of reserved subdelimiters to be used 134 | * unencoded as compared to the path portion of the URI. 135 | */ 136 | static void http_query_key_encode_test() { 137 | // Unreserved set *MUST NOT* be encoded 138 | ASSERT_EQUAL( 139 | HttpEncodeQueryKey("abcdefghijklmnopqrstuvwxyz0123456789-._~"), 140 | std::string("abcdefghijklmnopqrstuvwxyz0123456789-._~"), 141 | "URLEncoding unreserved characters (normal alphanumerics) should be a nop (http query string encoding)" 142 | ); 143 | 144 | // Reserved gen-delims 145 | ASSERT_EQUAL(HttpEncodeQueryKey(":"), "%3A", "Reserved gen-delim ':' should be percent encoded (http query string encoding)"); 146 | ASSERT_EQUAL(HttpEncodeQueryKey("/"), "%2F", "Reserved gen-delim '/' should be percent encoded (http query string encoding)"); 147 | ASSERT_EQUAL(HttpEncodeQueryKey("?"), "%3F", "Reserved gen-delim '?' should be percent encoded (http query string encoding)"); 148 | ASSERT_EQUAL(HttpEncodeQueryKey("#"), "%23", "Reserved gen-delim '#' should be percent encoded (http query string encoding)"); 149 | ASSERT_EQUAL(HttpEncodeQueryKey("["), "%5B", "Reserved gen-delim ']' should be percent encoded (http query string encoding)"); 150 | ASSERT_EQUAL(HttpEncodeQueryKey("]"), "%5D", "Reserved gen-delim '[' should be percent encoded (http query string encoding)"); 151 | ASSERT_EQUAL(HttpEncodeQueryKey("@"), "%40", "Reserved gen-delim '@' should be percent encoded (http query string encoding)"); 152 | // Reserved sub-delims 153 | ASSERT_EQUAL(HttpEncodeQueryKey("!"), "%21", "Reserved sub-delim '!' should be percent encoded (http query string encoding)"); 154 | ASSERT_EQUAL(HttpEncodeQueryKey("$"), "%24", "Reserved sub-delim '$' should be percent encoded (http query string encoding)"); 155 | ASSERT_EQUAL(HttpEncodeQueryKey("%"), "%25", "Reserved sub-delim '%' should be percent encoded (http query string encoding)"); 156 | // - '&' is the query arg separator 157 | ASSERT_EQUAL(HttpEncodeQueryKey("&"), "%26", "Reserved sub-delim '&' should be percent encoded (http query string encoding)"); 158 | ASSERT_EQUAL(HttpEncodeQueryKey("'"), "%27", "Reserved sub-delim ''' should be percent encoded (http query string encoding)"); 159 | ASSERT_EQUAL(HttpEncodeQueryKey("("), "%28", "Reserved sub-delim '(' should be percent encoded (http query string encoding)"); 160 | ASSERT_EQUAL(HttpEncodeQueryKey(")"), "%29", "Reserved sub-delim ')' should be percent encoded (http query string encoding)"); 161 | ASSERT_EQUAL(HttpEncodeQueryKey("*"), "%2A", "Reserved sub-delim '*' should be percent encoded (http query string encoding)"); 162 | // - '+' has to be encoded in query strings because spaces historically 163 | // were convereted to '+'. 164 | ASSERT_EQUAL(HttpEncodeQueryKey("+"), "%2B", "Reserved sub-delim '+' should be percent encoded (http query string encoding)"); 165 | ASSERT_EQUAL(HttpEncodeQueryKey(","), "%2C", "Reserved sub-delim ',' should be percent encoded (http query string encoding)"); 166 | ASSERT_EQUAL(HttpEncodeQueryKey(";"), "%3B", "Reserved sub-delim ';' should be percent encoded (http query string encoding)"); 167 | // - '=' is the key=value separator 168 | ASSERT_EQUAL(HttpEncodeQueryKey("="), "%3D", "Reserved sub-delim '=' should be percent encoded (http query string encoding)"); 169 | 170 | 171 | // Try to cover a reasonable set of non-unreserved 172 | // characters to make sure we're encoding what we should. We 173 | // can add more here as necessary if we find errors. 174 | ASSERT_EQUAL(HttpEncodeQueryKey(" "), "%20", "Non-unreserved character ' ' should be percent encoded (http query string encoding)"); 175 | ASSERT_EQUAL(HttpEncodeQueryKey("\""), "%22", "Non-unreserved character '\"' should be percent encoded (http query string encoding)"); 176 | ASSERT_EQUAL(HttpEncodeQueryKey("<"), "%3C", "Non-unreserved character '<' should be percent encoded (http query string encoding)"); 177 | ASSERT_EQUAL(HttpEncodeQueryKey(">"), "%3E", "Non-unreserved character '>' should be percent encoded (http query string encoding)"); 178 | ASSERT_EQUAL(HttpEncodeQueryKey("\\"), "%5C", "Non-unreserved character '\\' should be percent encoded (http query string encoding)"); 179 | ASSERT_EQUAL(HttpEncodeQueryKey("^"), "%5E", "Non-unreserved character '^' should be percent encoded (http query string encoding)"); 180 | ASSERT_EQUAL(HttpEncodeQueryKey("`"), "%60", "Non-unreserved character '`' should be percent encoded (http query string encoding)"); 181 | ASSERT_EQUAL(HttpEncodeQueryKey("{"), "%7B", "Non-unreserved character '{' should be percent encoded (http query string encoding)"); 182 | ASSERT_EQUAL(HttpEncodeQueryKey("|"), "%7C", "Non-unreserved character '|' should be percent encoded (http query string encoding)"); 183 | ASSERT_EQUAL(HttpEncodeQueryKey("}"), "%7D", "Non-unreserved character '}' should be percent encoded (http query string encoding)"); 184 | } 185 | 186 | 187 | /** Tests encoding of a query string key component of an HTTP URI, 188 | * allowing a different set of reserved subdelimiters to be used 189 | * unencoded as compared to the path portion of the URI. 190 | */ 191 | static void http_query_value_encode_test() { 192 | // Unreserved set *MUST NOT* be encoded 193 | ASSERT_EQUAL( 194 | HttpEncodeQueryValue("abcdefghijklmnopqrstuvwxyz0123456789-._~"), 195 | std::string("abcdefghijklmnopqrstuvwxyz0123456789-._~"), 196 | "URLEncoding unreserved characters (normal alphanumerics) should be a nop (http query string encoding)" 197 | ); 198 | 199 | // Reserved gen-delims 200 | ASSERT_EQUAL(HttpEncodeQueryValue(":"), "%3A", "Reserved gen-delim ':' should be percent encoded (http query string encoding)"); 201 | ASSERT_EQUAL(HttpEncodeQueryValue("/"), "%2F", "Reserved gen-delim '/' should be percent encoded (http query string encoding)"); 202 | ASSERT_EQUAL(HttpEncodeQueryValue("?"), "%3F", "Reserved gen-delim '?' should be percent encoded (http query string encoding)"); 203 | ASSERT_EQUAL(HttpEncodeQueryValue("#"), "%23", "Reserved gen-delim '#' should be percent encoded (http query string encoding)"); 204 | ASSERT_EQUAL(HttpEncodeQueryValue("["), "%5B", "Reserved gen-delim ']' should be percent encoded (http query string encoding)"); 205 | ASSERT_EQUAL(HttpEncodeQueryValue("]"), "%5D", "Reserved gen-delim '[' should be percent encoded (http query string encoding)"); 206 | ASSERT_EQUAL(HttpEncodeQueryValue("@"), "%40", "Reserved gen-delim '@' should be percent encoded (http query string encoding)"); 207 | // Reserved sub-delims 208 | ASSERT_EQUAL(HttpEncodeQueryValue("!"), "%21", "Reserved sub-delim '!' should be percent encoded (http query string encoding)"); 209 | ASSERT_EQUAL(HttpEncodeQueryValue("$"), "%24", "Reserved sub-delim '$' should be percent encoded (http query string encoding)"); 210 | ASSERT_EQUAL(HttpEncodeQueryValue("%"), "%25", "Reserved sub-delim '%' should be percent encoded (http query string encoding)"); 211 | // - '&' is the query arg separator 212 | ASSERT_EQUAL(HttpEncodeQueryValue("&"), "%26", "Reserved sub-delim '&' should be percent encoded (http query string encoding)"); 213 | ASSERT_EQUAL(HttpEncodeQueryValue("'"), "%27", "Reserved sub-delim ''' should be percent encoded (http query string encoding)"); 214 | ASSERT_EQUAL(HttpEncodeQueryValue("("), "%28", "Reserved sub-delim '(' should be percent encoded (http query string encoding)"); 215 | ASSERT_EQUAL(HttpEncodeQueryValue(")"), "%29", "Reserved sub-delim ')' should be percent encoded (http query string encoding)"); 216 | ASSERT_EQUAL(HttpEncodeQueryValue("*"), "%2A", "Reserved sub-delim '*' should be percent encoded (http query string encoding)"); 217 | // - '+' has to be encoded in query strings because spaces historically 218 | // were convereted to '+'. 219 | ASSERT_EQUAL(HttpEncodeQueryValue("+"), "%2B", "Reserved sub-delim '+' should be percent encoded (http query string encoding)"); 220 | ASSERT_EQUAL(HttpEncodeQueryValue(","), "%2C", "Reserved sub-delim ',' should be percent encoded (http query string encoding)"); 221 | ASSERT_EQUAL(HttpEncodeQueryValue(";"), "%3B", "Reserved sub-delim ';' should be percent encoded (http query string encoding)"); 222 | // - '=' is the key=value separator, but is safe in the values 223 | ASSERT_EQUAL(HttpEncodeQueryValue("="), "%3D", "Reserved sub-delim '=' should be percent encoded (http query string encoding)"); 224 | 225 | 226 | // Try to cover a reasonable set of non-unreserved 227 | // characters to make sure we're encoding what we should. We 228 | // can add more here as necessary if we find errors. 229 | ASSERT_EQUAL(HttpEncodeQueryValue(" "), "%20", "Non-unreserved character ' ' should be percent encoded (http query string encoding)"); 230 | ASSERT_EQUAL(HttpEncodeQueryValue("\""), "%22", "Non-unreserved character '\"' should be percent encoded (http query string encoding)"); 231 | ASSERT_EQUAL(HttpEncodeQueryValue("<"), "%3C", "Non-unreserved character '<' should be percent encoded (http query string encoding)"); 232 | ASSERT_EQUAL(HttpEncodeQueryValue(">"), "%3E", "Non-unreserved character '>' should be percent encoded (http query string encoding)"); 233 | ASSERT_EQUAL(HttpEncodeQueryValue("\\"), "%5C", "Non-unreserved character '\\' should be percent encoded (http query string encoding)"); 234 | ASSERT_EQUAL(HttpEncodeQueryValue("^"), "%5E", "Non-unreserved character '^' should be percent encoded (http query string encoding)"); 235 | ASSERT_EQUAL(HttpEncodeQueryValue("`"), "%60", "Non-unreserved character '`' should be percent encoded (http query string encoding)"); 236 | ASSERT_EQUAL(HttpEncodeQueryValue("{"), "%7B", "Non-unreserved character '{' should be percent encoded (http query string encoding)"); 237 | ASSERT_EQUAL(HttpEncodeQueryValue("|"), "%7C", "Non-unreserved character '|' should be percent encoded (http query string encoding)"); 238 | ASSERT_EQUAL(HttpEncodeQueryValue("}"), "%7D", "Non-unreserved character '}' should be percent encoded (http query string encoding)"); 239 | } 240 | }; 241 | 242 | } 243 | 244 | #endif 245 | --------------------------------------------------------------------------------