Full Code of Diego81/omnicontacts for AI

master 105b40f79cac cached
37 files
131.9 KB
34.9k tokens
187 symbols
1 requests
Download .txt
Repository: Diego81/omnicontacts
Branch: master
Commit: 105b40f79cac
Files: 37
Total size: 131.9 KB

Directory structure:
gitextract_m1xfdr8k/

├── .gitignore
├── Gemfile
├── README.md
├── Rakefile
├── lib/
│   ├── omnicontacts/
│   │   ├── authorization/
│   │   │   ├── oauth1.rb
│   │   │   └── oauth2.rb
│   │   ├── builder.rb
│   │   ├── http_utils.rb
│   │   ├── importer/
│   │   │   ├── facebook.rb
│   │   │   ├── gmail.rb
│   │   │   ├── hotmail.rb
│   │   │   ├── linkedin.rb
│   │   │   ├── outlook.rb
│   │   │   └── yahoo.rb
│   │   ├── importer.rb
│   │   ├── integration_test.rb
│   │   ├── middleware/
│   │   │   ├── base_oauth.rb
│   │   │   ├── oauth1.rb
│   │   │   └── oauth2.rb
│   │   └── parse_utils.rb
│   └── omnicontacts.rb
├── omnicontacts.gemspec
└── spec/
    ├── omnicontacts/
    │   ├── authorization/
    │   │   ├── oauth1_spec.rb
    │   │   └── oauth2_spec.rb
    │   ├── http_utils_spec.rb
    │   ├── importer/
    │   │   ├── facebook_spec.rb
    │   │   ├── gmail_spec.rb
    │   │   ├── hotmail_spec.rb
    │   │   ├── linkedin_spec.rb
    │   │   ├── outlook_spec.rb
    │   │   └── yahoo_spec.rb
    │   ├── integration_test_spec.rb
    │   ├── middleware/
    │   │   ├── base_oauth_spec.rb
    │   │   ├── oauth1_spec.rb
    │   │   └── oauth2_spec.rb
    │   └── parse_utils_spec.rb
    └── spec_helper.rb

================================================
FILE CONTENTS
================================================

================================================
FILE: .gitignore
================================================
coverage
*.iml
.idea
.rvmrc
.DS_Store
nbproject


================================================
FILE: Gemfile
================================================
source 'http://rubygems.org'

gemspec

================================================
FILE: README.md
================================================
# OmniContacts

Inspired by the popular OmniAuth, OmniContacts is a library that enables users of an application to import contacts
from their email or Facebook accounts. The email providers currently supported are Gmail, Yahoo and Hotmail.
OmniContacts is a Rack middleware, therefore you can use it with Rails, Sinatra and any other Rack-based framework.

OmniContacts uses the OAuth protocol to communicate with the contacts provider. Yahoo still uses OAuth 1.0, while
 Facebook, Gmail and Hotmail support OAuth 2.0.
In order to use OmniContacts, it is therefore necessary to first register your application with the provider and to obtain client_id and client_secret.

## Contribute!
Me (rubytastic) and the orginal author Diego don't actively use this code at the moment, anyone interested in maintaining and contributing to this codebase please write me up in a personal message ( rubytastic )
I try to merge pull requests in every once and a while but this code would benefit from someone actively use and contribute to it.

## Gem build updates
There is now a new gem build out which should address many issues people had when posting on the issue tracker. Please update to the latest GEM version if you have problems before posting new issues.


## Usage

Add OmniContacts as a dependency:

```ruby
gem "omnicontacts"

```

As for OmniAuth, there is a Builder facilitating the usage of multiple contacts importers. In the case of a Rails application, the following code could be placed at `config/initializers/omnicontacts.rb`:

```ruby
require "omnicontacts"

Rails.application.middleware.use OmniContacts::Builder do
  importer :gmail, "client_id", "client_secret", {:redirect_path => "/oauth2callback", :ssl_ca_file => "/etc/ssl/certs/curl-ca-bundle.crt"}
  importer :yahoo, "consumer_id", "consumer_secret", {:callback_path => "/callback"}
  importer :linkedin, "consumer_id", "consumer_secret", {:redirect_path => "/oauth2callback", :state => '<long_unique_string_value>'}
  importer :hotmail, "client_id", "client_secret"
  importer :outlook, "app_id", "app_secret"
  importer :facebook, "client_id", "client_secret"
end

```

Every importer expects `client_id` and `client_secret` as mandatory, while `:redirect_path` and `:ssl_ca_file` are optional (except linkedin - `state` arg  mandatory).
Since Yahoo implements the version 1.0 of the OAuth protocol, naming is slightly different. Instead of `:redirect_path` you should use `:callback_path` as key in the hash providing the optional parameters.
While `:ssl_ca_file` is optional, it is highly recommended to set it on production environments for obvious security reasons.
On the other hand it makes things much easier to leave the default value for `:redirect_path` and `:callback path`, the reason of which will be clear after reading the following section.

## Register your application

