garu 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/.document ADDED
@@ -0,0 +1,5 @@
1
+ lib/**/*.rb
2
+ bin/*
3
+ -
4
+ features/**/*.feature
5
+ LICENSE.txt
data/Gemfile ADDED
@@ -0,0 +1,13 @@
1
+ source "http://rubygems.org"
2
+ # Add dependencies required to use your gem here.
3
+ # Example:
4
+ # gem "activesupport", ">= 2.3.5"
5
+
6
+ # Add dependencies to develop your gem here.
7
+ # Include everything needed to run rake, tests, features, etc.
8
+ group :development do
9
+ gem "shoulda", ">= 0"
10
+ gem "bundler", "~> 1.0.0"
11
+ gem "jeweler", "~> 1.6.4"
12
+ gem "rcov", ">= 0"
13
+ end
data/Gemfile.lock ADDED
@@ -0,0 +1,20 @@
1
+ GEM
2
+ remote: http://rubygems.org/
3
+ specs:
4
+ git (1.2.5)
5
+ jeweler (1.6.4)
6
+ bundler (~> 1.0)
7
+ git (>= 1.2.5)
8
+ rake
9
+ rake (0.9.2.2)
10
+ rcov (0.9.11)
11
+ shoulda (2.11.3)
12
+
13
+ PLATFORMS
14
+ ruby
15
+
16
+ DEPENDENCIES
17
+ bundler (~> 1.0.0)
18
+ jeweler (~> 1.6.4)
19
+ rcov
20
+ shoulda
data/LICENSE.txt ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2011 Mathieu Davy
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.rdoc ADDED
@@ -0,0 +1,19 @@
1
+ = garu
2
+
3
+ A gem to send google analytics tracker requests from server side ruby code.
4
+
5
+ == Contributing to garu
6
+
7
+ * Check out the latest master to make sure the feature hasn't been implemented or the bug hasn't been fixed yet
8
+ * Check out the issue tracker to make sure someone already hasn't requested it and/or contributed it
9
+ * Fork the project
10
+ * Start a feature/bugfix branch
11
+ * Commit and push until you are happy with your contribution
12
+ * Make sure to add tests for it. This is important so I don't break it in a future version unintentionally.
13
+ * Please try not to mess with the Rakefile, version, or history. If you want to have your own version, or is otherwise necessary, that is fine, but please isolate to its own commit so I can cherry-pick around it.
14
+
15
+ == Copyright
16
+
17
+ Copyright (c) 2011 Mathieu Davy. See LICENSE.txt for
18
+ further details.
19
+
data/Rakefile ADDED
@@ -0,0 +1,53 @@
1
+ # encoding: utf-8
2
+
3
+ require 'rubygems'
4
+ require 'bundler'
5
+ begin
6
+ Bundler.setup(:default, :development)
7
+ rescue Bundler::BundlerError => e
8
+ $stderr.puts e.message
9
+ $stderr.puts "Run `bundle install` to install missing gems"
10
+ exit e.status_code
11
+ end
12
+ require 'rake'
13
+
14
+ require 'jeweler'
15
+ Jeweler::Tasks.new do |gem|
16
+ # gem is a Gem::Specification... see http://docs.rubygems.org/read/chapter/20 for more options
17
+ gem.name = "garu"
18
+ gem.homepage = "http://github.com/ekynoxe/garu"
19
+ gem.license = "MIT"
20
+ gem.summary = %Q{Garu! Google Analytics from Ruby.}
21
+ gem.description = %Q{A gem to send google analytics tracker requests from server side ruby code.}
22
+ gem.email = "matt@ekynoxe.com"
23
+ gem.authors = ["Mathieu Davy"]
24
+ # dependencies defined in Gemfile
25
+ end
26
+ Jeweler::RubygemsDotOrgTasks.new
27
+
28
+ require 'rake/testtask'
29
+ Rake::TestTask.new(:test) do |test|
30
+ test.libs << 'lib' << 'test'
31
+ test.pattern = 'test/**/test_*.rb'
32
+ test.verbose = true
33
+ end
34
+
35
+ require 'rcov/rcovtask'
36
+ Rcov::RcovTask.new do |test|
37
+ test.libs << 'test'
38
+ test.pattern = 'test/**/test_*.rb'
39
+ test.verbose = true
40
+ test.rcov_opts << '--exclude "gems/*"'
41
+ end
42
+
43
+ task :default => :test
44
+
45
+ require 'rake/rdoctask'
46
+ Rake::RDocTask.new do |rdoc|
47
+ version = File.exist?('VERSION') ? File.read('VERSION') : ""
48
+
49
+ rdoc.rdoc_dir = 'rdoc'
50
+ rdoc.title = "garu #{version}"
51
+ rdoc.rdoc_files.include('README*')
52
+ rdoc.rdoc_files.include('lib/**/*.rb')
53
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.1.0
data/garu.gemspec ADDED
@@ -0,0 +1,59 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE DIRECTLY
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = %q{garu}
8
+ s.version = "0.1.0"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
+ s.authors = ["Mathieu Davy"]
12
+ s.date = %q{2011-11-15}
13
+ s.description = %q{A gem to send google analytics tracker requests from server side ruby code.}
14
+ s.email = %q{matt@ekynoxe.com}
15
+ s.extra_rdoc_files = [
16
+ "LICENSE.txt",
17
+ "README.rdoc"
18
+ ]
19
+ s.files = [
20
+ ".document",
21
+ "Gemfile",
22
+ "Gemfile.lock",
23
+ "LICENSE.txt",
24
+ "README.rdoc",
25
+ "Rakefile",
26
+ "VERSION",
27
+ "garu.gemspec",
28
+ "lib/garu.rb",
29
+ "test/helper.rb",
30
+ "test/test_garu.rb"
31
+ ]
32
+ s.homepage = %q{http://github.com/ekynoxe/garu}
33
+ s.licenses = ["MIT"]
34
+ s.require_paths = ["lib"]
35
+ s.rubygems_version = %q{1.5.0}
36
+ s.summary = %q{Garu! Google Analytics from Ruby.}
37
+
38
+ if s.respond_to? :specification_version then
39
+ s.specification_version = 3
40
+
41
+ if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
42
+ s.add_development_dependency(%q<shoulda>, [">= 0"])
43
+ s.add_development_dependency(%q<bundler>, ["~> 1.0.0"])
44
+ s.add_development_dependency(%q<jeweler>, ["~> 1.6.4"])
45
+ s.add_development_dependency(%q<rcov>, [">= 0"])
46
+ else
47
+ s.add_dependency(%q<shoulda>, [">= 0"])
48
+ s.add_dependency(%q<bundler>, ["~> 1.0.0"])
49
+ s.add_dependency(%q<jeweler>, ["~> 1.6.4"])
50
+ s.add_dependency(%q<rcov>, [">= 0"])
51
+ end
52
+ else
53
+ s.add_dependency(%q<shoulda>, [">= 0"])
54
+ s.add_dependency(%q<bundler>, ["~> 1.0.0"])
55
+ s.add_dependency(%q<jeweler>, ["~> 1.6.4"])
56
+ s.add_dependency(%q<rcov>, [">= 0"])
57
+ end
58
+ end
59
+
data/lib/garu.rb ADDED
@@ -0,0 +1,195 @@
1
+ # GARU
2
+ #
3
+ # => A Ruby gem to place server side calls to google
4
+ # => analytics. Based on google analytics for mobile found
5
+ # => at http://code.google.com/mobile/analytics/
6
+ # => @Author: Mathieu Davy - ekynoxe http://ekynoxe.com
7
+ # => November 2011
8
+
9
+ require 'digest/md5'
10
+ require 'net/http'
11
+
12
+ class Garu
13
+ attr_accessor :account, :request
14
+
15
+ # Tracker version. (Kept from the google tools for mobile
16
+ # => as it's using the same parameters)
17
+ VERSION = "4.4sh"
18
+
19
+ COOKIE_NAME = "__utmmobile"
20
+ # The path the cookie will be available to, edit this to
21
+ # => use a different cookie path.
22
+ COOKIE_PATH = "/"
23
+ # Two years in seconds.
24
+ COOKIE_USER_PERSISTENCE = 63072000
25
+
26
+ #Google URL called to record the tracker hit
27
+ UTMGIFLOCATION = "http://www.google-analytics.com/__utm.gif"
28
+
29
+ # 1x1 transparent GIF to be sent back in the response body
30
+ GIF_DATA = [
31
+ 0x47.chr, 0x49.chr, 0x46.chr, 0x38.chr, 0x39.chr, 0x61.chr,
32
+ 0x01.chr, 0x00.chr, 0x01.chr, 0x00.chr, 0x80.chr, 0xff.chr,
33
+ 0x00.chr, 0xff.chr, 0xff.chr, 0xff.chr, 0x00.chr, 0x00.chr,
34
+ 0x00.chr, 0x2c.chr, 0x00.chr, 0x00.chr, 0x00.chr, 0x00.chr,
35
+ 0x01.chr, 0x00.chr, 0x01.chr, 0x00.chr, 0x00.chr, 0x02.chr,
36
+ 0x02.chr, 0x44.chr, 0x01.chr, 0x00.chr, 0x3b.chr
37
+ ]
38
+
39
+ def initialize (account, request)
40
+ @request = request
41
+ @account = account
42
+ end
43
+
44
+ # Get a random number string.
45
+ def getRandomNumber
46
+ return rand(0x7fffffff).to_s
47
+ end
48
+
49
+ # The last octect of the IP address is removed to
50
+ # => anonymize the user.
51
+ def getIP (remoteAddress=nil)
52
+ if remoteAddress.nil?
53
+ remoteAddress = @request.env['REMOTE_ADDR'].split(',').first
54
+ end
55
+ matches = /^([^.]+\.[^.]+\.[^.]+\.).*/.match(remoteAddress)
56
+ if !matches[1].nil?
57
+ remoteAddress = matches[1]+"0"
58
+ else
59
+ remoteAddress = ""
60
+ end
61
+
62
+ remoteAddress
63
+ end
64
+
65
+ # Generate a visitor id for this hit.
66
+ # => If there is a visitor id in the cookie, use
67
+ # => that, otherwise use the guid if we have one,
68
+ # => otherwise use a random number.
69
+ def getVisitorId(guid, account, userAgent, cookie)
70
+ # If there is a value in the cookie, don't change it.
71
+ if (!cookie.nil?)
72
+ return cookie
73
+ end
74
+
75
+ message = ""
76
+
77
+ if (!guid.nil?)
78
+ # Create the visitor id using the guid.
79
+ message = guid + account
80
+ else
81
+ # Otherwise this is a new user, create a new
82
+ # => random id.
83
+ message = userAgent + getRandomNumber
84
+ end
85
+
86
+ md5String = Digest::MD5.hexdigest(message)
87
+
88
+ return "0x" + md5String[0..16]
89
+ end
90
+
91
+ # Make a tracking request to Google Analytics
92
+ # => from this server.
93
+ # => Copies the headers from the original
94
+ # => request to the new one.
95
+ def sendRequestToGoogleAnalytics(utmUrl)
96
+ uri = URI.parse(utmUrl)
97
+ req = Net::HTTP::Get.new(uri.request_uri)
98
+ req['user_agent'] = @request.env["HTTP_USER_AGENT"]
99
+ req['Accepts-Language:'] = @request.env["HTTP_ACCEPT_LANGUAGE"]
100
+
101
+ res = Net::HTTP.start(uri.host, uri.port) {|http|
102
+ http.request(req)
103
+ }
104
+ res
105
+ end
106
+
107
+ # Track a page view, updates all the cookies
108
+ # => and campaign tracker, makes a server side
109
+ # => request to Google Analytics and writes the
110
+ # => transparent gif byte data to the response.
111
+ def trackPageView()
112
+ timeStamp = Time.now.getutc.to_i
113
+ domainName = @request.host
114
+
115
+ if domainName.nil?
116
+ domainName = ""
117
+ end
118
+
119
+ # Get the referrer from the utmr parameter,
120
+ # => this is the referrer to the page that
121
+ # => contains the tracking pixel, not the
122
+ # => referrer for tracking pixel.
123
+ documentReferer = @request.env["HTTP_REFERER"]
124
+
125
+ if (documentReferer.nil? && documentReferer != "0")
126
+ documentReferer = "-"
127
+ else
128
+ documentReferer = URI.unescape(documentReferer)
129
+ end
130
+
131
+ documentPath = @request.path
132
+ if documentPath.nil?
133
+ documentPath = ""
134
+ else
135
+ documentPath = URI.unescape(documentPath)
136
+ end
137
+
138
+ account = @account
139
+ userAgent = @request.env["HTTP_USER_AGENT"]
140
+ if userAgent.nil?
141
+ userAgent = ""
142
+ end
143
+
144
+ # Try and get visitor cookie from the request.
145
+ cookie = @request.cookies[Garu::COOKIE_NAME]
146
+
147
+ visitorId = getVisitorId(@request.env["HTTP_X_DCMGUID"], account, userAgent, cookie)
148
+
149
+ # Construct the gif hit url.
150
+ utmUrl = Garu::UTMGIFLOCATION + "?" +
151
+ "utmwv=" + Garu::VERSION +
152
+ "&utmn=" + getRandomNumber +
153
+ "&utmhn=" + URI.escape(domainName) +
154
+ "&utmr=" + URI.escape(documentReferer) +
155
+ "&utmp=" + URI.escape(documentPath) +
156
+ "&utmac=" + account.to_s +
157
+ "&utmcc=__utma%3D999.999.999.999.999.1%3B" +
158
+ "&utmvid=" + visitorId +
159
+ "&utmip=" + getIP(@request.env["REMOTE_ADDR"])
160
+
161
+ gaResponse = sendRequestToGoogleAnalytics(utmUrl)
162
+
163
+ response = {
164
+ 'headers' => {
165
+ 'Content-Type' => "image/gif",
166
+ 'Cache-Control' => "private, no-cache, no-cache=Set-Cookie, proxy-revalidate",
167
+ 'Pragma' => "no-cache",
168
+ 'Expires' => "Wed, 17 Sep 1975 21:32:10 GMT"
169
+ },
170
+ 'body' => GIF_DATA.join
171
+ }
172
+
173
+ # If the debug parameter is on, add a header
174
+ # => to the response that contains the url
175
+ # => that was used to contact Google Analytics.
176
+ if !@request.params["utmdebug"].nil?
177
+ response["headers"].merge!({"X-GA-MOBILE-URL" => utmUrl})
178
+ end
179
+
180
+ # If no cookie was passed with the request,
181
+ # => a new one has been created and therefore
182
+ # => needs to be sent back in the response from Garu
183
+ if !cookie
184
+ response.merge!({'cookie' => {
185
+ 'name' => COOKIE_NAME,
186
+ 'value' => visitorId,
187
+ 'path' => COOKIE_PATH,
188
+ 'domain' => domainName,
189
+ 'expires' => Time.at(timeStamp + COOKIE_USER_PERSISTENCE)
190
+ }})
191
+ end
192
+
193
+ response
194
+ end
195
+ end
data/test/helper.rb ADDED
@@ -0,0 +1,18 @@
1
+ require 'rubygems'
2
+ require 'bundler'
3
+ begin
4
+ Bundler.setup(:default, :development)
5
+ rescue Bundler::BundlerError => e
6
+ $stderr.puts e.message
7
+ $stderr.puts "Run `bundle install` to install missing gems"
8
+ exit e.status_code
9
+ end
10
+ require 'test/unit'
11
+ require 'shoulda'
12
+
13
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
14
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
15
+ require 'garu'
16
+
17
+ class Test::Unit::TestCase
18
+ end
data/test/test_garu.rb ADDED
@@ -0,0 +1,7 @@
1
+ require 'helper'
2
+
3
+ class TestGaru < Test::Unit::TestCase
4
+ should "remove the last block of an IP address" do
5
+ return true
6
+ end
7
+ end
metadata ADDED
@@ -0,0 +1,113 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: garu
3
+ version: !ruby/object:Gem::Version
4
+ prerelease:
5
+ version: 0.1.0
6
+ platform: ruby
7
+ authors:
8
+ - Mathieu Davy
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+
13
+ date: 2011-11-15 00:00:00 +00:00
14
+ default_executable:
15
+ dependencies:
16
+ - !ruby/object:Gem::Dependency
17
+ name: shoulda
18
+ requirement: &id001 !ruby/object:Gem::Requirement
19
+ none: false
20
+ requirements:
21
+ - - ">="
22
+ - !ruby/object:Gem::Version
23
+ version: "0"
24
+ type: :development
25
+ prerelease: false
26
+ version_requirements: *id001
27
+ - !ruby/object:Gem::Dependency
28
+ name: bundler
29
+ requirement: &id002 !ruby/object:Gem::Requirement
30
+ none: false
31
+ requirements:
32
+ - - ~>
33
+ - !ruby/object:Gem::Version
34
+ version: 1.0.0
35
+ type: :development
36
+ prerelease: false
37
+ version_requirements: *id002
38
+ - !ruby/object:Gem::Dependency
39
+ name: jeweler
40
+ requirement: &id003 !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ~>
44
+ - !ruby/object:Gem::Version
45
+ version: 1.6.4
46
+ type: :development
47
+ prerelease: false
48
+ version_requirements: *id003
49
+ - !ruby/object:Gem::Dependency
50
+ name: rcov
51
+ requirement: &id004 !ruby/object:Gem::Requirement
52
+ none: false
53
+ requirements:
54
+ - - ">="
55
+ - !ruby/object:Gem::Version
56
+ version: "0"
57
+ type: :development
58
+ prerelease: false
59
+ version_requirements: *id004
60
+ description: A gem to send google analytics tracker requests from server side ruby code.
61
+ email: matt@ekynoxe.com
62
+ executables: []
63
+
64
+ extensions: []
65
+
66
+ extra_rdoc_files:
67
+ - LICENSE.txt
68
+ - README.rdoc
69
+ files:
70
+ - .document
71
+ - Gemfile
72
+ - Gemfile.lock
73
+ - LICENSE.txt
74
+ - README.rdoc
75
+ - Rakefile
76
+ - VERSION
77
+ - garu.gemspec
78
+ - lib/garu.rb
79
+ - test/helper.rb
80
+ - test/test_garu.rb
81
+ has_rdoc: true
82
+ homepage: http://github.com/ekynoxe/garu
83
+ licenses:
84
+ - MIT
85
+ post_install_message:
86
+ rdoc_options: []
87
+
88
+ require_paths:
89
+ - lib
90
+ required_ruby_version: !ruby/object:Gem::Requirement
91
+ none: false
92
+ requirements:
93
+ - - ">="
94
+ - !ruby/object:Gem::Version
95
+ hash: 4023332570823800525
96
+ segments:
97
+ - 0
98
+ version: "0"
99
+ required_rubygems_version: !ruby/object:Gem::Requirement
100
+ none: false
101
+ requirements:
102
+ - - ">="
103
+ - !ruby/object:Gem::Version
104
+ version: "0"
105
+ requirements: []
106
+
107
+ rubyforge_project:
108
+ rubygems_version: 1.5.0
109
+ signing_key:
110
+ specification_version: 3
111
+ summary: Garu! Google Analytics from Ruby.
112
+ test_files: []
113
+