* For Gmail : [Google API Console](https://code.google.com/apis/console/)

* For Yahoo : [Yahoo Developer Network](https://developer.yahoo.com/social/contacts/)

* For Hotmail : [Microsoft Developer Network](https://account.live.com/developers/applications/index)

* For Outlook : [Microsoft Application Registration Portal](https://apps.dev.microsoft.com/)

* For Facebook : [Facebook Developers](https://developers.facebook.com/apps)

* For Linkedin : [Linkedin Developer Network](https://www.linkedin.com/secure/developer)


##### Note:
Please go through [MSDN](http://msdn.microsoft.com/en-us/library/cc287659.aspx) if above Hotmail link will not work.  
Outlook is a newer Microsoft API which allows to retrieve real email address instead of `email_hashes` when using Hotmail, it also works with all kinds of MS accounts (Office 365, Hotmail.com, Live.com, MSN.com, Outlook.com, and Passport.com).

## Integrating with your Application

To use the Gem you first need to redirect your users to `/contacts/:importer`, where `:importer` can be facebook, gmail, yahoo or hotmail.
No changes to `config/routes.rb` are needed for this step since OmniContacts will be listening on that path and redirect the user to the email provider's website in order to authorize your app to access his contact list.
Once that is done the user will be redirected back to your application, to the path specified in `:redirect_path` (or `:callback_path` for yahoo).
If nothing is specified the default value is `/contacts/:importer/callback` (e.g. `/contacts/yahoo/callback`). This makes things simpler and you can just add the following line to `config/routes.rb`:

```ruby
  match "/contacts/:importer/callback" => "your_controller#callback"
```

The list of contacts can be accessed via the `omnicontacts.contacts` key in the environment hash and it consists of a simple array of hashes.
The following table shows which fields are supported by which provider:

<table>
	<tr>
		<th>Provider</th>
		<th>:email</th>
		<th>:id</th>
		<th>:profile_picture</th>
		<th>:name</th>
		<th>:first_name</th>
		<th>:last_name</th>
		<th>:address_1</th>
		<th>:address_2</th>
		<th>:city</th>
		<th>:region</th>
		<th>:postcode</th>
		<th>:country</th>
		<th>:phone_number</th>
		<th>:birthday</th>
		<th>:gender</th>
		<th>:relation</th>
	</tr>
	<tr>
		<td>Gmail</td>
		<td>X</td>
		<td>X</td>
		<td></td>
		<td>X</td>
		<td>X</td>
		<td>X</td>
		<td>X</td>
		<td>X</td>
		<td>X</td>
		<td>X</td>
		<td>X</td>
		<td>X</td>
		<td>X</td>
		<td>X</td>
		<td>X</td>
		<td>X</td>
	</tr>
	<tr>
		<td>Facebook</td>
		<td></td>
		<td>X</td>
		<td>X</td>
		<td>X</td>
		<td>X</td>
		<td>X</td>
		<td></td>
		<td></td>
		<td></td>
		<td></td>
		<td></td>
		<td></td>
		<td></td>
		<td>X</td>
		<td>X</td>
		<td>X</td>
	</tr>
	<tr>
		<td>Yahoo</td>
		<td>X</td>
		<td>X</td>
		<td></td>
		<td>X</td>
		<td>X</td>
		<td>X</td>
		<td>X</td>
		<td>X</td>
		<td>X</td>
		<td>X</td>
		<td>X</td>
		<td></td>
		<td></td>
		<td>X</td>
		<td></td>
		<td></td>
	</tr>
	<tr>
		<td>Hotmail</td>
		<td>X</td>
		<td>X</td>
		<td>X</td>
		<td>X</td>
		<td>X</td>
		<td>X</td>
		<td></td>
		<td></td>
		<td></td>
		<td></td>
		<td></td>
		<td></td>
		<td></td>
		<td>X</td>
		<td>X</td>
		<td></td>
	</tr>
	<tr>
		<td>Outlook</td>
		<td>X</td>
		<td>X</td>
		<td></td>
		<td>X</td>
		<td>X</td>
		<td>X</td>
		<td>X</td>
		<td></td>
		<td>X</td>
		<td>X</td>
		<td>X</td>
		<td>X</td>
		<td></td>
		<td>X</td>
		<td></td>
		<td></td>
	</tr>
	<tr>
	<td>Linkedin</td>
		<td></td>
		<td>X</td>
		<td>X</td>
		<td>X</td>
		<td>X</td>
		<td>X</td>
		<td></td>
		<td></td>
		<td></td>
		<td></td>
		<td></td>
		<td></td>
		<td></td>
		<td></td>
		<td></td>
		<td></td>
	<tr>
</table>

Obviously it may happen that some fields are blank even if supported by the provider in the case that the contact did not provide any information about them.

The information for the logged in user can also be accessed via 'omnicontacts.user' key in the environment hash. It consists of a simple hash which includes the same fields as above.

The following snippet shows how to simply print name and email of each contact, and also the the name of logged in user:
```ruby
def contacts_callback
  @contacts = request.env['omnicontacts.contacts']
  @user = request.env['omnicontacts.user']
  puts "List of contacts of #{@user[:name]} obtained from #{params[:importer]}:"
  @contacts.each do |contact|
    puts "Contact found: name => #{contact[:name]}, email => #{contact[:email]}"
  end
end
```

If the user does not authorize your application to access his/her contacts list, or any other inconvenience occurs, he/she is redirected to `/contacts/failure`. The query string will contain a parameter named `error_message` which specifies why the list of contacts could not be retrieved. `error_message` can have one of the following values: `not_authorized`, `timeout` and `internal_error`.

##  Tips and tricks

OmniContacts supports OAuth 1.0 and OAuth 2.0 token refresh, but for both it needs to persist data between requests. OmniContacts stores access tokens in the session. If you hit the 4KB cookie storage limit you better opt for the Memcache or the Active Record storage.

Gmail requires you to register the redirect_path on their website along with your application. Make sure to use the same value present in the configuration file, or `/contacts/gmail/callback` if using the default. Also make sure that your full url is used including "www" if your site redirects from the root domain.

To configure the max number of contacts to download from Gmail, just add a max results parameter in your initializer:

```ruby
importer :gmail, "xxx", "yyy", :max_results => 1000
```

Yahoo requires you to configure the Permissions your application requires. Make sure to go the Yahoo website and to select Read permission for Contacts.

Hotmail presents a "peculiar" feature. Their API returns a Contact object which does not contain an e-mail field!
However, if the contact has either name, family name or both set to null, than there is a field called name which does contain the e-mail address.
This means that it may happen that an Hotmail contact does not contain the email field.

## Integration Testing

You can enable test mode like this:

```ruby
  OmniContacts.integration_test.enabled = true
```

In this way all requests to `/omnicontacts/provider` will be redirected automatically to `/omnicontacts/provider/callback`.

The `mock` method allows to configure per-provider the result to return:

```ruby
  OmniContacts.integration_test.mock(:provider_name, :email => "user@example.com")
```

You can either pass a single hash or an array of hashes. If you pass a string, an error will be triggered with subsequent redirect to `/contacts/failure?error_message=internal_error`

You can also pass a user to fill `omnicontacts.user` (optional)
```ruby
  OmniContacts.integration_test.mock(:provider_name, {:email => "contact@example.com"}, {:email => "user@example.com"})
```

Follows a full example of an integration test:

```ruby
  OmniContacts.integration_test.enabled = true
  OmniContacts.integration_test.mock(:gmail, :email => "user@example.com")
  visit '/contacts/gmail'
  page.should have_content("user@example.com")
```

## Working on localhost

Since Hotmail and Facebook do not allow the usage of `localhost` as redirect path for the authorization step, a workaround is to use `ngrok`.
This is really useful when you need someone, the contacts provider in this case, to access your locally running application using a unique url.

Install ngrok, download from:

https://ngrok.com/

https://github.com/inconshreveable/ngrok

Unzip the file
```bash
unzip /place/this/is/ngrok.zip
```
Start your application
```bash
$ rails server

=> Booting WEBrick
=> Rails 4.0.4 application starting in development on http://0.0.0.0:3000
```

In a new terminal window, start the tunnel and pass the port where your application is running:
```bash
./ngrok 3000
```

Check the output to see something like
```bash
ngrok                                                                                                                    (Ctrl+C to quit)

Tunnel Status                 online
Version                       1.6/1.5
Forwarding                    http://274101c1e.ngrok.com -> 127.0.0.1:3000
Forwarding                    https://274101c1e.ngrok.com -> 127.0.0.1:3000
Web Interface                 127.0.0.1:4040
# Conn                        0
Avg Conn Time                 0.00ms
```

This window will show all network transaction that your locally hosted application is processing.
Ngrok will process all of the requests and responses on your localhost. Visit:

```bash
http://123456789.ngrok.com # replace 123456789 with your instance
```

## Example application

Thanks to @sonianand11, you can find a full example of a Rails application using OmniContacts at: https://github.com/sonianand11/omnicontacts_example

## Thanks

As already mentioned above, a special thanks goes to @sonianand11 for implementing an example app.
Thanks also to @asmatameem for her huge contribution. She indeed added support for Facebook and for many fields which were missing before.

## License

Copyright (c) 2012-2013 Diego81

Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included
in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.


================================================
FILE: Rakefile
================================================
require 'bundler'
require 'rspec/core/rake_task'

Bundler::GemHelper.install_tasks
RSpec::Core::RakeTask.new(:spec)
task :default => :spec
task :test => :spec


================================================
FILE: lib/omnicontacts/authorization/oauth1.rb
================================================
require "omnicontacts/http_utils"
require "base64"

# This module represent a OAuth 1.0 Client.
#
# Classes including the module must implement
# the following methods:
# * auth_host ->  the host of the authorization server
# * auth_token_path -> the path to query to obtain a request token
# * consumer_key -> the registered consumer key of the client
# * consumer_secret -> the registered consumer secret of the client
# * callback -> the callback to include during the redirection step
# * auth_path -> the path on the authorization server to redirect the user to
# * access_token_path -> the path to query in order to obtain the access token
module OmniContacts
  module Authorization
    module OAuth1
      include HTTPUtils

      OAUTH_VERSION = "1.0"

      # Obtain an authorization token from the server.
      # The token is returned in an array along with the relative authorization token secret.
      def fetch_authorization_token
        request_token_response = https_post(auth_host, auth_token_path, request_token_req_params)
        values_from_query_string(request_token_response, ["oauth_token", "oauth_token_secret"])
      end

      private

      def request_token_req_params
        {
          :oauth_consumer_key => consumer_key,
          :oauth_nonce => encode(random_string),
          :oauth_signature_method => "PLAINTEXT",
          :oauth_signature => encode(consumer_secret + "&"),
          :oauth_timestamp => timestamp,
          :oauth_version => OAUTH_VERSION,
          :oauth_callback => callback
        }
      end

      def random_string
        (0...50).map { ('a'..'z').to_a[rand(26)] }.join
      end

      def timestamp
        Time.now.to_i.to_s
      end

      def values_from_query_string query_string, keys_to_extract
        map = query_string_to_map(query_string)
        keys_to_extract.collect do |key|
          if map.has_key?(key)
            map[key]
          else
            raise "No value found for #{key} in #{query_string}"
          end
        end
      end

      public

      # Returns the url the user has to be redirected to do in order grant permission to the client application.
      def authorization_url auth_token
        "https://" + auth_host + auth_path + "?oauth_token=" + auth_token
      end

      # Fetches the access token from the authorization server.
      # The method expects the authorization token, the authorization token secret and the authorization verifier.
      # The result comprises the access token, the access token secret and a list of additional fields extracted from the server's response.
      # The list of additional fields to extract is specified as last parameter
      def fetch_access_token auth_token, auth_token_secret, auth_verifier, additional_fields_to_extract = []
        access_token_resp = https_post(auth_host, access_token_path, access_token_req_params(auth_token, auth_token_secret, auth_verifier))
        values_from_query_string(access_token_resp, (["oauth_token", "oauth_token_secret"] + additional_fields_to_extract))
      end

      private

      def access_token_req_params auth_token, auth_token_secret, auth_verifier
        {
          :oauth_consumer_key => consumer_key,
          :oauth_nonce => encode(random_string),
          :oauth_signature_method => "PLAINTEXT",
          :oauth_signature => encode(consumer_secret + "&" + auth_token_secret),
          :oauth_version => OAUTH_VERSION,
          :oauth_timestamp => timestamp,
          :oauth_token => auth_token,
          :oauth_verifier => auth_verifier
        }
      end

      public

      # Calculates a signature using HMAC-SHA1 according to the OAuth 1.0 specifications.
      # 
      # The base string is given is a RFC 3986 encoded concatenation of:
      # * Uppercase HTTP method
      # * An '&'
      # * A url without any parameters
      # * An '&'
      # * All parameters to use in the request encoded themselves and sorted by key.
      #
      # The signature key is given by the concatenation of:
      # * RFC 3986 encoded consumer secret
      # * An  '&'
      # * RFC 3986 encoded token secret
      def oauth_signature method, url, params, secret
        encoded_method = encode(method.upcase)
        encoded_url = encode(url)
        # params must be in alphabetical order
        encoded_params = encode(to_query_string(params.sort { |x, y| x.to_s <=> y.to_s }))
        base_string = encoded_method + '&' + encoded_url + '&' + encoded_params
        key = encode(consumer_secret) + '&' + secret
        hmac_sha1 = OpenSSL::HMAC.digest('sha1', key, base_string)
        # base64 encode results must be stripped
        encode(Base64.encode64(hmac_sha1).strip)
      end

    end
  end
end


================================================
FILE: lib/omnicontacts/authorization/oauth2.rb
================================================
require "omnicontacts/http_utils"
require "json"

# This module represents an OAuth 2.0 client.
#
# Classes including the module must implement
# the following methods:
# * auth_host -> the host of the authorization server
# * authorize_path -> the path on the authorization server the redirect the use to
# * client_id -> the registered client id of the client
# * client_secret -> the registered client secret of the client
# * redirect_path -> the path the authorization server has to redirect the user back after authorization
# * auth_token_path -> the path to query once the user has granted permission to the application
# * scope -> the scope necessary to acquire the contacts list.
module OmniContacts
  module Authorization
    module OAuth2
      include HTTPUtils

      # Calculates the URL the user has to be redirected to in order to authorize
      # the application to access his contacts list.
      def authorization_url
        "https://" + auth_host + authorize_path + "?" + authorize_url_params
      end

      private

      def authorize_url_params
        to_query_string({
            :client_id => client_id,
            :scope => encode(scope),
            :response_type => "code",
            :access_type => "online",
            :approval_prompt => "auto",
            :redirect_uri => encode(redirect_uri)
          })
      end

      public

      # Fetches the access token from the authorization server using the given authorization code.
      def fetch_access_token code
        access_token_from_response https_post(auth_host, auth_token_path, token_req_params(code))
      end

      private

      def token_req_params code
        {
          :client_id => client_id,
          :client_secret => client_secret,
          :code => code,
          :redirect_uri => encode(redirect_uri),
          :grant_type => "authorization_code"
        }
      end

      def access_token_from_response response
        if auth_host == "graph.facebook.com"
          response = query_string_to_map(response).to_json
        end
        json = JSON.parse(response)
        raise json["error"] if json["error"]
        [json["access_token"], json["token_type"], json["refresh_token"]]
      end

      public

      # Refreshes the access token using the provided refresh_token.
      def refresh_access_token refresh_token
        access_token_from_response https_post(auth_host, auth_token_path, refresh_token_req_params(refresh_token))
      end

      private

      def refresh_token_req_params refresh_token
        {
          :client_id => client_id,
          :client_secret => client_secret,
          :refresh_token => refresh_token,
          :grant_type => "refresh_token"
        }

      end
    end
  end
end


================================================
FILE: lib/omnicontacts/builder.rb
================================================
require "omnicontacts"

module OmniContacts
  class Builder < Rack::Builder
    def initialize(app, &block)
      if rack14?
        super
      else
        @app = app
        super(&block)
      end
    end

    def rack14?
      v = Rack.release.split('.')
      v[0].to_i >= 1 || v[1].to_i >= 4
    end

    def importer importer, *args
      middleware = OmniContacts::Importer.const_get(importer.to_s.capitalize)
      use middleware, *args
    rescue NameError
      raise LoadError, "Could not find importer #{importer}."
    end

    def call env
      @ins << @app unless rack14? || @ins.include?(@app)
      to_app.call(env)
    end
  end
end


================================================
FILE: lib/omnicontacts/http_utils.rb
================================================
require "net/http"
require "net/https"
require "cgi"
require "openssl"

# This module contains a set of utility methods  related to the HTTP protocol.
module OmniContacts
  module HTTPUtils

    SSL_PORT = 443

    module_function

    def query_string_to_map query_string
      query_string.split('&').reduce({}) do |memo, key_value|
        (key, value) = key_value.split('=')
        memo[key]= value
        memo
      end
    end

    def to_query_string map
      map.collect do |key, value|
        key.to_s + "=" + value.to_s
      end.join("&")
    end

    # Encodes the given input according to RFC 3986
    def encode to_encode
      CGI.escape(to_encode)
    end

    # Calculates the url of the host from a Rack environment.
    # The result is in the form scheme://host:port
    # If port is 80 the result is scheme://host
    # According to Rack specification the HTTP_HOST variable is preferred over SERVER_NAME.
    def host_url_from_rack_env env
      port = ((env["SERVER_PORT"] == 80) && "") || ":#{env['SERVER_PORT']}"
      host = (env["HTTP_HOST"]) || (env["SERVER_NAME"] + port)
      "#{scheme(env)}://#{host}"
    end

    def scheme env
      if env['HTTPS'] == 'on'
        'https'
      elsif env['HTTP_X_FORWARDED_SSL'] == 'on'
        'https'
      elsif env['HTTP_X_FORWARDED_PROTO']
        env['HTTP_X_FORWARDED_PROTO'].split(',').first
      else
        env["rack.url_scheme"]
      end
    end

    # Classes including the module must respond to the ssl_ca_file message in order to use the following methods.
    # The response will be the path to the CA file to use when making https requests.
    # If the result of ssl_ca_file is nil no file is used. In this case a warn message is logged.
    private

    # Executes an HTTP GET request.
    # It raises a RuntimeError if the response code is not equal to 200
    def http_get host, path, params
      connection = Net::HTTP.new(host)
      process_http_response connection.request_get(path + "?" + to_query_string(params))
    end

    # Executes an HTTP POST request over SSL
    # It raises a RuntimeError if the response code is not equal to 200
    def https_post host, path, params
      https_connection host do |connection|
        connection.request_post(path, to_query_string(params))
      end
    end

    # Executes an HTTP GET request over SSL
    # It raises a RuntimeError if the response code is not equal to 200
    def https_get host, path, params, headers = nil
      https_connection host do |connection|
        connection.request_get(path + "?" + to_query_string(params), headers)
      end
    end

    def https_connection (host)
      connection = Net::HTTP.new(host, SSL_PORT)
      connection.use_ssl = true
      if ssl_ca_file
        connection.ca_file = ssl_ca_file
      else
        logger << "No SSL ca file provided. It is highly reccomended to use one in production envinronments" if respond_to?(:logger) && logger
        connection.verify_mode = OpenSSL::SSL::VERIFY_NONE
      end
      process_http_response(yield(connection))
    end

    def process_http_response response
      raise response.body if response.code != "200"
      response.body
    end

  end
end


================================================
FILE: lib/omnicontacts/importer/facebook.rb
================================================
require "omnicontacts/parse_utils"
require "omnicontacts/middleware/oauth2"
require "json"

module OmniContacts
  module Importer
    class Facebook < Middleware::OAuth2
      include ParseUtils

      attr_reader :auth_host, :authorize_path, :auth_token_path, :scope

      def initialize *args
        super *args
        @auth_host = 'graph.facebook.com'
        @authorize_path = '/oauth/authorize'
        @scope = 'email,user_relationships,user_birthday,user_friends'
        @auth_token_path = '/oauth/access_token'
        @contacts_host = 'graph.facebook.com'
        @friends_path = '/v2.5/me/friends'
        @family_path = '/v2.5/me/family'
        @self_path = '/v2.5/me'
      end

      def fetch_contacts_using_access_token access_token, access_token_secret
        self_response = fetch_current_user access_token
        user = current_user self_response
        set_current_user user
        spouse_id = extract_spouse_id self_response
        spouse_response = nil
        if spouse_id
          spouse_path = "/#{spouse_id}"
          spouse_response = https_get(@contacts_host, spouse_path, {:access_token => access_token, :fields => 'first_name,last_name,name,id,gender,birthday,picture'})
        end
        family_response = https_get(@contacts_host, @family_path, {:access_token => access_token, :fields => 'first_name,last_name,name,id,gender,birthday,picture,relationship'})
        friends_response = https_get(@contacts_host, @friends_path, {:access_token => access_token, :fields => 'first_name,last_name,name,id,gender,birthday,picture'})
        contacts_from_response(spouse_response, family_response, friends_response)
      end

      def fetch_current_user access_token
        self_response = https_get(@contacts_host, @self_path, {:access_token => access_token, :fields => 'first_name,last_name,name,id,gender,birthday,picture,relationship_status,significant_other,email'})
        self_response = JSON.parse(self_response) if self_response
        self_response
      end

      private

      def extract_spouse_id response
        return nil if response.nil?
        id = nil
        if response['significant_other'] && response['relationship_status'] == 'Married'
          id = response['significant_other']['id']
        end
        id
      end

      def contacts_from_response(spouse_response, family_response, friends_response)
        contacts = []
        family_ids = Set.new
        if spouse_response
          spouse_contact = create_contact_element(JSON.parse(spouse_response))
          spouse_contact[:relation] = 'spouse'
          contacts << spouse_contact
          family_ids.add(spouse_contact[:id])
        end
        if family_response
          family_response = JSON.parse(family_response)
          family_response['data'].each do |family_contact|
            contacts << create_contact_element(family_contact)
            family_ids.add(family_contact['id'])
          end
        end
        if friends_response
          friends_response = JSON.parse(friends_response)
          friends_response['data'].each do |friends_contact|
            contacts << create_contact_element(friends_contact) unless family_ids.include?(friends_contact['id'])
          end
        end
        contacts
      end

      def create_contact_element contact_info
        # creating nil fields to keep the fields consistent across other networks
        contact = {:id => nil, :first_name => nil, :last_name => nil, :name => nil, :email => nil, :gender => nil, :birthday => nil, :profile_picture=> nil, :relation => nil}
        contact[:id] = contact_info['id']
        contact[:first_name] = normalize_name(contact_info['first_name'])
        contact[:last_name] = normalize_name(contact_info['last_name'])
        contact[:name] = contact_info['name']
        contact[:email] = contact_info['email']
        contact[:gender] = contact_info['gender']
        contact[:birthday] = birthday(contact_info['birthday'])
        contact[:profile_picture] = image_url(contact_info['id'])
        contact[:relation] = contact_info['relationship']
        contact
      end

      def image_url fb_id
        return "https://graph.facebook.com/" + fb_id + "/picture" if fb_id
      end

      def escape_windows_format value
        value.gsub(/[\r\s]/, '')
      end

      def birthday dob
        return nil if dob.nil?
        birthday = dob.split('/')
        return birthday_format(birthday[0],birthday[1],birthday[2])
      end

      def current_user me
        return nil if me.nil?
        user = {:id => me['id'], :email => me['email'],
                :name => me['name'], :first_name => normalize_name(me['first_name']),
                :last_name => normalize_name(me['last_name']), :birthday => birthday(me['birthday']),
                :gender => me['gender'], :profile_picture => image_url(me['id'])
        }
        user
      end

    end
  end
end


================================================
FILE: lib/omnicontacts/importer/gmail.rb
================================================
require "omnicontacts/parse_utils"
require "omnicontacts/middleware/oauth2"

module OmniContacts
  module Importer
    class Gmail < Middleware::OAuth2
      include ParseUtils

      attr_reader :auth_host, :authorize_path, :auth_token_path, :scope

      def initialize *args
        super *args
        @auth_host = "accounts.google.com"
        @authorize_path = "/o/oauth2/auth"
        @auth_token_path = "/o/oauth2/token"
        @scope = (args[3] && args[3][:scope]) || "https://www.googleapis.com/auth/contacts.readonly https://www.googleapis.com/auth/userinfo#email https://www.googleapis.com/auth/userinfo.profile"
        @contacts_host = "www.google.com"
        @contacts_path = "/m8/feeds/contacts/default/full"
        @max_results =  (args[3] && args[3][:max_results]) || 100
        @self_host = "www.googleapis.com"
        @profile_path = "/oauth2/v3/userinfo"
      end

      def fetch_contacts_using_access_token access_token, token_type
        fetch_current_user(access_token, token_type)
        contacts_response = https_get(@contacts_host, @contacts_path, contacts_req_params, contacts_req_headers(access_token, token_type))
        contacts_from_response(contacts_response, access_token)
      end

      def fetch_current_user access_token, token_type
        self_response = https_get(@self_host, @profile_path, contacts_req_params, contacts_req_headers(access_token, token_type))
        user = current_user(self_response, access_token, token_type)
        set_current_user user
      end

      private

      def contacts_req_params
        {'max-results' => @max_results.to_s, 'alt' => 'json'}
      end

      def contacts_req_headers token, token_type
        {"GData-Version" => "3.0", "Authorization" => "#{token_type} #{token}"}
      end

      def contacts_from_response(response_as_json, access_token)
        response = JSON.parse(response_as_json)

        return [] if response['feed'].nil? || response['feed']['entry'].nil?
        contacts = []
        return contacts if response.nil?
        response['feed']['entry'].each do |entry|
          # creating nil fields to keep the fields consistent across other networks

          contact = { :id => nil,
                      :first_name => nil,
                      :last_name => nil,
                      :name => nil,
                      :emails => nil,
                      :gender => nil,
                      :birthday => nil,
                      :profile_picture=> nil,
                      :relation => nil,
                      :addresses => nil,
                      :phone_numbers => nil,
                      :dates => nil,
                      :company => nil,
                      :position => nil
          }
          contact[:id] = entry['id']['$t'] if entry['id']
          if entry['gd$name']
            gd_name = entry['gd$name']
            contact[:first_name] = normalize_name(entry['gd$name']['gd$givenName']['$t']) if gd_name['gd$givenName']
            contact[:last_name] = normalize_name(entry['gd$name']['gd$familyName']['$t']) if gd_name['gd$familyName']
            contact[:name] = normalize_name(entry['gd$name']['gd$fullName']['$t']) if gd_name['gd$fullName']
            contact[:name] = full_name(contact[:first_name],contact[:last_name]) if contact[:name].nil?
          end

          contact[:emails] = []
          entry['gd$email'].each do |email|
            if email['rel']
              split_index = email['rel'].index('#')
              contact[:emails] << {:name => email['rel'][split_index + 1, email['rel'].length - 1], :email => email['address']}
            elsif email['label']
              contact[:emails] << {:name => email['label'], :email => email['address']}
            end
          end if entry['gd$email']

          # Support older versions of the gem by keeping singular entries around
          contact[:email] = contact[:emails][0][:email] if contact[:emails][0]
          contact[:first_name], contact[:last_name], contact[:name] = email_to_name(contact[:name]) if !contact[:name].nil? && contact[:name].include?('@')
          contact[:first_name], contact[:last_name], contact[:name] = email_to_name(contact[:emails][0][:email]) if (contact[:name].nil? && contact[:emails][0] && contact[:emails][0][:email])
          #format - year-month-date
          contact[:birthday] = birthday(entry['gContact$birthday']['when'])  if entry['gContact$birthday']

          # value is either "male" or "female"
          contact[:gender] = entry['gContact$gender']['value']  if entry['gContact$gender']

          if entry['gContact$relation']
            if entry['gContact$relation'].is_a?(Hash)
              contact[:relation] = entry['gContact$relation']['rel']
            elsif entry['gContact$relation'].is_a?(Array)
              contact[:relation] = entry['gContact$relation'].first['rel']
            end
          end

          contact[:addresses] = []
          entry['gd$structuredPostalAddress'].each do |address|
            if address['rel']
              split_index = address['rel'].index('#')
              new_address = {:name => address['rel'][split_index + 1, address['rel'].length - 1]}
            elsif address['label']
              new_address = {:name => address['label']}
            end

            new_address[:address_1] = address['gd$street']['$t'] if address['gd$street']
            new_address[:address_1] = address['gd$formattedAddress']['$t'] if new_address[:address_1].nil? && address['gd$formattedAddress']
            if !new_address[:address_1].nil? && new_address[:address_1].index("\n")
              parts = new_address[:address_1].split("\n")
              new_address[:address_1] = parts.first
              # this may contain city/state/zip if user jammed it all into one string.... :-(
              new_address[:address_2] = parts[1..-1].join(', ')
            end
            new_address[:city] = address['gd$city']['$t'] if address['gd$city']
            new_address[:region] = address['gd$region']['$t'] if address['gd$region'] # like state or province
            new_address[:country] = address['gd$country']['code'] if address['gd$country']
            new_address[:postcode] = address['gd$postcode']['$t'] if address['gd$postcode']
            contact[:addresses] << new_address
          end if entry['gd$structuredPostalAddress']

          # Support older versions of the gem by keeping singular entries around
          if contact[:addresses][0]
            contact[:address_1] = contact[:addresses][0][:address_1]
            contact[:address_2] = contact[:addresses][0][:address_2]
            contact[:city] = contact[:addresses][0][:city]
            contact[:region] = contact[:addresses][0][:region]
            contact[:country] = contact[:addresses][0][:country]
            contact[:postcode] = contact[:addresses][0][:postcode]
          end

          contact[:phone_numbers] = []
          entry['gd$phoneNumber'].each do |phone_number|
            if phone_number['rel']
              split_index = phone_number['rel'].index('#')
              contact[:phone_numbers] << {:name => phone_number['rel'][split_index + 1, phone_number['rel'].length - 1], :number => phone_number['$t']}
            elsif phone_number['label']
              contact[:phone_numbers] << {:name => phone_number['label'], :number => phone_number['$t']}
            end
          end if entry['gd$phoneNumber']

          # Support older versions of the gem by keeping singular entries around
          contact[:phone_number] = contact[:phone_numbers][0][:number] if contact[:phone_numbers][0]

          if entry["link"] && entry["link"].is_a?(Array)
            entry["link"].each do |link|
              if link["type"] == 'image/*' && link["gd$etag"]
                contact[:profile_picture] = link["href"] + "?&access_token=" + access_token
                break
              end
            end
          end

          if entry['gContact$event']
            contact[:dates] = []
            entry['gContact$event'].each do |event|
              if event['rel']
                contact[:dates] << {:name => event['rel'], :date => birthday(event['gd$when']['startTime'])}
              elsif event['label']
                contact[:dates] << {:name => event['label'], :date => birthday(event['gd$when']['startTime'])}
              end
            end
          end

          if entry['gd$organization']
            contact[:company] = entry['gd$organization'][0]['gd$orgName']['$t'] if entry['gd$organization'][0]['gd$orgName']
            contact[:position] = entry['gd$organization'][0]['gd$orgTitle']['$t'] if entry['gd$organization'][0]['gd$orgTitle']
          end

          contacts << contact if contact[:name]
        end
        contacts.uniq! {|c| c[:email] || c[:profile_picture] || c[:name]}
        contacts
      end

      def current_user me, access_token, token_type
        return nil if me.nil?
        me = JSON.parse(me)
        user = {:id => me['id'], :email => me['email'], :name => me['name'], :first_name => me['given_name'],
                :last_name => me['family_name'], :gender => me['gender'], :birthday => birthday(me['birthday']), :profile_picture => me["picture"],
                :access_token => access_token, :token_type => token_type
        }
        user
      end

      def birthday dob
        return nil if dob.nil?
        birthday = dob.split('-')
        return birthday_format(birthday[2], birthday[3], nil) if birthday.size == 4
        return birthday_format(birthday[1], birthday[2], birthday[0]) if birthday.size == 3
      end

      def contact_id(profile_url)
        id = (profile_url.present?) ? File.basename(profile_url) : nil
        id
      end

    end
  end
end


================================================
FILE: lib/omnicontacts/importer/hotmail.rb
================================================
require "omnicontacts/middleware/oauth2"
require "omnicontacts/parse_utils"
require "json"

module OmniContacts
  module Importer
    class Hotmail < Middleware::OAuth2
      include ParseUtils

      attr_reader :auth_host, :authorize_path, :auth_token_path, :scope

      def initialize app, client_id, client_secret, options ={}
        super app, client_id, client_secret, options
        @auth_host = "login.live.com"
        @authorize_path = "/oauth20_authorize.srf"
        @scope = options[:permissions] || "wl.signin, wl.basic, wl.birthday , wl.emails ,wl.contacts_birthday , wl.contacts_photos, wl.contacts_emails"
        @auth_token_path = "/oauth20_token.srf"
        @contacts_host = "apis.live.net"
        @contacts_path = "/v5.0/me/contacts"
        @self_path = "/v5.0/me"
      end

      def fetch_contacts_using_access_token access_token, access_token_secret
        fetch_current_user(access_token)
        contacts_response = https_get(@contacts_host, @contacts_path, :access_token => access_token)
        contacts_from_response contacts_response
      end

      def fetch_current_user access_token
        self_response =  https_get(@contacts_host, @self_path, :access_token => access_token)
        user = current_user self_response
        set_current_user user
      end

      private

      def contacts_from_response response_as_json
        response = JSON.parse(response_as_json)
        contacts = []
        response['data'].each do |entry|
          # creating nil fields to keep the fields consistent across other networks
          contact = {:id => nil, :first_name => nil, :last_name => nil, :name => nil, :email => nil, :gender => nil, :birthday => nil, :profile_picture=> nil, :relation => nil, :email_hashes => []}
          contact[:id] = entry['user_id'] ? entry['user_id'] : entry['id']
	        contact[:email] = parse_email(entry['emails']) if valid_email? parse_email(entry['emails'])
	        contact[:first_name] = normalize_name(entry['first_name'])
	        contact[:last_name] = normalize_name(entry['last_name'])
	        contact[:name] = normalize_name(entry['name'])
          contact[:birthday] = birthday_format(entry['birth_month'], entry['birth_day'], entry['birth_year'])
          contact[:gender] = entry['gender']
          contact[:profile_picture] = image_url(entry['user_id'])
          contact[:email_hashes] = entry['email_hashes']
          contacts << contact if contact[:name] || contact[:first_name]
        end
        contacts
      end

      def parse_email(emails)
        return nil if emails.nil?
        emails['account'] || emails['preferred'] || emails['personal'] || emails['business'] || emails['other']
      end

      def current_user me
        return nil if me.nil?
        me = JSON.parse(me)
        email = parse_email(me['emails'])
        user = {:id => me['id'], :email => email, :name => me['name'], :first_name => me['first_name'],
                :last_name => me['last_name'], :gender => me['gender'], :profile_picture => image_url(me['id']),
                :birthday => birthday_format(me['birth_month'], me['birth_day'], me['birth_year'])
        }
        user
      end


      def image_url hotmail_id
        return 'https://apis.live.net/v5.0/' + hotmail_id + '/picture' if hotmail_id
      end

      def escape_windows_format value
        value.gsub(/[\r\s]/, '')
      end

      def valid_email? value
        /\A[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]+\z/.match(value)
      end

    end
  end
end


================================================
FILE: lib/omnicontacts/importer/linkedin.rb
================================================
require "omnicontacts/parse_utils"
require "omnicontacts/middleware/oauth2"

module OmniContacts
  module Importer
    class Linkedin < Middleware::OAuth2
      include ParseUtils

      attr_reader :auth_host, :authorize_path, :auth_token_path, :scope, :state

      def initialize *args
        super *args
        @auth_host = "www.linkedin.com"
        @authorize_path = "/uas/oauth2/authorization"
        @auth_token_path = "/uas/oauth2/accessToken"
        @scope = (args[3] && args[3][:scope]) || "r_network"
        @contacts_host = "api.linkedin.com"
        @contacts_path = "/v1/people/~/connections:(id,first-name,last-name,picture-url)"
        @self_host = "www.linkedin.com"
        @profile_path = "/oauth2/v1/userinfo"
        @state = (args[3] && args[3][:state])
      end

      def fetch_contacts_using_access_token access_token, token_type
        token_type = "Bearer" if token_type.nil?
        contacts_response = https_get(@contacts_host, @contacts_path, contacts_req_params, contacts_req_headers(access_token, token_type))
        contacts_from_response contacts_response
      end

      private

      def contacts_req_params
        {'format' => 'json'}
      end

      def contacts_req_headers token, token_type
        {"Authorization" => "#{token_type} #{token}"}
      end

      def contacts_from_response response_as_json
        response = JSON.parse(response_as_json)
        return [] if response['values'].nil?
        contacts = []
        return contacts if response.nil?
        response['values'].map do |entry|
          {
           id: entry['id'],
            first_name: normalize_name(entry['firstName']),
            last_name: normalize_name(entry['lastName']),
            name: full_name(entry['firstName'],entry['lastName']),
            profile_picture: entry['pictureUrl']
          }
        end
      end

      def authorize_url_params
        # merge state param required by LinkedIn
        _params = super
        _params += "&" + to_query_string(state: @state)
      end
    end
  end
end


================================================
FILE: lib/omnicontacts/importer/outlook.rb
================================================
require "omnicontacts/middleware/oauth2"
require "omnicontacts/parse_utils"
require "json"

# API Docs: https://msdn.microsoft.com/en-us/office/office365/api/api-catalog#Outlookcontacts
module OmniContacts
  module Importer
    class Outlook < Middleware::OAuth2
      include ParseUtils

      attr_reader :auth_host, :authorize_path, :auth_token_path, :scope

      def initialize app, client_id, client_secret, options ={}
        super app, client_id, client_secret, options
        @auth_host = "login.microsoftonline.com"
        @authorize_path = "/common/oauth2/v2.0/authorize"
        @scope = options[:permissions] || "https://outlook.office.com/contacts.read"
        @auth_token_path = "/common/oauth2/v2.0/token"
        @contacts_host = "outlook.office.com"
        @contacts_path = "/api/v2.0/me/contacts"
        @self_path = "/api/v2.0/me"
      end

      def fetch_contacts_using_access_token access_token, token_type
        fetch_current_user(access_token, token_type)
        contacts_response = https_get(@contacts_host, @contacts_path, {}, contacts_req_headers(access_token, token_type))
        contacts_from_response contacts_response
      end

      def fetch_current_user access_token, token_type
        self_response = https_get(@contacts_host, @self_path, {}, contacts_req_headers(access_token, token_type))
        user = current_user self_response
        set_current_user user
      end

      private

      def contacts_req_headers token, token_type
        { "Authorization" => "#{token_type} #{token}" }
      end

      def current_user me
        return nil if me.nil?
        me = JSON.parse(me)

        name_splitted = me["DisplayName"].split(" ")
        first_name = name_splitted.first
        last_name = name_splitted.last if name_splitted.size > 1

        user = empty_contact
        user[:id]         = me["Id"]
        user[:email]      = me["EmailAddress"]
        user[:name]       = me["DisplayName"]
        user[:first_name] = normalize_name(first_name)
        user[:last_name]  = normalize_name(last_name)
        user
      end

      def contacts_from_response response_as_json
        response = JSON.parse(response_as_json)
        contacts = []
        response["value"].each do |entry|
          contact = empty_contact
          # Full fields reference:
          # https://msdn.microsoft.com/office/office365/api/complex-types-for-mail-contacts-calendar#RESTAPIResourcesContact
          contact[:id]         = entry["Id"]
          contact[:first_name] = entry["GivenName"]
          contact[:last_name]  = entry["Surname"]
          contact[:name]       = entry["DisplayName"]
          contact[:email]      = parse_email(entry["EmailAddresses"])
          contact[:birthday]   = birthday(entry["Birthday"])

          address = [entry["HomeAddress"], entry["BusinessAddress"], entry["OtherAddress"]].reject(&:empty?).first
          if address
            contact[:address_1] = address["Street"]
            contact[:city]      = address["City"]
            contact[:region]    = address["State"]
            contact[:postcode]  = address["PostalCode"]
            contact[:country]   = address["CountryOrRegion"]
          end

          contacts << contact if contact[:name] || contact[:first_name]
        end
        contacts
      end

      def empty_contact
        { :id => nil, :first_name => nil, :last_name => nil, :name => nil, :email => nil,
          :gender => nil, :birthday => nil, :profile_picture => nil, :address_1 => nil,
          :address_2 => nil, :city => nil, :region => nil, :postcode => nil, :relation => nil }
      end

      def parse_email emails
        return nil if emails.nil?
        emails.map! { |email| email["Address"] }
        emails.select! { |email| valid_email? email }
        emails.first
      end

      def birthday dob
        return nil if dob.nil?
        birthday = dob[0..9].split("-")
        birthday[0] = nil if birthday[0].to_i < 1900 # if year is not set it returns 1604
        return birthday_format(birthday[1], birthday[2], birthday[0])
      end

      def valid_email? value
        /\A[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]+\z/.match(value)
      end
    end
  end
end


================================================
FILE: lib/omnicontacts/importer/yahoo.rb
================================================
require "omnicontacts/parse_utils"
require "omnicontacts/middleware/oauth1"
require "json"

module OmniContacts
  module Importer
    class Yahoo < Middleware::OAuth1
      include ParseUtils

      attr_reader :auth_host, :auth_token_path, :auth_path, :access_token_path

      def initialize *args
        super *args
        @auth_host = 'api.login.yahoo.com'
        @auth_token_path = '/oauth/v2/get_request_token'
        @auth_path = '/oauth/v2/request_auth'
        @access_token_path = '/oauth/v2/get_token'
        @contacts_host = 'social.yahooapis.com'
      end

      def fetch_contacts_from_token_and_verifier auth_token, auth_token_secret, auth_verifier
        (access_token, access_token_secret, guid) = fetch_access_token(auth_token, auth_token_secret, auth_verifier, ['xoauth_yahoo_guid'])
        fetch_current_user(access_token, access_token_secret, guid)
        contacts_path = "/v1/user/#{guid}/contacts"
        contacts_response = https_get(@contacts_host, contacts_path, contacts_req_params(access_token, access_token_secret, contacts_path))
        contacts_from_response contacts_response
      end

      def fetch_current_user access_token, access_token_secret, guid
        self_path = "/v1/user/#{guid}/profile"
        self_response =  https_get(@contacts_host, self_path, contacts_req_params(access_token, access_token_secret, self_path))
        user = current_user self_response
        set_current_user user
      end

      private

      def contacts_req_params access_token, access_token_secret, contacts_path
        params = {
            :format => 'json',
            :oauth_consumer_key => consumer_key,
            :oauth_nonce => encode(random_string),
            :oauth_signature_method => 'HMAC-SHA1',
            :oauth_timestamp => timestamp,
            :oauth_token => access_token,
            :oauth_version => OmniContacts::Authorization::OAuth1::OAUTH_VERSION
        }
        contacts_url = "https://#{@contacts_host}#{contacts_path}"
        params['oauth_signature'] = oauth_signature('GET', contacts_url, params, access_token_secret)
        params
      end

      def contacts_from_response response_as_json
        response = JSON.parse(response_as_json)
        contacts = []
        return contacts unless response['contacts']['contact']
        response['contacts']['contact'].each do |entry|
          # creating nil fields to keep the fields consistent across other networks
          contact = { :id => nil,
                      :first_name => nil,
                      :last_name => nil,
                      :name => nil,
                      :email => nil,
                      :gender => nil,
                      :birthday => nil,
                      :profile_picture=> nil,
                      :address_1 => nil,
                      :address_2 => nil,
                      :city => nil,
                      :region => nil,
                      :postcode => nil,
                      :relation => nil }
          yahoo_id = nil
          contact[:id] = entry['id'].to_s
          entry['fields'].each do |field|
            case field['type']
            when 'name'
              contact[:first_name] = normalize_name(field['value']['givenName'])
              contact[:last_name] = normalize_name(field['value']['familyName'])
              contact[:name] = full_name(contact[:first_name],contact[:last_name])
            when 'email'
              contact[:email] = field['value'] if field['type'] == 'email'
            when 'yahooid'
              yahoo_id = field['value']
            when 'address'
              value = field['value']
              contact[:address_1] = street = value['street']
              if street.index("\n")
                parts = street.split("\n")
                contact[:address_1] = parts.first
                contact[:address_2] = parts[1..-1].join(', ')
              end
              contact[:city] = value['city']
              contact[:region] = value['stateOrProvince']
              contact[:postcode] = value['postalCode']
            when 'birthday'
              contact[:birthday] = birthday_format(field['value']['month'], field['value']['day'],field['value']['year'])
            end
            contact[:first_name], contact[:last_name], contact[:name] = email_to_name(contact[:email]) if contact[:name].nil? && contact[:email]
            # contact[:first_name], contact[:last_name], contact[:name] = email_to_name(yahoo_id) if (yahoo_id && contact[:name].nil? && contact[:email].nil?)

            if yahoo_id
              contact[:profile_picture] = image_url(yahoo_id)
            else
              contact[:profile_picture] = image_url_from_email(contact[:email])
            end
          end
          contacts << contact if contact[:name]
        end
        contacts.uniq! {|c| c[:email] || c[:profile_picture] || c[:name]}
        contacts
      end

      def image_url yahoo_id
        return 'https://img.msg.yahoo.com/avatar.php?yids=' + yahoo_id if yahoo_id
      end

      def parse_email(emails)
        return nil if emails.nil?
        email = nil
        if emails.is_a?(Hash)
          if emails.has_key?("primary")
            email = emails['handle']
          end
        elsif emails.is_a?(Array)
          emails.each do |e|
            if e.has_key?('primary') && e['primary']
              email = e['handle']
              break
            end
          end
        end
        email
      end

      def birthday dob
        return nil if dob.nil?
        birthday = dob.split('/')
        return birthday_format(birthday[0], birthday[1], birthday[3]) if birthday.size == 3
        return birthday_format(birthday[0], birthday[1], nil) if birthday.size == 2

      end

      def gender g
        return "female" if g == "F"
        return "male" if g == "M"
      end

      def my_image img
        return nil if img.nil?
        return img['imageUrl']
      end

      def current_user me
        return nil if me.nil?
        me = JSON.parse(me)
        me = me['profile']
        email = parse_email(me['emails'])
        user = {:id => me["guid"], :email => email, :name => full_name(me['givenName'],me['familyName']), :first_name => normalize_name(me['givenName']),
                :last_name => normalize_name(me['familyName']), :gender => gender(me['gender']), :birthday => birthday(me['birthdate']),
                :profile_picture => my_image(me['image'])
               }
        user
      end

      #def profile_image_url(guid, access_token, access_token_secret)
      #  image_path = "/v1/user/#{guid}/profile/image/48x48"
      #  response = https_get(@contacts_host, image_path, contacts_req_params(access_token, access_token_secret, image_path))
      #  image_data = JSON.parse(response)
      #  return image_data['image']['imageUrl'] if image_data['image']
      #  return nil
      #end
    end
  end
end


================================================
FILE: lib/omnicontacts/importer.rb
================================================
module OmniContacts
  module Importer

    autoload :Gmail, "omnicontacts/importer/gmail"
    autoload :Yahoo, "omnicontacts/importer/yahoo"
    autoload :Hotmail, "omnicontacts/importer/hotmail"
    autoload :Outlook, "omnicontacts/importer/outlook"
    autoload :Facebook, "omnicontacts/importer/facebook"
    autoload :Linkedin, "omnicontacts/importer/linkedin"

  end
end


================================================
FILE: lib/omnicontacts/integration_test.rb
================================================
require 'singleton'

class IntegrationTest
  include Singleton

  attr_accessor :enabled

  def initialize
    enabled = false
    clear_mocks
  end

  def clear_mocks
    @user_mocks = {}
    @contact_mocks = {}
  end

  def mock provider, contacts, user = {}
    @contact_mocks[provider.to_sym] = contacts
    @user_mocks[provider.to_sym] = user
  end

  def mock_authorization_from_user provider
    [302, {"Content-Type" => "application/x-www-form-urlencoded", "location" => provider.redirect_path}, []]
  end

  def mock_fetch_contacts provider
    result = @contact_mocks[provider.class_name.to_sym] || []
    if result.is_a? Array
      result
    elsif result.is_a? Hash
      [result]
    else
      raise result.to_s
    end
  end

  def mock_fetch_user provider
    @user_mocks[provider.class_name.to_sym] || {}
  end
end


================================================
FILE: lib/omnicontacts/middleware/base_oauth.rb
================================================
# This class contains the common behavior for middlewares
# implementing either versions of OAuth.
#
# Extending classes are required to implement
# the following methods:
# * request_authorization_from_user
# * fetch_contatcs
module OmniContacts
  module Middleware
    class BaseOAuth

      attr_reader :ssl_ca_file

      def initialize app, options
        @app = app
        @listening_path = MOUNT_PATH + class_name
        @ssl_ca_file = options[:ssl_ca_file]
      end

      def class_name
        self.class.name.split('::').last.downcase
      end

      # Rack callback. It handles three cases:
      # * user visit middleware entry point.
      #   In this case request_authorization_from_user is called
      # * user is redirected back to the application
      #   from the authorization site. In this case the list
      #   of contacts is fetched and stored in the variables
      #   omnicontacts.contacts within the Rack env variable.
      #   Once that is done the next middleware component is called.
      # * user visits any other resource. In this case the request
      #   is simply forwarded to the next middleware component.
      def call env
        @env = env
        if env["PATH_INFO"] =~ /^#{@listening_path}\/?$/
          session['omnicontacts.params'] = Rack::Request.new(env).params
          handle_initial_request
        elsif env["PATH_INFO"] =~ /^#{redirect_path}/
          env['omnicontacts.params'] = session.delete('omnicontacts.params')
          handle_callback
        else
          @app.call(env)
        end
      end

      private

      def test_mode?
        IntegrationTest.instance.enabled
      end

      def handle_initial_request
        execute_and_rescue_exceptions do
          if test_mode?
            IntegrationTest.instance.mock_authorization_from_user(self)
          else
            request_authorization_from_user
          end
        end
      end

      def handle_callback
        execute_and_rescue_exceptions do
          @env["omnicontacts.contacts"] = if test_mode?
            IntegrationTest.instance.mock_fetch_contacts(self)
          else
            fetch_contacts
          end
          set_current_user IntegrationTest.instance.mock_fetch_user(self) if test_mode?
          @app.call(@env)
        end
      end

      def set_current_user user
        @env["omnicontacts.user"] = user
      end

      #  This method rescues executes a block of code and
      #  rescue all exceptions. In case of an exception the
      #  user is redirected to the failure endpoint.
      def execute_and_rescue_exceptions
        yield
      rescue AuthorizationError => e
        handle_error :not_authorized, e
      rescue ::Timeout::Error, ::Errno::ETIMEDOUT => e
        handle_error :timeout, e
      rescue ::RuntimeError => e
        handle_error :internal_error, e
      end

      def handle_error error_type, exception
        logger.puts("Error #{error_type} while processing #{@env["PATH_INFO"]}: #{exception.message}") if logger
        failure_url = "#{ MOUNT_PATH }failure?error_message=#{error_type}&importer=#{class_name}"
        params_url = append_request_params(failure_url)
        target_url = append_state_query(params_url)
        [302, {"Content-Type" => "text/html", "location" => target_url}, []]
      end

      def session
        raise "You must provide a session to use OmniContacts" unless @env["rack.session"]
        @env["rack.session"]
      end

      def logger
        @env["rack.errors"] if @env
      end

      def base_prop_name
        "omnicontacts." + class_name
      end

      def append_request_params(target_url)
        return target_url unless @env['omnicontacts.params']
        params = Rack::Utils.build_query(@env['omnicontacts.params'])
        unless params.nil? or params.empty?
          target_url = target_url + (target_url.include?("?")?"&":"?") + params
        end
        return target_url
      end

      def append_state_query(target_url)
        state = Rack::Utils.parse_query(@env['QUERY_STRING'])['state']
        unless state.nil?
          target_url = target_url + (target_url.include?("?")?"&":"?") + 'state=' + state
        end

        return target_url
      end
    end
  end
end


================================================
FILE: lib/omnicontacts/middleware/oauth1.rb
================================================
require "omnicontacts/authorization/oauth1"
require "omnicontacts/middleware/base_oauth"

# This class is an OAuth 1.0 Rack middleware.
#
# Extending classes are required to 
# implement the following methods:
# * fetch_token_from_token_and_verifier -> this method has to
#   fetch the list of contacts from the authorization server.
module OmniContacts
  module Middleware
    class OAuth1 < BaseOAuth
      include Authorization::OAuth1

      attr_reader :consumer_key, :consumer_secret, :callback_path

      def initialize app, consumer_key, consumer_secret, options = {}
        super app, options
        @consumer_key = consumer_key
        @consumer_secret = consumer_secret
        @callback_path = options[:callback_path] || "#{ MOUNT_PATH }#{class_name}/callback"
        @token_prop_name = "#{base_prop_name}.oauth_token"
      end

      def callback
        host_url_from_rack_env(@env) + callback_path
      end

      alias :redirect_path :callback_path

      # Obtains an authorization token from the server,
      # stores it and the session and redirect the user
      # to the authorization website.
      def request_authorization_from_user
        (auth_token, auth_token_secret) = fetch_authorization_token
        session[@token_prop_name] = auth_token
        session[token_secret_prop_name(auth_token)] = auth_token_secret
        redirect_to_authorization_site(auth_token)
      end

      def token_secret_prop_name oauth_token
        "#{base_prop_name}.#{oauth_token}.oauth_token_secret"
      end

      def redirect_to_authorization_site auth_token
        authorization_url = authorization_url(auth_token)
        target_url = append_state_query(authorization_url)
        [302, {"Content-Type" => "application/x-www-form-urlencoded", "location" => target_url}, []]
      end

      # Parses the authorization token from the query string and 
      # obtain the relative secret from the session.
      # Finally it calls fetch_contacts_from_token_and_verifier.
      # If token is found in the query string an AuhorizationError
      # is raised.
      def fetch_contacts
        params = query_string_to_map(@env["QUERY_STRING"])
        oauth_token = params["oauth_token"]
        oauth_verifier = params["oauth_verifier"]
        oauth_token_secret = session[token_secret_prop_name(oauth_token)]
        if oauth_token && oauth_verifier && oauth_token_secret
          fetch_contacts_from_token_and_verifier(oauth_token, oauth_token_secret, oauth_verifier)
        else
          raise AuthorizationError.new("User did not grant access to contacts list")
        end
      end

    end
  end
end


================================================
FILE: lib/omnicontacts/middleware/oauth2.rb
================================================
require "omnicontacts/authorization/oauth2"
require "omnicontacts/middleware/base_oauth"

# This class is a OAuth 2 Rack middleware.
#
# Extending class are required to implement
# the following methods:
# * fetch_contacts_using_access_token -> it
#   fetches the list of contacts from the authorization
#   server.
module OmniContacts
  module Middleware
    class OAuth2 < BaseOAuth
      include Authorization::OAuth2

      attr_reader :client_id, :client_secret, :redirect_path

      def initialize app, client_id, client_secret, options ={}
        super app, options
        @client_id = client_id
        @client_secret = client_secret
        @redirect_path = options[:redirect_path] || "#{ MOUNT_PATH }#{class_name}/callback"
        @ssl_ca_file = options[:ssl_ca_file]
      end

      def request_authorization_from_user
        target_url = append_state_query(authorization_url)
        [302, {"Content-Type" => "application/x-www-form-urlencoded", "location" => target_url}, []]
      end

      def redirect_uri
        host_url_from_rack_env(@env) + redirect_path
      end

      # It extract the authorization code from the query string.
      # It uses it to obtain an access token.
      # If the authorization code has a refresh token associated
      # with it in the session, it uses the obtain an access token.
      # It fetches the list of contacts and stores the refresh token
      # associated with the access token in the session.
      # Finally it returns the list of contacts.
      # If no authorization code is found in the query string an
      # AuthoriazationError is raised.
      def fetch_contacts
        code = query_string_to_map(@env["QUERY_STRING"])["code"]
        if code
          refresh_token = session[refresh_token_prop_name(code)]
          (access_token, token_type, refresh_token) = if refresh_token
                                                        refresh_access_token(refresh_token)
                                                      else
                                                        fetch_access_token(code)
                                                      end
          contacts = fetch_contacts_using_access_token(access_token, token_type)
          session[refresh_token_prop_name(code)] = refresh_token if refresh_token
          contacts
        else
          raise AuthorizationError.new("User did not grant access to contacts list")
        end
      end

      def refresh_token_prop_name code
        "#{base_prop_name}.#{code}.refresh_token"
      end

    end
  end
end


================================================
FILE: lib/omnicontacts/parse_utils.rb
================================================
module OmniContacts
  module ParseUtils

    # return has of birthday day, month and year
    def birthday_format month, day, year
      return {:day => day.to_i, :month => month.to_i, :year => year.to_i}if year && month && day
      return {:day => day.to_i, :month => month.to_i, :year => nil} if !year && month && day
      return nil if (!year && !month) || (!year && !day)
    end

    # normalize the name
    def normalize_name name
      return nil if name.nil?
      name.chomp!
      name.squeeze!(' ')
      name.strip!
      return name
    end

    # create a full name given the individual first and last name
    def full_name first_name, last_name
      return "#{first_name} #{last_name}" if first_name && last_name
      return "#{first_name}" if first_name && !last_name
      return "#{last_name}" if !first_name && last_name
      return nil
    end

    # create a username/name from a given email
    def email_to_name username_or_email
      username_or_email = username_or_email.split('@').first if username_or_email.include?('@')
      if group = (/(?<first>[a-z|A-Z]+)[\.|_](?<last>[a-z|A-Z]+)/).match(username_or_email)
        first_name = normalize_name(group[:first])
        last_name = normalize_name(group[:last])
        return first_name, last_name, "#{first_name} #{last_name}"
      end
      username = normalize_name(username_or_email)
      return username, nil, username
    end

    # create an image_url from a gmail or yahoo email id.
    def image_url_from_email email
      return nil if email.nil? || !email.include?('@')
      username, domain = *(email.split('@'))
      return nil if username.nil? or domain.nil?
      gmail_base_url = "https://profiles.google.com/s2/photos/profile/"
      yahoo_base_url = "https://img.msg.yahoo.com/avatar.php?yids="
      if domain.include?('gmail')
        image_url = gmail_base_url + username
      elsif domain.include?('yahoo')
        image_url = yahoo_base_url + username
      end
      image_url
    end

  end
end


================================================
FILE: lib/omnicontacts.rb
================================================
module OmniContacts
  
  VERSION = "0.3.10"

  MOUNT_PATH = "/contacts/"

  autoload :Builder, "omnicontacts/builder"
  autoload :Importer, "omnicontacts/importer"
  autoload :IntegrationTest, "omnicontacts/integration_test"

  class AuthorizationError < RuntimeError
  end


  def self.integration_test
    IntegrationTest.instance
  end
  
end


================================================
FILE: omnicontacts.gemspec
================================================
# encoding: utf-8
require File.expand_path('../lib/omnicontacts', __FILE__)

Gem::Specification.new do |gem|
  gem.name = 'omnicontacts'
  gem.description = %q{A generalized Rack middleware for importing contacts from major email providers.}
  gem.authors = ['Diego Castorina', 'Jordan Lance', 'Asma Tameem', 'Randy Villanueva']
  gem.email = ['diegocastorina@gmail.com', 'voorruby@gmail.com']

  gem.add_runtime_dependency 'rack'
  gem.add_runtime_dependency 'json'

  gem.add_development_dependency 'simplecov'
  gem.add_development_dependency 'rake'
  gem.add_development_dependency 'rack-test'
  gem.add_development_dependency 'rspec'

  gem.version = OmniContacts::VERSION
  gem.files = `git ls-files`.split("\n")
  gem.homepage = 'http://github.com/Diego81/omnicontacts'
  gem.require_paths = ['lib']
  gem.required_rubygems_version = Gem::Requirement.new('>= 1.3.6') if gem.respond_to? :required_rubygems_version=
  gem.summary = gem.description
  gem.test_files = `git ls-files -- {spec}/*`.split("\n")
end


================================================
FILE: spec/omnicontacts/authorization/oauth1_spec.rb
================================================
require "spec_helper"
require "omnicontacts/authorization/oauth1"

describe OmniContacts::Authorization::OAuth1 do

  before(:all) do
    OAuth1TestClass= Struct.new(:consumer_key, :consumer_secret, :auth_host, :auth_token_path, :auth_path, :access_token_path, :callback)
    class OAuth1TestClass
      include OmniContacts::Authorization::OAuth1
    end
  end

  let(:test_target) do
    OAuth1TestClass.new("consumer_key", "secret1", "auth_host", "auth_token_path", "auth_path", "access_token_path", "callback")
  end

  describe "fetch_authorization_token" do

    it "should request the token providing all mandatory parameters" do
      test_target.should_receive(:https_post) do |host, path, params|
        host.should eq(test_target.auth_host)
        path.should eq(test_target.auth_token_path)
        params[:oauth_consumer_key].should eq(test_target.consumer_key)
        params[:oauth_nonce].should_not be_nil
        params[:oauth_signature_method].should eq("PLAINTEXT")
        params[:oauth_signature].should eq(test_target.consumer_secret + "%26")
        params[:oauth_timestamp].should_not be_nil
        params[:oauth_version].should eq("1.0")
        params[:oauth_callback].should eq(test_target.callback)
        "oauth_token=token&oauth_token_secret=token_secret"
      end
      test_target.fetch_authorization_token
    end

    it "should successfully parse the result" do
      test_target.should_receive(:https_post).and_return("oauth_token=token&oauth_token_secret=token_secret")
      test_target.fetch_authorization_token.should eq(["token", "token_secret"])
    end

    it "should raise an error if request is invalid" do
      test_target.should_receive(:https_post).and_return("invalid_request")
      expect { test_target.fetch_authorization_token }.to raise_error
    end

  end

  describe "authorization_url" do
    subject { test_target.authorization_url("token") }
    it { should eq("https://#{test_target.auth_host}#{test_target.auth_path}?oauth_token=token") }
  end

  describe "fetch_access_token" do
    it "should request the access token using all required parameters" do
      auth_token = "token"
      auth_token_secret = "token_secret"
      auth_verifier = "verifier"
      test_target.should_receive(:https_post) do |host, path, params|
        host.should eq(test_target.auth_host)
        path.should eq(test_target.access_token_path)
        params[:oauth_consumer_key].should eq(test_target.consumer_key)
        params[:oauth_nonce].should_not be_nil
        params[:oauth_signature_method].should eq("PLAINTEXT")
        params[:oauth_version].should eq("1.0")
        params[:oauth_signature].should eq("#{test_target.consumer_secret}%26#{auth_token_secret}")
        params[:oauth_token].should eq(auth_token)
        params[:oauth_verifier].should eq(auth_verifier)
        "oauth_token=access_token&oauth_token_secret=access_token_secret&other_param=other_value"
      end
      test_target.fetch_access_token auth_token, auth_token_secret, auth_verifier, ["other_param"]
    end

    it "should successfully extract access_token and the other fields" do
      test_target.should_receive(:https_post).and_return("oauth_token=access_token&oauth_token_secret=access_token_secret&other_param=other_value")
      test_target.fetch_access_token("token", "token_scret", "verified", ["other_param"]).should eq(["access_token", "access_token_secret", "other_value"])
    end
  end

  describe "oauth_signature" do
    subject { test_target.oauth_signature("GET", "https://social.yahooapis.com/v1/user", {:name => "diego", :surname => "castorina"}, "secret2") }
    it { should eq("xfumZoyVYUbHXSAafdha3HZUqQg%3D") }
  end
end


================================================
FILE: spec/omnicontacts/authorization/oauth2_spec.rb
================================================
require "spec_helper"
require "omnicontacts/authorization/oauth2"

describe OmniContacts::Authorization::OAuth2 do

  before(:all) do
    OAuth2TestClass= Struct.new(:auth_host, :authorize_path, :client_id, :client_secret, :scope, :redirect_uri, :auth_token_path)
    class OAuth2TestClass
      include OmniContacts::Authorization::OAuth2
    end
  end

  let(:test_target) do
    OAuth2TestClass.new("auth_host", "authorize_path", "client_id", "client_secret", "scope", "redirect_uri", "auth_token_path")
  end

  describe "authorization_url" do

    subject { test_target.authorization_url }

    it { should include("https://#{test_target.auth_host}#{test_target.authorize_path}") }
    it { should include("client_id=#{test_target.client_id}") }
    it { should include("scope=#{test_target.scope}") }
    it { should include("redirect_uri=#{test_target.redirect_uri}") }
    it { should include("access_type=online") }
    it { should include("response_type=code") }
  end

  let(:access_token_response) { %[{"access_token": "access_token", "token_type":"token_type", "refresh_token":"refresh_token"}] }

  describe "fetch_access_token" do

    it "should provide all mandatory parameters in a https post request" do
      code = "code"
      test_target.should_receive(:https_post) do |host, path, params|
        host.should eq(test_target.auth_host)
        path.should eq(test_target.auth_token_path)
        params[:code].should eq(code)
        params[:client_id].should eq(test_target.client_id)
        params[:client_secret].should eq(test_target.client_secret)
        params[:redirect_uri].should eq(test_target.redirect_uri)
        params[:grant_type].should eq("authorization_code")
        access_token_response
      end
      test_target.fetch_access_token code
    end

    it "should successfully parse the token from the JSON response" do
      test_target.should_receive(:https_post).and_return(access_token_response)
      (access_token, token_type, refresh_token) = test_target.fetch_access_token "code"
      access_token.should eq("access_token")
      token_type.should eq("token_type")
      refresh_token.should eq("refresh_token")
    end

    it "should raise if the http request fails" do
      test_target.should_receive(:https_post).and_raise("Invalid code")
      expect { test_target.fetch_access_token("code") }.to raise_error
    end

    it "should raise an error if the JSON response contains an error field" do
      test_target.should_receive(:https_post).and_return(%[{"error": "error_message"}])
      expect { test_target.fetch_access_token("code") }.to raise_error
    end
  end

  describe "refresh_access_token" do
    it "should provide all mandatory fields in a https post request" do
      refresh_token = "refresh_token"
      test_target.should_receive(:https_post) do |host, path, params|
        host.should eq(test_target.auth_host)
        path.should eq(test_target.auth_token_path)
        params[:client_id].should eq(test_target.client_id)
        params[:client_secret].should eq(test_target.client_secret)
        params[:refresh_token].should eq(refresh_token)
        params[:grant_type].should eq("refresh_token")
        access_token_response
      end
      test_target.refresh_access_token refresh_token
    end

    it "should successfully parse the token from the JSON response" do
      test_target.should_receive(:https_post).and_return(access_token_response)
      (access_token, token_type, refresh_token) = test_target.refresh_access_token "refresh_token"
      access_token.should eq("access_token")
      token_type.should eq("token_type")
      refresh_token.should eq("refresh_token")
    end

  end

end


================================================
FILE: spec/omnicontacts/http_utils_spec.rb
================================================
require "spec_helper"
require "omnicontacts/http_utils"

describe OmniContacts::HTTPUtils do

  describe "to_query_string" do
    it "should create a query string from a map" do
      result = OmniContacts::HTTPUtils.to_query_string(:name => "john", :surname => "doe")
      if result.match(/^name/)
        result.should eq("name=john&surname=doe")
      else
        result.should eq("surname=doe&name=john")
      end
    end

    it "should work for integer values in the map" do
      result = OmniContacts::HTTPUtils.to_query_string(:client_id => 1234, :secret => "1234HJL8")
      result.should eq("client_id=1234&secret=1234HJL8")
    end

  end

  describe "encode" do
    it "should encode the space" do
      OmniContacts::HTTPUtils.encode("name=\"john\"").should eq("name%3D%22john%22")
    end
  end

  describe "query_string_to_map" do
    it "should split a query string into a map" do
      query_string = "name=john&surname=doe"
      result = OmniContacts::HTTPUtils.query_string_to_map(query_string)
      result["name"].should eq("john")
      result["surname"].should eq("doe")
    end
  end

  describe "host_url_from_rack_env" do
    it "should calculate the host url using the HTTP_HOST variable" do
      env = {"rack.url_scheme" => "http", "HTTP_HOST" => "localhost:8080", "SERVER_NAME" => "localhost", "SERVER_PORT" => 8080}
      OmniContacts::HTTPUtils.host_url_from_rack_env(env).should eq("http://localhost:8080")
    end

    it "should calculate the host url using SERVER_NAME and SERVER_PORT variables" do
      env = {"rack.url_scheme" => "http", "SERVER_NAME" => "localhost", "SERVER_PORT" => 8080}
      OmniContacts::HTTPUtils.host_url_from_rack_env(env).should eq("http://localhost:8080")
    end
  end

  describe "https_post" do

    before(:each) do
      @connection = double
      Net::HTTP.should_receive(:new).and_return(@connection)
      @connection.should_receive(:use_ssl=).with(true)
      @test_target = Object.new
      @test_target.extend OmniContacts::HTTPUtils
      @response = double
    end

    it "should execute a request with success" do
      @test_target.should_receive(:ssl_ca_file).and_return(nil)
      @connection.should_receive(:verify_mode=).with(OpenSSL::SSL::VERIFY_NONE)
      @connection.should_receive(:request_post).and_return(@response)
      @response.should_receive(:code).and_return("200")
      @response.should_receive(:body).and_return("some content")
      @test_target.send(:https_post, "host", "path", {})
    end

    it "should raise an exception with response code != 200" do
      @test_target.should_receive(:ssl_ca_file).and_return(nil)
      @connection.should_receive(:verify_mode=).with(OpenSSL::SSL::VERIFY_NONE)
      @connection.should_receive(:request_get).and_return(@response)
      @response.should_receive(:code).and_return("500")
      @response.should_receive(:body).and_return("some error message")
      expect { @test_target.send(:https_get, "host", "path", {}) }.to raise_error
    end
  end
end


================================================
FILE: spec/omnicontacts/importer/facebook_spec.rb
================================================
require "spec_helper"
require "omnicontacts/importer/facebook"

describe OmniContacts::Importer::Facebook do

  let(:facebook) { OmniContacts::Importer::Facebook.new({}, "client_id", "client_secret") }

  let(:self_response) {
    '{
        "first_name":"Chris",
        "last_name":"Johnson",
        "name":"Chris Johnson",
        "id":"543216789",
        "gender":"male",
        "birthday":"06/21/1982",
        "significant_other":{"id": "243435322"},
        "relationship_status": "Married",
        "picture":{"data":{"url":"http://profile.ak.fbcdn.net/hprofile-ak-snc6/186364_543216789_2089044200_q.jpg","is_silhouette":false}},
        "email": "chrisjohnson@gmail.com"
    }'
  }

  let(:spouse_response) {
    '{
        "first_name":"Mary",
        "last_name":"Johnson",
        "name":"Mary Johnson",
        "id":"243435322",
        "gender":"female",
        "birthday":"01/21",
        "picture":{"data":{"url":"http://profile.ak.fbcdn.net/hprofile-ak-snc6/186364_243435322_2089044200_q.jpg","is_silhouette":false}}
    }'
  }

  let(:contacts_as_json) {
    '{"data":[
        {
          "first_name":"John",
          "last_name":"Smith",
          "name":"John Smith",
          "id":"608061886",
          "gender":"male",
          "birthday":"06/21",
          "relationship":"cousin",
          "picture":{"data":{"url":"http://profile.ak.fbcdn.net/hprofile-ak-snc6/186364_608061886_2089044200_q.jpg","is_silhouette":false}}
        }
      ]
    }' }

  describe "fetch_contacts_using_access_token" do
    let(:token) { "token" }
    let(:token_type) { "token_type" }

    before(:each) do
      facebook.instance_variable_set(:@env, {})
    end

    it "should request the contacts by providing the token in the url" do
      facebook.should_receive(:https_get) do |host, self_path, params, headers|
        params[:access_token].should eq(token)
        params[:fields].should eq('first_name,last_name,name,id,gender,birthday,picture,relationship_status,significant_other,email')
        self_response
      end
      facebook.should_receive(:https_get) do |host, spouse_path, params, headers|
        params[:access_token].should eq(token)
        params[:fields].should eq('first_name,last_name,name,id,gender,birthday,picture')
        spouse_response
      end
      facebook.should_receive(:https_get) do |host, path, params, headers|
        params[:access_token].should eq(token)
        params[:fields].should eq('first_name,last_name,name,id,gender,birthday,picture,relationship')
        contacts_as_json
      end.exactly(1).times
      facebook.should_receive(:https_get) do |host, path, params, headers|
        params[:access_token].should eq(token)
        params[:fields].should eq('first_name,last_name,name,id,gender,birthday,picture')
        contacts_as_json
      end.exactly(1).times

      facebook.fetch_contacts_using_access_token token, token_type
    end

    it "should correctly parse id, name,email,gender, birthday, profile picture and relation" do
      1.times { facebook.should_receive(:https_get).and_return(self_response) }
      1.times { facebook.should_receive(:https_get) }
      2.times { facebook.should_receive(:https_get).and_return(contacts_as_json) }
      result = facebook.fetch_contacts_using_access_token token, token_type
      result.size.should be(1)
      result.first[:id].should eq('608061886')
      result.first[:first_name].should eq('John')
      result.first[:last_name].should eq('Smith')
      result.first[:name].should eq('John Smith')
      result.first[:email].should be_nil
      result.first[:gender].should eq('male')
      result.first[:birthday].should eq({:day=>21, :month=>06, :year=>nil})
      result.first[:profile_picture].should eq('https://graph.facebook.com/608061886/picture')
      result.first[:relation].should eq('cousin')
    end

    it "should correctly parse and set logged in user information" do
      1.times { facebook.should_receive(:https_get).and_return(self_response) }
      1.times { facebook.should_receive(:https_get) }
      2.times { facebook.should_receive(:https_get).and_return(contacts_as_json) }

      facebook.fetch_contacts_using_access_token token, token_type

      user = facebook.instance_variable_get(:@env)["omnicontacts.user"]
      user.should_not be_nil
      user[:id].should eq("543216789")
      user[:first_name].should eq("Chris")
      user[:last_name].should eq("Johnson")
      user[:name].should eq("Chris Johnson")
      user[:email].should eq("chrisjohnson@gmail.com")
      user[:gender].should eq("male")
      user[:birthday].should eq({:day=>21, :month=>06, :year=>1982})
      user[:profile_picture].should eq("https://graph.facebook.com/543216789/picture")
    end
  end

end


================================================
FILE: spec/omnicontacts/importer/gmail_spec.rb
================================================
require "spec_helper"
require "omnicontacts/importer/gmail"

describe OmniContacts::Importer::Gmail do
  let(:gmail) { OmniContacts::Importer::Gmail.new({}, "client_id", "client_secret") }

  let(:gmail_with_scope_args) {
    OmniContacts::Importer::Gmail.new(
      {},
      "client_id",
      "client_secret",
      {
        scope: %w(
          https://www.googleapis.com/auth/contacts.readonly
          https://www.googleapis.com/auth/userinfo#email
          https://www.googleapis.com/auth/userinfo.profile
        ).join(" ")
      }
    )
  }

  let(:self_response) {
    '{
      "id":"16482944006464829443",
      "email":"chrisjohnson@gmail.com",
      "name":"Chris Johnson",
      "given_name":"Chris",
      "family_name":"Johnson",
      "picture":"https://lh3.googleusercontent.com/-b8aFbTBM/AAAAAAI/IWA/vsek/photo.jpg",
      "gender":"male",
      "birthday":"1982-06-21"
    }'
  }

  let(:contacts_as_json) {
    '{"version":"1.0","encoding":"UTF-8",
        "feed":{
          "xmlns":"http://www.w3.org/2005/Atom",
          "xmlns$openSearch":"http://a9.com/-/spec/opensearch/1.1/",
          "xmlns$gContact":"http://schemas.google.com/contact/2008",
          "xmlns$batch":"http://schemas.google.com/gdata/batch",
          "xmlns$gd":"http://schemas.google.com/g/2005",
          "gd$etag":"W/\"C0YHRno7fSt7I2A9WhBSQ0Q.\"",

          "id":{"$t":"logged_in_user@gmail.com"},
          "updated":{"$t":"2013-02-20T20:12:17.405Z"},
          "category":[{
            "scheme":"http://schemas.google.com/g/2005#kind",
            "term":"http://schemas.google.com/contact/2008#contact"
           }],

          "title":{"$t":"Users\'s Contacts"},
          "link":[
            {"rel":"http://schemas.google.com/contacts/2008/rel#photo","type":"image/*",
             "href":"https://www.google.com/m8/feeds/photos/media/logged_in_user%40gmail.com/6b41d030b05abc","gd$etag":"\"VSxuN0cISit7I2A1UVUSdy12KHwgBFkE333.\""},
            {"rel":"alternate","type":"text/html","href":"http://www.google.com/"},
            {"rel":"http://schemas.google.com/g/2005#feed","type":"application/atom+xml","href":"https://www.google.com/m8/feeds/contacts/logged_in_user%40gmail.com/full"},
            {"rel":"http://schemas.google.com/g/2005#post","type":"application/atom+xml","href":"https://www.google.com/m8/feeds/contacts/logged_in_user%40gmail.com/full"},
            {"rel":"http://schemas.google.com/g/2005#batch","type":"application/atom+xml","href":"https://www.google.com/m8/feeds/contacts/logged_in_user%40gmail.com/full/batch"},
            {"rel":"self","type":"application/atom+xml","href":"https://www.google.com/m8/feeds/contacts/logged_in_user%40gmail.com/full?alt\u003djson\u0026max-results\u003d1"},
            {"rel":"next","type":"application/atom+xml","href":"https://www.google.com/m8/feeds/contacts/logged_in_user%40gmail.com/full?alt\u003djson\u0026start-index\u003d2\u0026max-results\u003d1"}
          ],
          "author":[{"name":{"$t":"Edward"},"email":{"$t":"logged_in_user@gmail.com"}}],
          "generator":{"version":"1.0","uri":"http://www.google.com/m8/feeds","$t":"Contacts"},
          "openSearch$totalResults":{"$t":"1007"},
          "openSearch$startIndex":{"$t":"1"},
          "openSearch$itemsPerPage":{"$t":"1"},
          "entry":[
            {
            "gd$etag":"\"R3oyfDVSLyt7I2A9WhBTSEULRA0.\"",
            "id":{"$t":"http://www.google.com/m8/feeds/contacts/logged_in_user%40gmail.com/base/1"},
            "updated":{"$t":"2013-02-14T22:36:36.494Z"},
            "app$edited":{"xmlns$app":"http://www.w3.org/2007/app","$t":"2013-02-14T22:36:36.494Z"},
            "category":[{"scheme":"http://schemas.google.com/g/2005#kind","term":"http://schemas.google.com/contact/2008#contact"}],
            "title":{"$t":"Edward Bennet"},
            "link":[
              {"rel":"http://schemas.google.com/contacts/2008/rel#photo","type":"image/*",
               "href":"https://www.google.com/m8/feeds/photos/media/logged_in_user%40gmail.com/6b41d030b05abc", "gd$etag":"\"VSxuN0cISit7I2A1UVUSdy12KHwgBFkE333.\""},
              {"rel":"self","type":"application/atom+xml","href":"https://www.google.com/m8/feeds/contacts/logged_in_user%40gmail.com/full/1"},
              {"rel":"edit","type":"application/atom+xml","href":"https://www.google.com/m8/feeds/contacts/logged_in_user%40gmail.com/full/1"}
            ],
            "gd$name":{
              "gd$fullName":{"$t":"Edward Bennet"},
              "gd$givenName":{"$t":"Edward"},
              "gd$familyName":{"$t":"Bennet"}
            },
            "gd$organization":[{"rel":"http://schemas.google.com/g/2005#other","gd$orgName":{"$t":"Google"},"gd$orgTitle":{"$t":"Master Developer"}}],
            "gContact$birthday":{"when":"1954-07-02"},
            "gContact$relation":{"rel":"father"},
            "gContact$gender":{"value":"male"},
            "gContact$event":[{"rel":"anniversary","gd$when":{"startTime":"1983-04-21"}},{"label":"New Job","gd$when":{"startTime":"2014-12-01"}}],
            "gd$email":[{"rel":"http://schemas.google.com/g/2005#other","address":"bennet@gmail.com","primary":"true"}],
            "gContact$groupMembershipInfo":[{"deleted":"false","href":"http://www.google.com/m8/feeds/groups/logged_in_user%40gmail.com/base/6"}],
            "gd$structuredPostalAddress":[{"rel":"http://schemas.google.com/g/2005#home","gd$formattedAddress":{"$t":"1313 Trashview Court\nApt. 13\nNowheresville, OK 66666"},"gd$street":{"$t":"1313 Trashview Court\nApt. 13"},"gd$postcode":{"$t":"66666"},"gd$country":{"code":"VA","$t":"Valoran"},"gd$city":{"$t":"Nowheresville"},"gd$region":{"$t":"OK"}}],
            "gd$phoneNumber":[{"rel":"http://schemas.google.com/g/2005#mobile","uri":"tel:+34-653-15-76-88","$t":"653157688"}]
          },
          {
            "gd$etag":"\"R3oyfDVSLyt7I2A9WhBTSEULRA0.\"",
            "id":{"$t":"http://www.google.com/m8/feeds/contacts/logged_in_user%40gmail.com/base/1"},
            "updated":{"$t":"2013-02-15T22:36:36.494Z"},
            "app$edited":{"xmlns$app":"http://www.w3.org/2007/app","$t":"2013-02-15T22:36:36.494Z"},
            "category":[{"scheme":"http://schemas.google.com/g/2005#kind","term":"http://schemas.google.com/contact/2008#contact"}],
            "title":{"$t":"Emilia Fox"},
            "link":[
              {"rel":"http://schemas.google.com/contacts/2008/rel#photo","type":"image/*","href":"https://www.google.com/m8/feeds/photos/media/logged_in_user%40gmail.com/1"},
              {"rel":"self","type":"application/atom+xml","href":"https://www.google.com/m8/feeds/contacts/logged_in_user%40gmail.com/full/1"},
              {"rel":"edit","type":"application/atom+xml","href":"https://www.google.com/m8/feeds/contacts/logged_in_user%40gmail.com/full/1"}
            ],
            "gd$name":{
              "gd$fullName":{"$t":"Emilia Fox"},
              "gd$givenName":{"$t":"Emilia"},
              "gd$familyName":{"$t":"Fox"}
            },
            "gContact$birthday":{"when":"1974-02-10"},
            "gContact$relation":[{"rel":"spouse"}],
            "gContact$gender":{"value":"female"},
            "gd$email":[{"rel":"http://schemas.google.com/g/2005#other","address":"emilia.fox@gmail.com","primary":"true"}],
            "gContact$groupMembershipInfo":[{"deleted":"false","href":"http://www.google.com/m8/feeds/groups/logged_in_user%40gmail.com/base/6"}]
          }]
        }
      }'
  }

  describe "fetch_contacts_using_access_token" do
    let(:token) { "token" }
    let(:token_type) { "token_type" }

    before(:each) do
      gmail.instance_variable_set(:@env, {})
      gmail_with_scope_args.instance_variable_set(:@env, {})
    end

    it "should request the contacts by specifying version and code in the http headers" do
      gmail.should_receive(:https_get) do |host, path, params, headers|
        headers["GData-Version"].should eq("3.0")
        headers["Authorization"].should eq("#{token_type} #{token}")
        self_response
      end
      gmail.should_receive(:https_get) do |host, path, params, headers|
        headers["GData-Version"].should eq("3.0")
        headers["Authorization"].should eq("#{token_type} #{token}")
        contacts_as_json
      end
      gmail.fetch_contacts_using_access_token token, token_type

      gmail.scope.should eq "https://www.googleapis.com/auth/contacts.readonly https://www.googleapis.com/auth/userinfo#email https://www.googleapis.com/auth/userinfo.profile"
      gmail_with_scope_args.scope.should eq "https://www.googleapis.com/auth/contacts.readonly https://www.googleapis.com/auth/userinfo#email https://www.googleapis.com/auth/userinfo.profile"
    end

    it "should correctly parse id, name, email, gender, birthday, profile picture and relation for 1st contact" do
      gmail.should_receive(:https_get)
      gmail.should_receive(:https_get).and_return(contacts_as_json)
      result = gmail.fetch_contacts_using_access_token token, token_type

      result.size.should be(2)
      result.first[:id].should eq('http://www.google.com/m8/feeds/contacts/logged_in_user%40gmail.com/base/1')
      result.first[:first_name].should eq('Edward')
      result.first[:last_name].should eq('Bennet')
      result.first[:name].should eq("Edward Bennet")
      result.first[:email].should eq("bennet@gmail.com")
      result.first[:gender].should eq("male")
      result.first[:birthday].should eq({ :day => 02, :month => 07, :year => 1954 })
      result.first[:relation].should eq('father')
      result.first[:profile_picture].should eq("https://www.google.com/m8/feeds/photos/media/logged_in_user%40gmail.com/6b41d030b05abc?&access_token=token")
      result.first[:dates][0][:name].should eq("anniversary")
    end

    it "should correctly parse id, name, email, gender, birthday, profile picture, snailmail address, phone and relation for 2nd contact" do
      gmail.should_receive(:https_get)
      gmail.should_receive(:https_get).and_return(contacts_as_json)
      result = gmail.fetch_contacts_using_access_token token, token_type
      result.size.should be(2)
      result.last[:id].should eq('http://www.google.com/m8/feeds/contacts/logged_in_user%40gmail.com/base/1')
      result.last[:first_name].should eq('Emilia')
      result.last[:last_name].should eq('Fox')
      result.last[:name].should eq("Emilia Fox")
      result.last[:email].should eq("emilia.fox@gmail.com")
      result.last[:gender].should eq("female")
      result.last[:birthday].should eq({ :day => 10, :month => 02, :year => 1974 })
      result.last[:profile_picture].should be_nil
      result.last[:relation].should eq('spouse')
      result.first[:address_1].should eq('1313 Trashview Court')
      result.first[:address_2].should eq('Apt. 13')
      result.first[:city].should eq('Nowheresville')
      result.first[:region].should eq('OK')
      result.first[:country].should eq('VA')
      result.first[:postcode].should eq('66666')
      result.first[:phone_number].should eq('653157688')
    end

    it "should correctly parse and set logged in user information" do
      gmail.should_receive(:https_get).and_return(self_response)
      gmail.should_receive(:https_get).and_return(contacts_as_json)

      gmail.fetch_contacts_using_access_token token, token_type

      user = gmail.instance_variable_get(:@env)["omnicontacts.user"]
      user.should_not be_nil
      user[:id].should eq("16482944006464829443")
      user[:first_name].should eq("Chris")
      user[:last_name].should eq("Johnson")
      user[:name].should eq("Chris Johnson")
      user[:email].should eq("chrisjohnson@gmail.com")
      user[:gender].should eq("male")
      user[:birthday].should eq({ :day => 21, :month => 06, :year => 1982 })
      user[:profile_picture].should eq("https://lh3.googleusercontent.com/-b8aFbTBM/AAAAAAI/IWA/vsek/photo.jpg")
    end

    context "when address_1 is nil" do
      let(:contacts_as_json) {
        '{"version":"1.0","encoding":"UTF-8",
        "feed":{
          "xmlns":"http://www.w3.org/2005/Atom",
          "xmlns$openSearch":"http://a9.com/-/spec/opensearch/1.1/",
          "xmlns$gContact":"http://schemas.google.com/contact/2008",
          "xmlns$batch":"http://schemas.google.com/gdata/batch",
          "xmlns$gd":"http://schemas.google.com/g/2005",
          "gd$etag":"W/\"C0YHRno7fSt7I2A9WhBSQ0Q.\"",

          "id":{"$t":"logged_in_user@gmail.com"},
          "updated":{"$t":"2013-02-20T20:12:17.405Z"},
          "category":[{
            "scheme":"http://schemas.google.com/g/2005#kind",
            "term":"http://schemas.google.com/contact/2008#contact"
           }],

          "title":{"$t":"Users\'s Contacts"},
          "link":[
            {"rel":"http://schemas.google.com/contacts/2008/rel#photo","type":"image/*",
             "href":"https://www.google.com/m8/feeds/photos/media/logged_in_user%40gmail.com/6b41d030b05abc","gd$etag":"\"VSxuN0cISit7I2A1UVUSdy12KHwgBFkE333.\""},
            {"rel":"alternate","type":"text/html","href":"http://www.google.com/"},
            {"rel":"http://schemas.google.com/g/2005#feed","type":"application/atom+xml","href":"https://www.google.com/m8/feeds/contacts/logged_in_user%40gmail.com/full"},
            {"rel":"http://schemas.google.com/g/2005#post","type":"application/atom+xml","href":"https://www.google.com/m8/feeds/contacts/logged_in_user%40gmail.com/full"},
            {"rel":"http://schemas.google.com/g/2005#batch","type":"application/atom+xml","href":"https://www.google.com/m8/feeds/contacts/logged_in_user%40gmail.com/full/batch"},
            {"rel":"self","type":"application/atom+xml","href":"https://www.google.com/m8/feeds/contacts/logged_in_user%40gmail.com/full?alt\u003djson\u0026max-results\u003d1"},
            {"rel":"next","type":"application/atom+xml","href":"https://www.google.com/m8/feeds/contacts/logged_in_user%40gmail.com/full?alt\u003djson\u0026start-index\u003d2\u0026max-results\u003d1"}
          ],
          "author":[{"name":{"$t":"Edward"},"email":{"$t":"logged_in_user@gmail.com"}}],
          "generator":{"version":"1.0","uri":"http://www.google.com/m8/feeds","$t":"Contacts"},
          "openSearch$totalResults":{"$t":"1007"},
          "openSearch$startIndex":{"$t":"1"},
          "openSearch$itemsPerPage":{"$t":"1"},
          "entry":[
            {
            "gd$etag":"\"R3oyfDVSLyt7I2A9WhBTSEULRA0.\"",
            "id":{"$t":"http://www.google.com/m8/feeds/contacts/logged_in_user%40gmail.com/base/1"},
            "updated":{"$t":"2013-02-14T22:36:36.494Z"},
            "app$edited":{"xmlns$app":"http://www.w3.org/2007/app","$t":"2013-02-14T22:36:36.494Z"},
            "category":[{"scheme":"http://schemas.google.com/g/2005#kind","term":"http://schemas.google.com/contact/2008#contact"}],
            "title":{"$t":"Edward Bennet"},
            "link":[
              {"rel":"http://schemas.google.com/contacts/2008/rel#photo","type":"image/*",
               "href":"https://www.google.com/m8/feeds/photos/media/logged_in_user%40gmail.com/6b41d030b05abc", "gd$etag":"\"VSxuN0cISit7I2A1UVUSdy12KHwgBFkE333.\""},
              {"rel":"self","type":"application/atom+xml","href":"https://www.google.com/m8/feeds/contacts/logged_in_user%40gmail.com/full/1"},
              {"rel":"edit","type":"application/atom+xml","href":"https://www.google.com/m8/feeds/contacts/logged_in_user%40gmail.com/full/1"}
            ],
            "gd$name":{
              "gd$fullName":{"$t":"Edward Bennet"},
              "gd$givenName":{"$t":"Edward"},
              "gd$familyName":{"$t":"Bennet"}
            },
            "gd$organization":[{"rel":"http://schemas.google.com/g/2005#other","gd$orgName":{"$t":"Google"},"gd$orgTitle":{"$t":"Master Developer"}}],
            "gContact$birthday":{"when":"1954-07-02"},
            "gContact$relation":{"rel":"father"},
            "gContact$gender":{"value":"male"},
            "gContact$event":[{"rel":"anniversary","gd$when":{"startTime":"1983-04-21"}},{"label":"New Job","gd$when":{"startTime":"2014-12-01"}}],
            "gd$email":[{"rel":"http://schemas.google.com/g/2005#other","address":"bennet@gmail.com","primary":"true"}],
            "gContact$groupMembershipInfo":[{"deleted":"false","href":"http://www.google.com/m8/feeds/groups/logged_in_user%40gmail.com/base/6"}],
            "gd$structuredPostalAddress":[{"rel":"http://schemas.google.com/g/2005#home","gd$formattedAddress":{},"gd$street":{},"gd$postcode":{"$t":"66666"},"gd$country":{"code":"VA","$t":"Valoran"},"gd$city":{"$t":"Nowheresville"},"gd$region":{"$t":"OK"}}],
            "gd$phoneNumber":[{"rel":"http://schemas.google.com/g/2005#mobile","uri":"tel:+34-653-15-76-88","$t":"653157688"}]
          },
          {
            "gd$etag":"\"R3oyfDVSLyt7I2A9WhBTSEULRA0.\"",
            "id":{"$t":"http://www.google.com/m8/feeds/contacts/logged_in_user%40gmail.com/base/1"},
            "updated":{"$t":"2013-02-15T22:36:36.494Z"},
            "app$edited":{"xmlns$app":"http://www.w3.org/2007/app","$t":"2013-02-15T22:36:36.494Z"},
            "category":[{"scheme":"http://schemas.google.com/g/2005#kind","term":"http://schemas.google.com/contact/2008#contact"}],
            "title":{"$t":"Emilia Fox"},
            "link":[
              {"rel":"http://schemas.google.com/contacts/2008/rel#photo","type":"image/*","href":"https://www.google.com/m8/feeds/photos/media/logged_in_user%40gmail.com/1"},
              {"rel":"self","type":"application/atom+xml","href":"https://www.google.com/m8/feeds/contacts/logged_in_user%40gmail.com/full/1"},
              {"rel":"edit","type":"application/atom+xml","href":"https://www.google.com/m8/feeds/contacts/logged_in_user%40gmail.com/full/1"}
            ],
            "gd$name":{
              "gd$fullName":{"$t":"Emilia Fox"},
              "gd$givenName":{"$t":"Emilia"},
              "gd$familyName":{"$t":"Fox"}
            },
            "gContact$birthday":{"when":"1974-02-10"},
            "gContact$relation":[{"rel":"spouse"}],
            "gContact$gender":{"value":"female"},
            "gd$email":[{"rel":"http://schemas.google.com/g/2005#other","address":"emilia.fox@gmail.com","primary":"true"}],
            "gContact$groupMembershipInfo":[{"deleted":"false","href":"http://www.google.com/m8/feeds/groups/logged_in_user%40gmail.com/base/6"}]
          }]
        }
      }'
      }

      it "should correctly parse id, name, email, gender, birthday, profile picture, snailmail address, phone and relation for 2nd contact" do
        gmail.should_receive(:https_get)
        gmail.should_receive(:https_get).and_return(contacts_as_json)
        result = gmail.fetch_contacts_using_access_token token, token_type
        result.size.should be(2)
        result.last[:id].should eq('http://www.google.com/m8/feeds/contacts/logged_in_user%40gmail.com/base/1')
        result.last[:first_name].should eq('Emilia')
        result.last[:last_name].should eq('Fox')
        result.last[:name].should eq("Emilia Fox")
        result.last[:email].should eq("emilia.fox@gmail.com")
        result.last[:gender].should eq("female")
        result.last[:birthday].should eq({ :day => 10, :month => 02, :year => 1974 })
        result.last[:profile_picture].should be_nil
        result.last[:relation].should eq('spouse')
        result.first[:address_1].should eq(nil)
        result.first[:address_2].should eq(nil)
        result.first[:city].should eq('Nowheresville')
        result.first[:region].should eq('OK')
        result.first[:country].should eq('VA')
        result.first[:postcode].should eq('66666')
        result.first[:phone_number].should eq('653157688')
      end
    end
  end
end


================================================
FILE: spec/omnicontacts/importer/hotmail_spec.rb
================================================
require "spec_helper"
require "omnicontacts/importer/hotmail"

describe OmniContacts::Importer::Hotmail do

  let(:permissions) { "perm1, perm2" }
  let(:hotmail) { OmniContacts::Importer::Hotmail.new({}, "client_id", "client_secret", {:permissions => permissions}) }

  let(:self_response) {
    '{
      "id": "4502de12390223d0",
      "name": "Chris Johnson",
      "first_name": "Chris",
      "last_name": "Johnson",
      "birth_day": 21,
      "birth_month": 6,
      "birth_year": 1982,
      "gender": null,
      "emails": {"preferred":"chrisjohnson@gmail.com", "account":"chrisjohn@gmail.com", "personal":null, "business":null}
    }'
  }

  let(:contacts_as_json) {
    '{
   "data": [
       {
         "id": "contact.7fac34bb000000000000000000000000",
         "first_name": "John",
         "last_name": "Smith",
         "name": "John Smith",
         "gender": null,
         "user_id": "123456",
         "is_friend": false,
         "is_favorite": false,
         "birth_day": 5,
         "birth_month": 6,
         "birth_year":1952,
         "email_hashes":["1234567890"]
      }
    ]}'
  }

  describe "fetch_contacts_using_access_token" do

    let(:token) { "token" }
    let(:token_type) { "token_type" }

    before(:each) do
      hotmail.instance_variable_set(:@env, {"HTTP_HOST" => "http://example.com"})
    end

    it "should request the contacts by providing the token in the url" do
      hotmail.should_receive(:https_get) do |host, path, params, headers|
        params[:access_token].should eq(token)
        self_response
      end

      hotmail.should_receive(:https_get) do |host, path, params, headers|
        params[:access_token].should eq(token)
        contacts_as_json
      end
      hotmail.fetch_contacts_using_access_token token, token_type
    end

    it "should set requested permissions in the authorization url" do
      hotmail.authorization_url.should match(/scope=#{Regexp.quote(CGI.escape(permissions))}/)
    end

    it "should correctly parse id, name, email, gender, birthday, profile picture, relation and email hashes" do
      hotmail.should_receive(:https_get).and_return(self_response)
      hotmail.should_receive(:https_get).and_return(contacts_as_json)
      result = hotmail.fetch_contacts_using_access_token token, token_type

      result.size.should be(1)
      result.first[:id].should eq('123456')
      result.first[:first_name].should eq("John")
      result.first[:last_name].should eq('Smith')
      result.first[:name].should eq("John Smith")
      result.first[:email].should be_nil
      result.first[:gender].should be_nil
      result.first[:birthday].should eq({:day=>5, :month=>6, :year=>1952})
      result.first[:profile_picture].should eq('https://apis.live.net/v5.0/123456/picture')
      result.first[:relation].should be_nil
      result.first[:email_hashes].should eq(["1234567890"])
    end

    it "should correctly parse and set logged in user information" do
      hotmail.should_receive(:https_get).and_return(self_response)
      hotmail.should_receive(:https_get).and_return(contacts_as_json)

      hotmail.fetch_contacts_using_access_token token, token_type

      user = hotmail.instance_variable_get(:@env)["omnicontacts.user"]
      user.should_not be_nil
      user[:id].should eq('4502de12390223d0')
      user[:first_name].should eq('Chris')
      user[:last_name].should eq('Johnson')
      user[:name].should eq('Chris Johnson')
      user[:gender].should be_nil
      user[:birthday].should eq({:day=>21, :month=>06, :year=>1982})
      user[:email].should eq('chrisjohn@gmail.com')
      user[:profile_picture].should eq('https://apis.live.net/v5.0/4502de12390223d0/picture')
    end
  end

end


================================================
FILE: spec/omnicontacts/importer/linkedin_spec.rb
================================================
require "spec_helper"
require "omnicontacts/importer/linkedin"

describe OmniContacts::Importer::Linkedin do

  let(:linkedin) { OmniContacts::Importer::Linkedin.new({}, "client_id", "client_secret", state: "ipsaeumeaque") }

  let(:contacts_as_json) do
    "{
      \n  \"_total\":  2,
      \n  \"values\":  [
        \n    {
          \n      \"firstName\":  \"Adolf\",
          \n      \"id\":  \"k71S5q6MKe\",
          \n      \"lastName\":  \"Witting\",
          \n      \"pictureUrl\":  \"https://media.licdn.com/mpr/mprx/0_mLnj-7szw130pFRLB8Op7-p1Sxoyv53U3B47Scp1Sxoyv53U3B47Scp1Sxoyv53U3B47Sc\"\n
        },
        \n    {
          \n      \"firstName\":  \"Emmet\",
          \n      \"id\":  \"ms5r3lI3J2\",
          \n      \"lastName\":  \"Little\",
          \n      \"pictureUrl\":  \"https://media.licdn.com/mpr/mprx/0_iH9m158zCdISt1X6iH9m158zCdISt1X6iH9m158zCdISt1X6iH9m158zCdISt1X6iH9m158zCdISt1X6\"\n
        }
      ]\n
    }"
  end

  describe "fetch_contacts_using_access_token" do
    let(:token) { "token" }
    let(:token_type) { nil }

    before(:each) do
      linkedin.instance_variable_set(:@env, {})
    end

    it "should request the contacts by specifying code in the http headers" do
      linkedin.should_receive(:https_get) do |host, path, params, headers|
        headers["Authorization"].should eq("Bearer #{token}")
        contacts_as_json
      end
      linkedin.fetch_contacts_using_access_token token, token_type
    end

    it "should correctly parse id, name, and profile picture for 1st contact" do
      linkedin.should_receive(:https_get).and_return(contacts_as_json)
      result = linkedin.fetch_contacts_using_access_token token, token_type

      result.size.should be(2)
      result.first[:id].should eq('k71S5q6MKe')
      result.first[:first_name].should eq('Adolf')
      result.first[:last_name].should eq('Witting')
      result.first[:name].should eq("Adolf Witting")
      result.first[:profile_picture].should eq("https://media.licdn.com/mpr/mprx/0_mLnj-7szw130pFRLB8Op7-p1Sxoyv53U3B47Scp1Sxoyv53U3B47Scp1Sxoyv53U3B47Sc")
    end

    it "should correctly parse id, name, and profile picture for 2nd contact" do
      linkedin.should_receive(:https_get).and_return(contacts_as_json)
      result = linkedin.fetch_contacts_using_access_token token, token_type
      result.size.should be(2)
      result.last[:id].should eq('ms5r3lI3J2')
      result.last[:first_name].should eq('Emmet')
      result.last[:last_name].should eq('Little')
      result.last[:name].should eq("Emmet Little")
      result.last[:profile_picture].should eq("https://media.licdn.com/mpr/mprx/0_iH9m158zCdISt1X6iH9m158zCdISt1X6iH9m158zCdISt1X6iH9m158zCdISt1X6iH9m158zCdISt1X6")
    end
  end
end

================================================
FILE: spec/omnicontacts/importer/outlook_spec.rb
================================================
require "spec_helper"
require "omnicontacts/importer/outlook"

describe OmniContacts::Importer::Outlook do

  let(:permissions) { "Contacts.Read" }
  let(:outlook) { OmniContacts::Importer::Outlook.new({}, "app_id", "app_secret", {:permissions => permissions}) }

  let(:self_response) {
    '{
      "@odata.context": "https://outlook.office.com/api/v2.0/$metadata#Me",
      "@odata.id": "https://outlook.office.com/api/v2.0/Users(\'00034001-df52-d3d5-0000-000000000000@84df9e7f-e9f6-40af-b435-aaaaaaaaaaaa\')",
      "Id": "00034001-df52-d3d5-0000-000000000000@84df9e7f-e9f6-40af-b435-aaaaaaaaaaaa",
      "EmailAddress": "test.user@outlook.com",
      "DisplayName": "Test User",
      "Alias": "puid-00034001DF52D3D5",
      "MailboxGuid": "00034001-df52-d3d5-0000-000000000000"
    }'
  }

  let(:contacts_as_json) {
    '{
      "@odata.context": "https://outlook.office.com/api/v2.0/$metadata#Me/Contacts",
      "value": [{
        "@odata.id": "https://outlook.office.com/api/v2.0/Users(\'00034001-df52-d3d5-0000-000000000000@84df9e7f-e9f6-40af-b435-aaaaaaaaaaaa\')/Contacts(\'AQMkADAwATM0MDAAMS1kZjUyLWQzZDUtMDACLTAwCgBGAAADQ1hAWLJpwk6DZYyOhnclvgcAxCL3G7jnpkiRUVmiNrhjJgAAAgEOAAAAxCL3G7jnpkiRUVmiNrhjJgAAAixsAAAA\')",
        "@odata.etag": "W/\"EQAAABYAAADEIvcbuOemSJFRWaI2uGMmAAAAlo4t\"",
        "Id": "AQMkADAwATM0MDAAMS1kZjUyLWQzZDUtMDACLTAwCgBGAAADQ1hAWLJpwk6DZYyOhnclvgcAxCL3G7jnpkiRUVmiNrhjJgAAAgEOAAAAxCL3G7jnpkiRUVmiNrhjJgAAAixsAAAA",
        "CreatedDateTime": "2016-04-13T21:25:24Z",
        "LastModifiedDateTime": "2016-04-14T19:36:55Z",
        "ChangeKey": "EQAAABYAAADEIvcbuOemSJFRWaI2uGMmAAAAlo4t",
        "Categories": [],
        "ParentFolderId": "AQMkADAwATM0MDAAMS1kZjUyLWQzZDUtMDACLTAwCgAuAAADQ1hAWLJpwk6DZYyOhnclvgEAxCL3G7jnpkiRUVmiNrhjJgAAAgEOAAAA",
        "Birthday": "1604-08-14T00:00:00Z",
        "FileAs": "Contact, First",
        "DisplayName": "First Contact",
        "GivenName": "First",
        "Initials": null,
        "MiddleName": null,
        "NickName": null,
        "Surname": "Contact",
        "Title": null,
        "YomiGivenName": null,
        "YomiSurname": null,
        "YomiCompanyName": null,
        "Generation": null,
        "EmailAddresses": [{
          "Name": "contact.first@email.com",
          "Address": "contact.first@email.com"
        }, {
          "Name": "contact.second@email.com",
          "Address": "contact.second@email.com"
        }],
        "ImAddresses": [],
        "JobTitle": null,
        "CompanyName": null,
        "Department": null,
        "OfficeLocation": null,
        "Profession": null,
        "BusinessHomePage": null,
        "AssistantName": null,
        "Manager": null,
        "HomePhones": [],
        "MobilePhone1": null,
        "BusinessPhones": [],
        "HomeAddress": {
          "Street": "address1",
          "City": "city",
          "State": "state",
          "CountryOrRegion": "US",
          "PostalCode": "89111"
        },
        "BusinessAddress": {},
        "OtherAddress": {},
        "SpouseName": null,
        "PersonalNotes": null,
        "Children": []
      }]
    }'
  }

  describe "fetch_contacts_using_access_token" do

    let(:access_token) { "access_token" }
    let(:token_type) { "token_type" }

    before(:each) do
      outlook.instance_variable_set(:@env, {"HTTP_HOST" => "http://example.com"})
    end

    it "should request the contacts by providing the authorization header with token_type and access_token" do
      outlook.should_receive(:https_get) do |host, path, params, headers|
        params.should eq({})
        headers["Authorization"].should eq("token_type access_token")
        self_response
      end

      outlook.should_receive(:https_get) do |host, path, params, headers|
        params.should eq({})
        headers["Authorization"].should eq("token_type access_token")
        contacts_as_json
      end
      outlook.fetch_contacts_using_access_token access_token, token_type
    end

    it "should set requested permissions in the authorization url" do
      outlook.authorization_url.should match(/scope=#{Regexp.quote(CGI.escape(permissions))}/)
    end

    it "should correctly parse id, name and email" do
      outlook.should_receive(:https_get).and_return(self_response)
      outlook.should_receive(:https_get).and_return(contacts_as_json)
      result = outlook.fetch_contacts_using_access_token access_token, token_type

      result.size.should be(1)
      result.first[:id].should eq('AQMkADAwATM0MDAAMS1kZjUyLWQzZDUtMDACLTAwCgBGAAADQ1hAWLJpwk6DZYyOhnclvgcAxCL3G7jnpkiRUVmiNrhjJgAAAgEOAAAAxCL3G7jnpkiRUVmiNrhjJgAAAixsAAAA')
      result.first[:first_name].should eq("First")
      result.first[:last_name].should eq("Contact")
      result.first[:name].should eq("First Contact")
      result.first[:email].should eq("contact.first@email.com")
      result.first[:birthday].should eq({ :day => 14, :month => 8, :year => nil })
      result.first[:address_1].should eq("address1")
      result.first[:address_2].should be_nil
      result.first[:city].should eq("city")
      result.first[:region].should eq("state")
      result.first[:postcode].should eq("89111")
      result.first[:country].should eq("US")
      result.first[:gender].should be_nil
      result.first[:profile_picture].should be_nil
      result.first[:relation].should be_nil
    end

    it "should correctly parse and set logged in user information" do
      outlook.should_receive(:https_get).and_return(self_response)
      outlook.should_receive(:https_get).and_return(contacts_as_json)

      outlook.fetch_contacts_using_access_token access_token, token_type

      user = outlook.instance_variable_get(:@env)["omnicontacts.user"]
      user.should_not be_nil
      user[:id].should eq('00034001-df52-d3d5-0000-000000000000@84df9e7f-e9f6-40af-b435-aaaaaaaaaaaa')
      user[:first_name].should eq("Test")
      user[:last_name].should eq("User")
      user[:name].should eq("Test User")
      user[:email].should eq("test.user@outlook.com")
      user[:gender].should be_nil
      user[:birthday].should be_nil
    end
  end

end


================================================
FILE: spec/omnicontacts/importer/yahoo_spec.rb
================================================
require "spec_helper"
require "omnicontacts/importer/yahoo"

describe OmniContacts::Importer::Yahoo do

  describe "fetch_contacts_from_token_and_verifier" do
    let(:self_response) {
      '{"profile":{
                  "guid":"PCLASP523T3E2R5TFMHDW9KWQQ",
                  "birthdate": "06/21",
                  "emails":[{"handle":"chrisjohnson@gmail.com", "id":10, "primary":true, "type":"HOME"}, {"handle":"xyz@xyz.com", "id":11, "type":"HOME"}],
                  "familyName": "Johnson",
                  "gender":"M",
                  "givenName":"Chris",
                  "image":{"imageUrl":"https://avatars.zenfs.com/users/23T3E2R5TFMHDW-AFE-I7lUpIsGQ==.large.png"}
                }
      }'
    }

    let(:contacts_as_json) {
      '{
        "contacts": {
          "start":1,
          "count":1,
          "contact":[
            {
              "id":10,
              "fields":[
                {"id":819, "type":"email", "value":"johnny@yahoo.com"},
                {"id":806,"type":"name","value":{"givenName":"John","middleName":"","familyName":"Smith"},"editedBy":"OWNER","categories":[]},
                {"id":33555343,"type":"guid","value":"7ET6MYV2UQ6VR6CBSNMCLFJIVI"},
                {"id":946,"type":"birthday","value":{"day":"22","month":"2","year":"1952"},"editedBy":"OWNER","categories":[]},
                {"id":21, "type":"address", "value":{"street":"1313 Trashview Court\nApt. 13", "city":"Nowheresville", "stateOrProvince":"OK", "postalCode":"66666", "country":"", "countryCode":""}, "editedBy":"OWNER", "flags":["HOME"], "categories":[]}
              ]
            }
          ]
        }
      }' }

    let(:yahoo) { OmniContacts::Importer::Yahoo.new({}, "consumer_key", "consumer_secret") }

    before(:each) do
      yahoo.instance_variable_set(:@env, {})
    end

    it "should request the contacts by specifying all required parameters" do
      yahoo.should_receive(:fetch_access_token).and_return(["access_token", "access_token_secret", "guid"])

      yahoo.should_receive(:https_get) do |host, path, params|
        params[:format].should eq("json")
        params[:oauth_consumer_key].should eq("consumer_key")
        params[:oauth_nonce].should_not be_nil
        params[:oauth_signature_method].should eq("HMAC-SHA1")
        params[:oauth_timestamp].should_not be_nil
        params[:oauth_token].should eq("access_token")
        params[:oauth_version].should eq("1.0")
        self_response
      end

      yahoo.should_receive(:https_get) do |host, path, params|
        params[:format].should eq("json")
        params[:oauth_consumer_key].should eq("consumer_key")
        params[:oauth_nonce].should_not be_nil
        params[:oauth_signature_method].should eq("HMAC-SHA1")
        params[:oauth_timestamp].should_not be_nil
        params[:oauth_token].should eq("access_token")
        params[:oauth_version].should eq("1.0")
        contacts_as_json
      end
      yahoo.fetch_contacts_from_token_and_verifier "auth_token", "auth_token_secret", "oauth_verifier"
    end

    it "should correctly parse id, name,email,gender, birthday, snailmail address, image source and relation for contact and logged in user" do
      yahoo.should_receive(:fetch_access_token).and_return(["access_token", "access_token_secret", "guid"])
      yahoo.should_receive(:https_get).and_return(self_response)
      yahoo.should_receive(:https_get).and_return(contacts_as_json)
      result = yahoo.fetch_contacts_from_token_and_verifier "auth_token", "auth_token_secret", "oauth_verifier"

      result.size.should be(1)
      result.first[:id].should eq('10')
      result.first[:first_name].should eq('John')
      result.first[:last_name].should eq('Smith')
      result.first[:name].should eq("John Smith")
      result.first[:email].should eq("johnny@yahoo.com")
      result.first[:gender].should be_nil
      result.first[:birthday].should eq({:day=>22, :month=>2, :year=>1952})
      result.first[:address_1].should eq('1313 Trashview Court')
      result.first[:address_2].should eq('Apt. 13')
      result.first[:city].should eq('Nowheresville')
      result.first[:region].should eq('OK')
      result.first[:postcode].should eq('66666')
      result.first[:relation].should be_nil
    end

    it "should return an empty list of contacts" do
      empty_contacts_list = '{"contacts": {"start":0, "count":0}}'
      yahoo.should_receive(:fetch_access_token).and_return(["access_token", "access_token_secret", "guid"])
      yahoo.should_receive(:https_get).and_return(self_response)
      yahoo.should_receive(:https_get).and_return(empty_contacts_list)
      result = yahoo.fetch_contacts_from_token_and_verifier "auth_token", "auth_token_secret", "oauth_verifier"

      result.should be_empty
    end

    it "should correctly parse and set logged in user information" do
      yahoo.should_receive(:fetch_access_token).and_return(["access_token", "access_token_secret", "guid"])
      yahoo.should_receive(:https_get).and_return(self_response)
      yahoo.should_receive(:https_get).and_return(contacts_as_json)
      yahoo.fetch_contacts_from_token_and_verifier "auth_token", "auth_token_secret", "oauth_verifier"

      user = yahoo.instance_variable_get(:@env)["omnicontacts.user"]
      user.should_not be_nil
      user[:id].should eq('PCLASP523T3E2R5TFMHDW9KWQQ')
      user[:first_name].should eq('Chris')
      user[:last_name].should eq('Johnson')
      user[:name].should eq('Chris Johnson')
      user[:gender].should eq('male')
      user[:birthday].should eq({:day=>21, :month=>06, :year=>nil})
      user[:email].should eq('chrisjohnson@gmail.com')
      user[:profile_picture].should eq('https://avatars.zenfs.com/users/23T3E2R5TFMHDW-AFE-I7lUpIsGQ==.large.png')
    end

  end
end


================================================
FILE: spec/omnicontacts/integration_test_spec.rb
================================================
require "spec_helper"
require "omnicontacts/integration_test"

describe IntegrationTest do

  context "mock_initial_request" do
    it "should redirect to the provider's redirect_path" do
      provider = mock
      redirect_path = "/redirect_path"
      provider.stub(:redirect_path => redirect_path)
      IntegrationTest.instance.mock_authorization_from_user(provider)[1]["location"].should eq(redirect_path)
    end
  end

  context "mock_callback" do

    before(:each) {
      @env = {}
      @provider = self.mock
      @provider.stub(:class_name => "test")
      IntegrationTest.instance.clear_mocks
    }

    it "should return an empty contacts list" do
      IntegrationTest.instance.mock_fetch_contacts(@provider).should be_empty
    end

    it "should return a configured list of contacts " do
      contacts = [:name => 'John Doe', :email => 'john@doe.com']
      IntegrationTest.instance.mock('test', contacts)
      result = IntegrationTest.instance.mock_fetch_contacts(@provider)
      result.size.should be(1)
      result.first[:email].should eq(contacts.first[:email])
      result.first[:name].should eq(contacts.first[:name])
    end

    it "should return a single element list of contacts " do
      contact = {:name => 'John Doe', :email => 'john@doe.com'}
      IntegrationTest.instance.mock('test', contact)
      result = IntegrationTest.instance.mock_fetch_contacts(@provider)
      result.size.should be(1)
      result.first[:email].should eq(contact[:email])
      result.first[:name].should eq(contact[:name])
    end

    it "should return a user" do
      contact = {:name => 'John Doe', :email => 'john@doe.com'}
      user = {:name => 'Mary Smith', :email => 'mary@smith.com'}
      IntegrationTest.instance.mock('test', contact, user)
      result = IntegrationTest.instance.mock_fetch_user(@provider)
      result[:email].should eq(user[:email])
      result[:name].should eq(user[:name])
    end

    it "should throw an exception" do
      IntegrationTest.instance.mock('test', :some_error)
      expect {IntegrationTest.instance.mock_fetch_contacts(@provider)}.to raise_error
    end
  end
end


================================================
FILE: spec/omnicontacts/middleware/base_oauth_spec.rb
================================================
require "spec_helper"
require "omnicontacts"
require "omnicontacts/middleware/base_oauth"

describe OmniContacts::Middleware::BaseOAuth do
  
  before(:all) do 
    class TestProvider < OmniContacts::Middleware::BaseOAuth
      def initialize app, consumer_key, consumer_secret, options = {}
        super app, options
      end
      
      def redirect_path
        "#{ MOUNT_PATH }testprovider/callback"
      end

      def self.mock_session
        @mock_session ||= {}
      end

      def session
        TestProvider.mock_session
      end
    end
    OmniContacts.integration_test.enabled = true
  end

  let(:app) {
    Rack::Builder.new do |b|
      b.use TestProvider, "consumer_id", "consumer_secret"
      b.run lambda { |env| [200, {"Content-Type" => "text/html"}, ["Hello World"]] }
    end.to_app
  }
  
  it "should return a preconfigured list of contacts" do
    OmniContacts.integration_test.mock(:testprovider, :email => "user@example.com")
    get "#{ MOUNT_PATH }testprovider"
    get "#{ MOUNT_PATH }testprovider/callback"
    last_request.env["omnicontacts.contacts"].first[:email].should eq("user@example.com")
  end

  it "should redirect to failure url" do
    OmniContacts.integration_test.mock(:testprovider, "some_error" )
    get "#{ MOUNT_PATH }testprovider"
    get "#{MOUNT_PATH }testprovider/callback"
    last_response.should be_redirect
    last_response.headers["location"].should eq("#{ MOUNT_PATH }failure?error_message=internal_error&importer=testprovider")
  end
  
  it "should pass through state query params to the failure url" do
    OmniContacts.integration_test.mock(:testprovider, "some_error" )
    get "#{MOUNT_PATH }testprovider/callback?state=/parent/resource/id"
    last_response.headers["location"].should eq("#{ MOUNT_PATH }failure?error_message=internal_error&importer=testprovider&state=/parent/resource/id")
  end

  it "should store request params in session" do
    OmniContacts.integration_test.mock(:testprovider, :email => "user@example.com")
    get "#{ MOUNT_PATH }testprovider?foo=bar"
    app.session['omnicontacts.params'].should eq({'foo' => 'bar'})
  end

  it "should pass the params from session to callback environment " do
    OmniContacts.integration_test.mock(:testprovider, :email => "user@example.com")
    app.session.merge!({'omnicontacts.params' => {'foo' => 'bar'}})
    get "#{MOUNT_PATH }testprovider/callback?state=/parent/resource/id"
    last_request.env["omnicontacts.params"].should eq({'foo' => 'bar'})
  end

  it "should pass the params from session on failure" do
    OmniContacts.integration_test.mock(:testprovider, "some_error" )
    get "#{ MOUNT_PATH }testprovider"
    app.session.merge!({'omnicontacts.params' => {'foo' => 'bar'}})
    get "#{MOUNT_PATH }testprovider/callback"
    last_response.should be_redirect
    last_response.headers["location"].should be_include("foo=bar")
  end
  
  after(:all) do 
    OmniContacts.integration_test.enabled = false
    OmniContacts.integration_test.clear_mocks
  end
end


================================================
FILE: spec/omnicontacts/middleware/oauth1_spec.rb
================================================
require "spec_helper"
require "omnicontacts/middleware/oauth1"

describe OmniContacts::Middleware::OAuth1 do

  before(:all) do
    class OAuth1Middleware < OmniContacts::Middleware::OAuth1
      def self.mock_auth_token_resp
        @mock_auth_token_resp ||= Object.new
      end

      def fetch_authorization_token
        OAuth1Middleware.mock_auth_token_resp.body
      end

      def authorization_url auth_token
        "http://www.example.com"
      end

      def fetch_contacts_from_token_and_verifier oauth_token, ouath_token_secret, oauth_verifier
        [{:name => "John Doe", :email => "john@example.com"}]
      end

      def self.mock_session
        @mock_session ||= {}
      end

      def session
        OAuth1Middleware.mock_session
      end
    end
  end

  let(:app) {
    Rack::Builder.new do |b|
      b.use OAuth1Middleware, "consumer_id", "consumer_secret"
      b.run lambda { |env| [200, {"Content-Type" => "text/html"}, ["Hello World"]] }
    end.to_app
  }

  context "visiting the listening path" do
    it "should save the authorization token and redirect to the authorization url" do
      OAuth1Middleware.mock_auth_token_resp.should_receive(:body).and_return(["auth_token", "auth_token_secret"])
      get "#{ MOUNT_PATH }oauth1middleware"
      last_response.should be_redirect
      last_response.headers['location'].should eq("http://www.example.com")
    end

    it "should pass through state query params visiting the listening path" do
      OAuth1Middleware.mock_auth_token_resp.should_receive(:body).and_return(["auth_token", "auth_token_secret"])
      get "#{ MOUNT_PATH }oauth1middleware?state=/parent/resource/id"
      last_response.headers['location'].should eq("http://www.example.com?state=/parent/resource/id")
    end

    it "should redirect to failure url if fetching the request token does not succeed" do
      OAuth1Middleware.mock_auth_token_resp.should_receive(:body).and_raise("Request failed")
      get "contacts/oauth1middleware"
      last_response.should be_redirect
      last_response.headers["location"].should eq("#{ MOUNT_PATH }failure?error_message=internal_error&importer=oauth1middleware")
    end
  end

  context "visiting the callback url after authorization" do
    it "should return the list of contacts" do
      OAuth1Middleware.mock_session.should_receive(:[]).and_return("oauth_token_secret")
      get "#{ MOUNT_PATH }oauth1middleware/callback?oauth_token=token&oauth_verifier=verifier"
      last_response.should be_ok
      last_request.env["omnicontacts.contacts"].size.should be(1)
    end

    it "should redirect to failure url if oauth_token_secret is not found in the session" do
      OAuth1Middleware.mock_session.should_receive(:[]).and_return(nil)
      get "#{ MOUNT_PATH }oauth1middleware/callback?oauth_token=token&oauth_verifier=verifier"
      last_response.should be_redirect
      last_response.headers["location"].should eq("#{ MOUNT_PATH }failure?error_message=not_authorized&importer=oauth1middleware")
    end
  end
end


================================================
FILE: spec/omnicontacts/middleware/oauth2_spec.rb
================================================
require "spec_helper"
require "omnicontacts/middleware/oauth2"

describe OmniContacts::Middleware::OAuth2 do

  before(:all) do
    class OAuth2Middleware < OmniContacts::Middleware::OAuth2
      def authorization_url
        "http://www.example.com"
      end

      def redirect_path
        "/redirect_path"
      end

      def self.mock_session
        @mock_session ||= {}
      end

      def session
        OAuth2Middleware.mock_session
      end

      def fetch_access_token code
        ["access_token", "token_type", "token_refresh"]
      end

      def fetch_contacts_using_access_token token, token_type
        [{:name => "John Doe", :email => "john@example.com"}]
      end
    end
  end

  let(:app) {
    Rack::Builder.new do |b|
      b.use OAuth2Middleware, "client_id", "client_secret"
      b.run lambda { |env| [200, {"Content-Type" => "text/html"}, ["Hello World"]] }
    end.to_app
  }

  context "visiting the listening path" do
    it "should redirect to authorization site when visiting the listening path" do
      get "#{ MOUNT_PATH }oauth2middleware"
      last_response.should be_redirect
      last_response.headers['location'].should eq("http://www.example.com")
    end

    it "should pass through state query params visiting the listening path" do
      get "#{ MOUNT_PATH }oauth2middleware?state=/parent/resource/id"
      last_response.headers['location'].should eq("http://www.example.com?state=/parent/resource/id")
    end
  end

  context "visiting the callback url after authorization" do
    it "should fetch the contacts" do
      get '/redirect_path?code=ABC'
      last_response.should be_ok
      last_request.env["omnicontacts.contacts"].size.should be(1)
    end

    it "should redirect to failure page because user did not allow access to contacts list" do
      get '/redirect_path?error=not_authorized'
      last_response.should be_redirect
      last_response.headers["location"].should eq("#{ MOUNT_PATH }failure?error_message=not_authorized&importer=oauth2middleware")
    end
  end
end


================================================
FILE: spec/omnicontacts/parse_utils_spec.rb
================================================
require "spec_helper"
require "omnicontacts/parse_utils"

include OmniContacts::ParseUtils

describe OmniContacts::ParseUtils do
  describe "normalize_name" do
    it "should remove trailing spaces" do
      result = normalize_name("John ")
      result.should eq("John")
    end

    it "should preserve capitalization" do
      result = normalize_name("John McDonald")
      result.should eq("John McDonald")
    end
  end

  describe "full_name" do
    it "should preserve capitalization" do
      result = full_name("John", "McDonald")
      result.should eq("John McDonald")
    end

    it "returns only first name if no last name present" do
      result = full_name("John", nil)
      result.should eq("John")
    end

    it "returns only last name if no first name present" do
      result = full_name(nil, "McDonald")
      result.should eq("McDonald")
    end
  end

  describe "birthday_format" do
    it "returns nil if (!year && !month) || (!year && !day)" do
      result = birthday_format(nil, Date.today, nil)
      result.should eq(nil)

      result = birthday_format(Date.today.month, nil, nil)
      result.should eq(nil)
    end
  end

  describe "email_to_name" do
    it "create a probable name from email" do
      username_or_email = "foo.bar@test.com"
      result = email_to_name(username_or_email)
      result.should eq ['foo','bar',"foo bar"]
    end
  end
end


================================================
FILE: spec/spec_helper.rb
================================================
require "simplecov"
SimpleCov.start do
  add_filter "spec/"
end

require "rspec"
require "rack/test"
RSpec.configure do |config|
  config.include Rack::Test::Methods
end

MOUNT_PATH = "/contacts/"
Download .txt
gitextract_m1xfdr8k/

├── .gitignore
├── Gemfile
├── README.md
├── Rakefile
├── lib/
│   ├── omnicontacts/
│   │   ├── authorization/
│   │   │   ├── oauth1.rb
│   │   │   └── oauth2.rb
│   │   ├── builder.rb
│   │   ├── http_utils.rb
│   │   ├── importer/
│   │   │   ├── facebook.rb
│   │   │   ├── gmail.rb
│   │   │   ├── hotmail.rb
│   │   │   ├── linkedin.rb
│   │   │   ├── outlook.rb
│   │   │   └── yahoo.rb
│   │   ├── importer.rb
│   │   ├── integration_test.rb
│   │   ├── middleware/
│   │   │   ├── base_oauth.rb
│   │   │   ├── oauth1.rb
│   │   │   └── oauth2.rb
│   │   └── parse_utils.rb
│   └── omnicontacts.rb
├── omnicontacts.gemspec
└── spec/
    ├── omnicontacts/
    │   ├── authorization/
    │   │   ├── oauth1_spec.rb
    │   │   └── oauth2_spec.rb
    │   ├── http_utils_spec.rb
    │   ├── importer/
    │   │   ├── facebook_spec.rb
    │   │   ├── gmail_spec.rb
    │   │   ├── hotmail_spec.rb
    │   │   ├── linkedin_spec.rb
    │   │   ├── outlook_spec.rb
    │   │   └── yahoo_spec.rb
    │   ├── integration_test_spec.rb
    │   ├── middleware/
    │   │   ├── base_oauth_spec.rb
    │   │   ├── oauth1_spec.rb
    │   │   └── oauth2_spec.rb
    │   └── parse_utils_spec.rb
    └── spec_helper.rb
Download .txt
SYMBOL INDEX (187 symbols across 22 files)

FILE: lib/omnicontacts.rb
  type OmniContacts (line 1) | module OmniContacts
    class AuthorizationError (line 11) | class AuthorizationError < RuntimeError
    function integration_test (line 15) | def self.integration_test

FILE: lib/omnicontacts/authorization/oauth1.rb
  type OmniContacts (line 15) | module OmniContacts
    type Authorization (line 16) | module Authorization
      type OAuth1 (line 17) | module OAuth1
        function fetch_authorization_token (line 24) | def fetch_authorization_token
        function request_token_req_params (line 31) | def request_token_req_params
        function random_string (line 43) | def random_string
        function timestamp (line 47) | def timestamp
        function values_from_query_string (line 51) | def values_from_query_string query_string, keys_to_extract
        function authorization_url (line 65) | def authorization_url auth_token
        function fetch_access_token (line 73) | def fetch_access_token auth_token, auth_token_secret, auth_verifie...
        function access_token_req_params (line 80) | def access_token_req_params auth_token, auth_token_secret, auth_ve...
        function oauth_signature (line 108) | def oauth_signature method, url, params, secret

FILE: lib/omnicontacts/authorization/oauth2.rb
  type OmniContacts (line 15) | module OmniContacts
    type Authorization (line 16) | module Authorization
      type OAuth2 (line 17) | module OAuth2
        function authorization_url (line 22) | def authorization_url
        function authorize_url_params (line 28) | def authorize_url_params
        function fetch_access_token (line 42) | def fetch_access_token code
        function token_req_params (line 48) | def token_req_params code
        function access_token_from_response (line 58) | def access_token_from_response response
        function refresh_access_token (line 70) | def refresh_access_token refresh_token
        function refresh_token_req_params (line 76) | def refresh_token_req_params refresh_token

FILE: lib/omnicontacts/builder.rb
  type OmniContacts (line 3) | module OmniContacts
    class Builder (line 4) | class Builder < Rack::Builder
      method initialize (line 5) | def initialize(app, &block)
      method rack14? (line 14) | def rack14?
      method importer (line 19) | def importer importer, *args
      method call (line 26) | def call env

FILE: lib/omnicontacts/http_utils.rb
  type OmniContacts (line 7) | module OmniContacts
    type HTTPUtils (line 8) | module HTTPUtils
      function query_string_to_map (line 14) | def query_string_to_map query_string
      function to_query_string (line 22) | def to_query_string map
      function encode (line 29) | def encode to_encode
      function host_url_from_rack_env (line 37) | def host_url_from_rack_env env
      function scheme (line 43) | def scheme env
      function http_get (line 62) | def http_get host, path, params
      function https_post (line 69) | def https_post host, path, params
      function https_get (line 77) | def https_get host, path, params, headers = nil
      function https_connection (line 83) | def https_connection (host)
      function process_http_response (line 95) | def process_http_response response

FILE: lib/omnicontacts/importer.rb
  type OmniContacts (line 1) | module OmniContacts
    type Importer (line 2) | module Importer

FILE: lib/omnicontacts/importer/facebook.rb
  type OmniContacts (line 5) | module OmniContacts
    type Importer (line 6) | module Importer
      class Facebook (line 7) | class Facebook < Middleware::OAuth2
        method initialize (line 12) | def initialize *args
        method fetch_contacts_using_access_token (line 24) | def fetch_contacts_using_access_token access_token, access_token_s...
        method fetch_current_user (line 39) | def fetch_current_user access_token
        method extract_spouse_id (line 47) | def extract_spouse_id response
        method contacts_from_response (line 56) | def contacts_from_response(spouse_response, family_response, frien...
        method create_contact_element (line 81) | def create_contact_element contact_info
        method image_url (line 96) | def image_url fb_id
        method escape_windows_format (line 100) | def escape_windows_format value
        method birthday (line 104) | def birthday dob
        method current_user (line 110) | def current_user me

FILE: lib/omnicontacts/importer/gmail.rb
  type OmniContacts (line 4) | module OmniContacts
    type Importer (line 5) | module Importer
      class Gmail (line 6) | class Gmail < Middleware::OAuth2
        method initialize (line 11) | def initialize *args
        method fetch_contacts_using_access_token (line 24) | def fetch_contacts_using_access_token access_token, token_type
        method fetch_current_user (line 30) | def fetch_current_user access_token, token_type
        method contacts_req_params (line 38) | def contacts_req_params
        method contacts_req_headers (line 42) | def contacts_req_headers token, token_type
        method contacts_from_response (line 46) | def contacts_from_response(response_as_json, access_token)
        method current_user (line 185) | def current_user me, access_token, token_type
        method birthday (line 195) | def birthday dob
        method contact_id (line 202) | def contact_id(profile_url)

FILE: lib/omnicontacts/importer/hotmail.rb
  type OmniContacts (line 5) | module OmniContacts
    type Importer (line 6) | module Importer
      class Hotmail (line 7) | class Hotmail < Middleware::OAuth2
        method initialize (line 12) | def initialize app, client_id, client_secret, options ={}
        method fetch_contacts_using_access_token (line 23) | def fetch_contacts_using_access_token access_token, access_token_s...
        method fetch_current_user (line 29) | def fetch_current_user access_token
        method contacts_from_response (line 37) | def contacts_from_response response_as_json
        method parse_email (line 57) | def parse_email(emails)
        method current_user (line 62) | def current_user me
        method image_url (line 74) | def image_url hotmail_id
        method escape_windows_format (line 78) | def escape_windows_format value
        method valid_email? (line 82) | def valid_email? value

FILE: lib/omnicontacts/importer/linkedin.rb
  type OmniContacts (line 4) | module OmniContacts
    type Importer (line 5) | module Importer
      class Linkedin (line 6) | class Linkedin < Middleware::OAuth2
        method initialize (line 11) | def initialize *args
        method fetch_contacts_using_access_token (line 24) | def fetch_contacts_using_access_token access_token, token_type
        method contacts_req_params (line 32) | def contacts_req_params
        method contacts_req_headers (line 36) | def contacts_req_headers token, token_type
        method contacts_from_response (line 40) | def contacts_from_response response_as_json
        method authorize_url_params (line 56) | def authorize_url_params

FILE: lib/omnicontacts/importer/outlook.rb
  type OmniContacts (line 6) | module OmniContacts
    type Importer (line 7) | module Importer
      class Outlook (line 8) | class Outlook < Middleware::OAuth2
        method initialize (line 13) | def initialize app, client_id, client_secret, options ={}
        method fetch_contacts_using_access_token (line 24) | def fetch_contacts_using_access_token access_token, token_type
        method fetch_current_user (line 30) | def fetch_current_user access_token, token_type
        method contacts_req_headers (line 38) | def contacts_req_headers token, token_type
        method current_user (line 42) | def current_user me
        method contacts_from_response (line 59) | def contacts_from_response response_as_json
        method empty_contact (line 87) | def empty_contact
        method parse_email (line 93) | def parse_email emails
        method birthday (line 100) | def birthday dob
        method valid_email? (line 107) | def valid_email? value

FILE: lib/omnicontacts/importer/yahoo.rb
  type OmniContacts (line 5) | module OmniContacts
    type Importer (line 6) | module Importer
      class Yahoo (line 7) | class Yahoo < Middleware::OAuth1
        method initialize (line 12) | def initialize *args
        method fetch_contacts_from_token_and_verifier (line 21) | def fetch_contacts_from_token_and_verifier auth_token, auth_token_...
        method fetch_current_user (line 29) | def fetch_current_user access_token, access_token_secret, guid
        method contacts_req_params (line 38) | def contacts_req_params access_token, access_token_secret, contact...
        method contacts_from_response (line 53) | def contacts_from_response response_as_json
        method image_url (line 114) | def image_url yahoo_id
        method parse_email (line 118) | def parse_email(emails)
        method birthday (line 136) | def birthday dob
        method gender (line 144) | def gender g
        method my_image (line 149) | def my_image img
        method current_user (line 154) | def current_user me

FILE: lib/omnicontacts/integration_test.rb
  class IntegrationTest (line 3) | class IntegrationTest
    method initialize (line 8) | def initialize
    method clear_mocks (line 13) | def clear_mocks
    method mock (line 18) | def mock provider, contacts, user = {}
    method mock_authorization_from_user (line 23) | def mock_authorization_from_user provider
    method mock_fetch_contacts (line 27) | def mock_fetch_contacts provider
    method mock_fetch_user (line 38) | def mock_fetch_user provider

FILE: lib/omnicontacts/middleware/base_oauth.rb
  type OmniContacts (line 8) | module OmniContacts
    type Middleware (line 9) | module Middleware
      class BaseOAuth (line 10) | class BaseOAuth
        method initialize (line 14) | def initialize app, options
        method class_name (line 20) | def class_name
        method call (line 34) | def call env
        method test_mode? (line 49) | def test_mode?
        method handle_initial_request (line 53) | def handle_initial_request
        method handle_callback (line 63) | def handle_callback
        method set_current_user (line 75) | def set_current_user user
        method execute_and_rescue_exceptions (line 82) | def execute_and_rescue_exceptions
        method handle_error (line 92) | def handle_error error_type, exception
        method session (line 100) | def session
        method logger (line 105) | def logger
        method base_prop_name (line 109) | def base_prop_name
        method append_request_params (line 113) | def append_request_params(target_url)
        method append_state_query (line 122) | def append_state_query(target_url)

FILE: lib/omnicontacts/middleware/oauth1.rb
  type OmniContacts (line 10) | module OmniContacts
    type Middleware (line 11) | module Middleware
      class OAuth1 (line 12) | class OAuth1 < BaseOAuth
        method initialize (line 17) | def initialize app, consumer_key, consumer_secret, options = {}
        method callback (line 25) | def callback
        method request_authorization_from_user (line 34) | def request_authorization_from_user
        method token_secret_prop_name (line 41) | def token_secret_prop_name oauth_token
        method redirect_to_authorization_site (line 45) | def redirect_to_authorization_site auth_token
        method fetch_contacts (line 56) | def fetch_contacts

FILE: lib/omnicontacts/middleware/oauth2.rb
  type OmniContacts (line 11) | module OmniContacts
    type Middleware (line 12) | module Middleware
      class OAuth2 (line 13) | class OAuth2 < BaseOAuth
        method initialize (line 18) | def initialize app, client_id, client_secret, options ={}
        method request_authorization_from_user (line 26) | def request_authorization_from_user
        method redirect_uri (line 31) | def redirect_uri
        method fetch_contacts (line 44) | def fetch_contacts
        method refresh_token_prop_name (line 61) | def refresh_token_prop_name code

FILE: lib/omnicontacts/parse_utils.rb
  type OmniContacts (line 1) | module OmniContacts
    type ParseUtils (line 2) | module ParseUtils
      function birthday_format (line 5) | def birthday_format month, day, year
      function normalize_name (line 12) | def normalize_name name
      function full_name (line 21) | def full_name first_name, last_name
      function email_to_name (line 29) | def email_to_name username_or_email
      function image_url_from_email (line 41) | def image_url_from_email email

FILE: spec/omnicontacts/authorization/oauth1_spec.rb
  class OAuth1TestClass (line 8) | class OAuth1TestClass

FILE: spec/omnicontacts/authorization/oauth2_spec.rb
  class OAuth2TestClass (line 8) | class OAuth2TestClass

FILE: spec/omnicontacts/middleware/base_oauth_spec.rb
  class TestProvider (line 8) | class TestProvider < OmniContacts::Middleware::BaseOAuth
    method initialize (line 9) | def initialize app, consumer_key, consumer_secret, options = {}
    method redirect_path (line 13) | def redirect_path
    method mock_session (line 17) | def self.mock_session
    method session (line 21) | def session

FILE: spec/omnicontacts/middleware/oauth1_spec.rb
  class OAuth1Middleware (line 7) | class OAuth1Middleware < OmniContacts::Middleware::OAuth1
    method mock_auth_token_resp (line 8) | def self.mock_auth_token_resp
    method fetch_authorization_token (line 12) | def fetch_authorization_token
    method authorization_url (line 16) | def authorization_url auth_token
    method fetch_contacts_from_token_and_verifier (line 20) | def fetch_contacts_from_token_and_verifier oauth_token, ouath_token_se...
    method mock_session (line 24) | def self.mock_session
    method session (line 28) | def session

FILE: spec/omnicontacts/middleware/oauth2_spec.rb
  class OAuth2Middleware (line 7) | class OAuth2Middleware < OmniContacts::Middleware::OAuth2
    method authorization_url (line 8) | def authorization_url
    method redirect_path (line 12) | def redirect_path
    method mock_session (line 16) | def self.mock_session
    method session (line 20) | def session
    method fetch_access_token (line 24) | def fetch_access_token code
    method fetch_contacts_using_access_token (line 28) | def fetch_contacts_using_access_token token, token_type
Condensed preview — 37 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (144K chars).
[
  {
    "path": ".gitignore",
    "chars": 48,
    "preview": "coverage\n*.iml\n.idea\n.rvmrc\n.DS_Store\nnbproject\n"
  },
  {
    "path": "Gemfile",
    "chars": 37,
    "preview": "source 'http://rubygems.org'\n\ngemspec"
  },
  {
    "path": "README.md",
    "chars": 13137,
    "preview": "# OmniContacts\n\nInspired by the popular OmniAuth, OmniContacts is a library that enables users of an application to impo"
  },
  {
    "path": "Rakefile",
    "chars": 159,
    "preview": "require 'bundler'\nrequire 'rspec/core/rake_task'\n\nBundler::GemHelper.install_tasks\nRSpec::Core::RakeTask.new(:spec)\ntask"
  },
  {
    "path": "lib/omnicontacts/authorization/oauth1.rb",
    "chars": 4730,
    "preview": "require \"omnicontacts/http_utils\"\nrequire \"base64\"\n\n# This module represent a OAuth 1.0 Client.\n#\n# Classes including th"
  },
  {
    "path": "lib/omnicontacts/authorization/oauth2.rb",
    "chars": 2753,
    "preview": "require \"omnicontacts/http_utils\"\nrequire \"json\"\n\n# This module represents an OAuth 2.0 client.\n#\n# Classes including th"
  },
  {
    "path": "lib/omnicontacts/builder.rb",
    "chars": 654,
    "preview": "require \"omnicontacts\"\n\nmodule OmniContacts\n  class Builder < Rack::Builder\n    def initialize(app, &block)\n      if rac"
  },
  {
    "path": "lib/omnicontacts/http_utils.rb",
    "chars": 3199,
    "preview": "require \"net/http\"\nrequire \"net/https\"\nrequire \"cgi\"\nrequire \"openssl\"\n\n# This module contains a set of utility methods "
  },
  {
    "path": "lib/omnicontacts/importer/facebook.rb",
    "chars": 4915,
    "preview": "require \"omnicontacts/parse_utils\"\nrequire \"omnicontacts/middleware/oauth2\"\nrequire \"json\"\n\nmodule OmniContacts\n  module"
  },
  {
    "path": "lib/omnicontacts/importer/gmail.rb",
    "chars": 9759,
    "preview": "require \"omnicontacts/parse_utils\"\nrequire \"omnicontacts/middleware/oauth2\"\n\nmodule OmniContacts\n  module Importer\n    c"
  },
  {
    "path": "lib/omnicontacts/importer/hotmail.rb",
    "chars": 3518,
    "preview": "require \"omnicontacts/middleware/oauth2\"\nrequire \"omnicontacts/parse_utils\"\nrequire \"json\"\n\nmodule OmniContacts\n  module"
  },
  {
    "path": "lib/omnicontacts/importer/linkedin.rb",
    "chars": 2055,
    "preview": "require \"omnicontacts/parse_utils\"\nrequire \"omnicontacts/middleware/oauth2\"\n\nmodule OmniContacts\n  module Importer\n    c"
  },
  {
    "path": "lib/omnicontacts/importer/outlook.rb",
    "chars": 4213,
    "preview": "require \"omnicontacts/middleware/oauth2\"\nrequire \"omnicontacts/parse_utils\"\nrequire \"json\"\n\n# API Docs: https://msdn.mic"
  },
  {
    "path": "lib/omnicontacts/importer/yahoo.rb",
    "chars": 6913,
    "preview": "require \"omnicontacts/parse_utils\"\nrequire \"omnicontacts/middleware/oauth1\"\nrequire \"json\"\n\nmodule OmniContacts\n  module"
  },
  {
    "path": "lib/omnicontacts/importer.rb",
    "chars": 376,
    "preview": "module OmniContacts\n  module Importer\n\n    autoload :Gmail, \"omnicontacts/importer/gmail\"\n    autoload :Yahoo, \"omnicont"
  },
  {
    "path": "lib/omnicontacts/integration_test.rb",
    "chars": 833,
    "preview": "require 'singleton'\n\nclass IntegrationTest\n  include Singleton\n\n  attr_accessor :enabled\n\n  def initialize\n    enabled ="
  },
  {
    "path": "lib/omnicontacts/middleware/base_oauth.rb",
    "chars": 4246,
    "preview": "# This class contains the common behavior for middlewares\n# implementing either versions of OAuth.\n#\n# Extending classes"
  },
  {
    "path": "lib/omnicontacts/middleware/oauth1.rb",
    "chars": 2634,
    "preview": "require \"omnicontacts/authorization/oauth1\"\nrequire \"omnicontacts/middleware/base_oauth\"\n\n# This class is an OAuth 1.0 R"
  },
  {
    "path": "lib/omnicontacts/middleware/oauth2.rb",
    "chars": 2569,
    "preview": "require \"omnicontacts/authorization/oauth2\"\nrequire \"omnicontacts/middleware/base_oauth\"\n\n# This class is a OAuth 2 Rack"
  },
  {
    "path": "lib/omnicontacts/parse_utils.rb",
    "chars": 2012,
    "preview": "module OmniContacts\n  module ParseUtils\n\n    # return has of birthday day, month and year\n    def birthday_format month,"
  },
  {
    "path": "lib/omnicontacts.rb",
    "chars": 346,
    "preview": "module OmniContacts\n  \n  VERSION = \"0.3.10\"\n\n  MOUNT_PATH = \"/contacts/\"\n\n  autoload :Builder, \"omnicontacts/builder\"\n  "
  },
  {
    "path": "omnicontacts.gemspec",
    "chars": 1015,
    "preview": "# encoding: utf-8\nrequire File.expand_path('../lib/omnicontacts', __FILE__)\n\nGem::Specification.new do |gem|\n  gem.name "
  },
  {
    "path": "spec/omnicontacts/authorization/oauth1_spec.rb",
    "chars": 3686,
    "preview": "require \"spec_helper\"\nrequire \"omnicontacts/authorization/oauth1\"\n\ndescribe OmniContacts::Authorization::OAuth1 do\n\n  be"
  },
  {
    "path": "spec/omnicontacts/authorization/oauth2_spec.rb",
    "chars": 3690,
    "preview": "require \"spec_helper\"\nrequire \"omnicontacts/authorization/oauth2\"\n\ndescribe OmniContacts::Authorization::OAuth2 do\n\n  be"
  },
  {
    "path": "spec/omnicontacts/http_utils_spec.rb",
    "chars": 3006,
    "preview": "require \"spec_helper\"\nrequire \"omnicontacts/http_utils\"\n\ndescribe OmniContacts::HTTPUtils do\n\n  describe \"to_query_strin"
  },
  {
    "path": "spec/omnicontacts/importer/facebook_spec.rb",
    "chars": 4747,
    "preview": "require \"spec_helper\"\nrequire \"omnicontacts/importer/facebook\"\n\ndescribe OmniContacts::Importer::Facebook do\n\n  let(:fac"
  },
  {
    "path": "spec/omnicontacts/importer/gmail_spec.rb",
    "chars": 19631,
    "preview": "require \"spec_helper\"\nrequire \"omnicontacts/importer/gmail\"\n\ndescribe OmniContacts::Importer::Gmail do\n  let(:gmail) { O"
  },
  {
    "path": "spec/omnicontacts/importer/hotmail_spec.rb",
    "chars": 3712,
    "preview": "require \"spec_helper\"\nrequire \"omnicontacts/importer/hotmail\"\n\ndescribe OmniContacts::Importer::Hotmail do\n\n  let(:permi"
  },
  {
    "path": "spec/omnicontacts/importer/linkedin_spec.rb",
    "chars": 2741,
    "preview": "require \"spec_helper\"\nrequire \"omnicontacts/importer/linkedin\"\n\ndescribe OmniContacts::Importer::Linkedin do\n\n  let(:lin"
  },
  {
    "path": "spec/omnicontacts/importer/outlook_spec.rb",
    "chars": 6117,
    "preview": "require \"spec_helper\"\nrequire \"omnicontacts/importer/outlook\"\n\ndescribe OmniContacts::Importer::Outlook do\n\n  let(:permi"
  },
  {
    "path": "spec/omnicontacts/importer/yahoo_spec.rb",
    "chars": 5766,
    "preview": "require \"spec_helper\"\nrequire \"omnicontacts/importer/yahoo\"\n\ndescribe OmniContacts::Importer::Yahoo do\n\n  describe \"fetc"
  },
  {
    "path": "spec/omnicontacts/integration_test_spec.rb",
    "chars": 2137,
    "preview": "require \"spec_helper\"\nrequire \"omnicontacts/integration_test\"\n\ndescribe IntegrationTest do\n\n  context \"mock_initial_requ"
  },
  {
    "path": "spec/omnicontacts/middleware/base_oauth_spec.rb",
    "chars": 3018,
    "preview": "require \"spec_helper\"\nrequire \"omnicontacts\"\nrequire \"omnicontacts/middleware/base_oauth\"\n\ndescribe OmniContacts::Middle"
  },
  {
    "path": "spec/omnicontacts/middleware/oauth1_spec.rb",
    "chars": 3033,
    "preview": "require \"spec_helper\"\nrequire \"omnicontacts/middleware/oauth1\"\n\ndescribe OmniContacts::Middleware::OAuth1 do\n\n  before(:"
  },
  {
    "path": "spec/omnicontacts/middleware/oauth2_spec.rb",
    "chars": 2048,
    "preview": "require \"spec_helper\"\nrequire \"omnicontacts/middleware/oauth2\"\n\ndescribe OmniContacts::Middleware::OAuth2 do\n\n  before(:"
  },
  {
    "path": "spec/omnicontacts/parse_utils_spec.rb",
    "chars": 1393,
    "preview": "require \"spec_helper\"\nrequire \"omnicontacts/parse_utils\"\n\ninclude OmniContacts::ParseUtils\n\ndescribe OmniContacts::Parse"
  },
  {
    "path": "spec/spec_helper.rb",
    "chars": 197,
    "preview": "require \"simplecov\"\nSimpleCov.start do\n  add_filter \"spec/\"\nend\n\nrequire \"rspec\"\nrequire \"rack/test\"\nRSpec.configure do "
  }
]

About this extraction

This page contains the full source code of the Diego81/omnicontacts GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 37 files (131.9 KB), approximately 34.9k tokens, and a symbol index with 187 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!