atom-tools 0.9.1 → 0.9.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/Rakefile +1 -1
- data/doc/classes/Atom/Author.html +130 -0
- data/doc/classes/Atom/Category.html +128 -0
- data/doc/classes/Atom/Collection.html +322 -0
- data/doc/classes/Atom/Content.html +129 -0
- data/doc/classes/Atom/Contributor.html +119 -0
- data/doc/classes/Atom/DigestAuth.html +285 -0
- data/doc/classes/Atom/Element.html +325 -0
- data/doc/classes/Atom/Entry.html +369 -0
- data/doc/classes/Atom/Feed.html +595 -0
- data/doc/classes/Atom/HTTP.html +436 -0
- data/doc/classes/Atom/HTTPResponse.html +149 -0
- data/doc/classes/Atom/Link.html +137 -0
- data/doc/classes/Atom/Service.html +260 -0
- data/doc/classes/Atom/Text.html +245 -0
- data/doc/classes/Atom/Workspace.html +121 -0
- data/doc/classes/XHTML.html +118 -0
- data/doc/created.rid +1 -0
- data/doc/files/README.html +213 -0
- data/doc/files/lib/atom/collection_rb.html +110 -0
- data/doc/files/lib/atom/element_rb.html +109 -0
- data/doc/files/lib/atom/entry_rb.html +111 -0
- data/doc/files/lib/atom/feed_rb.html +112 -0
- data/doc/files/lib/atom/http_rb.html +112 -0
- data/doc/files/lib/atom/service_rb.html +111 -0
- data/doc/files/lib/atom/text_rb.html +109 -0
- data/doc/files/lib/atom/xml_rb.html +110 -0
- data/doc/files/lib/atom/yaml_rb.html +109 -0
- data/doc/fr_class_index.html +42 -0
- data/doc/fr_file_index.html +36 -0
- data/doc/fr_method_index.html +69 -0
- data/doc/index.html +24 -0
- data/doc/rdoc-style.css +208 -0
- data/lib/atom/collection.rb +0 -21
- data/lib/atom/entry.rb +13 -0
- data/lib/atom/feed.rb +6 -7
- data/lib/atom/http.rb +97 -19
- data/lib/atom/service.rb +2 -22
- data/lib/atom/yaml.rb +1 -1
- data/test/test_feed.rb +27 -4
- data/test/test_http.rb +153 -34
- metadata +39 -2
@@ -0,0 +1,36 @@
|
|
1
|
+
|
2
|
+
<?xml version="1.0" encoding="iso-8859-1"?>
|
3
|
+
<!DOCTYPE html
|
4
|
+
PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
|
5
|
+
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
|
6
|
+
|
7
|
+
<!--
|
8
|
+
|
9
|
+
Files
|
10
|
+
|
11
|
+
-->
|
12
|
+
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
|
13
|
+
<head>
|
14
|
+
<title>Files</title>
|
15
|
+
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" />
|
16
|
+
<link rel="stylesheet" href="rdoc-style.css" type="text/css" />
|
17
|
+
<base target="docwin" />
|
18
|
+
</head>
|
19
|
+
<body>
|
20
|
+
<div id="index">
|
21
|
+
<h1 class="section-bar">Files</h1>
|
22
|
+
<div id="index-entries">
|
23
|
+
<a href="files/README.html">README</a><br />
|
24
|
+
<a href="files/lib/atom/collection_rb.html">lib/atom/collection.rb</a><br />
|
25
|
+
<a href="files/lib/atom/element_rb.html">lib/atom/element.rb</a><br />
|
26
|
+
<a href="files/lib/atom/entry_rb.html">lib/atom/entry.rb</a><br />
|
27
|
+
<a href="files/lib/atom/feed_rb.html">lib/atom/feed.rb</a><br />
|
28
|
+
<a href="files/lib/atom/http_rb.html">lib/atom/http.rb</a><br />
|
29
|
+
<a href="files/lib/atom/service_rb.html">lib/atom/service.rb</a><br />
|
30
|
+
<a href="files/lib/atom/text_rb.html">lib/atom/text.rb</a><br />
|
31
|
+
<a href="files/lib/atom/xml_rb.html">lib/atom/xml.rb</a><br />
|
32
|
+
<a href="files/lib/atom/yaml_rb.html">lib/atom/yaml.rb</a><br />
|
33
|
+
</div>
|
34
|
+
</div>
|
35
|
+
</body>
|
36
|
+
</html>
|
@@ -0,0 +1,69 @@
|
|
1
|
+
|
2
|
+
<?xml version="1.0" encoding="iso-8859-1"?>
|
3
|
+
<!DOCTYPE html
|
4
|
+
PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
|
5
|
+
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
|
6
|
+
|
7
|
+
<!--
|
8
|
+
|
9
|
+
Methods
|
10
|
+
|
11
|
+
-->
|
12
|
+
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
|
13
|
+
<head>
|
14
|
+
<title>Methods</title>
|
15
|
+
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" />
|
16
|
+
<link rel="stylesheet" href="rdoc-style.css" type="text/css" />
|
17
|
+
<base target="docwin" />
|
18
|
+
</head>
|
19
|
+
<body>
|
20
|
+
<div id="index">
|
21
|
+
<h1 class="section-bar">Methods</h1>
|
22
|
+
<div id="index-entries">
|
23
|
+
<a href="classes/Atom/Feed.html#M000019"><< (Atom::Feed)</a><br />
|
24
|
+
<a href="classes/Atom/Element.html#M000039">[] (Atom::Element)</a><br />
|
25
|
+
<a href="classes/Atom/Element.html#M000040">[]= (Atom::Element)</a><br />
|
26
|
+
<a href="classes/Atom/HTTP.html#M000026">delete (Atom::HTTP)</a><br />
|
27
|
+
<a href="classes/Atom/Collection.html#M000033">delete! (Atom::Collection)</a><br />
|
28
|
+
<a href="classes/Atom/DigestAuth.html#M000004">digest_authenticate (Atom::DigestAuth)</a><br />
|
29
|
+
<a href="classes/Atom/Feed.html#M000013">each (Atom::Feed)</a><br />
|
30
|
+
<a href="classes/Atom/Entry.html#M000010">edit_url (Atom::Entry)</a><br />
|
31
|
+
<a href="classes/Atom/Entry.html#M000006">from_yaml (Atom::Entry)</a><br />
|
32
|
+
<a href="classes/Atom/HTTP.html#M000023">get (Atom::HTTP)</a><br />
|
33
|
+
<a href="classes/Atom/HTTP.html#M000028">get_atom_entry (Atom::HTTP)</a><br />
|
34
|
+
<a href="classes/Atom/Feed.html#M000014">get_everything! (Atom::Feed)</a><br />
|
35
|
+
<a href="classes/Atom/DigestAuth.html#M000002">h (Atom::DigestAuth)</a><br />
|
36
|
+
<a href="classes/Atom/Text.html#M000021">html (Atom::Text)</a><br />
|
37
|
+
<a href="classes/Atom/DigestAuth.html#M000003">kd (Atom::DigestAuth)</a><br />
|
38
|
+
<a href="classes/Atom/Feed.html#M000017">merge (Atom::Feed)</a><br />
|
39
|
+
<a href="classes/Atom/Feed.html#M000016">merge! (Atom::Feed)</a><br />
|
40
|
+
<a href="classes/Atom/Feed.html#M000015">merge_entries! (Atom::Feed)</a><br />
|
41
|
+
<a href="classes/Atom/Feed.html#M000012">new (Atom::Feed)</a><br />
|
42
|
+
<a href="classes/Atom/Collection.html#M000030">new (Atom::Collection)</a><br />
|
43
|
+
<a href="classes/Atom/Service.html#M000036">new (Atom::Service)</a><br />
|
44
|
+
<a href="classes/Atom/Feed.html#M000011">parse (Atom::Feed)</a><br />
|
45
|
+
<a href="classes/Atom/Service.html#M000037">parse (Atom::Service)</a><br />
|
46
|
+
<a href="classes/Atom/Entry.html#M000007">parse (Atom::Entry)</a><br />
|
47
|
+
<a href="classes/Atom/DigestAuth.html#M000001">parse_wwwauth_digest (Atom::DigestAuth)</a><br />
|
48
|
+
<a href="classes/Atom/HTTP.html#M000024">post (Atom::HTTP)</a><br />
|
49
|
+
<a href="classes/Atom/Collection.html#M000031">post! (Atom::Collection)</a><br />
|
50
|
+
<a href="classes/Atom/Collection.html#M000034">post_media! (Atom::Collection)</a><br />
|
51
|
+
<a href="classes/Atom/HTTP.html#M000025">put (Atom::HTTP)</a><br />
|
52
|
+
<a href="classes/Atom/Collection.html#M000032">put! (Atom::Collection)</a><br />
|
53
|
+
<a href="classes/Atom/HTTP.html#M000029">put_atom_entry (Atom::HTTP)</a><br />
|
54
|
+
<a href="classes/Atom/Collection.html#M000035">put_media! (Atom::Collection)</a><br />
|
55
|
+
<a href="classes/Atom/Entry.html#M000009">tag_with (Atom::Entry)</a><br />
|
56
|
+
<a href="classes/Atom/Element.html#M000041">to_element (Atom::Element)</a><br />
|
57
|
+
<a href="classes/Atom/Text.html#M000020">to_s (Atom::Text)</a><br />
|
58
|
+
<a href="classes/Atom/Element.html#M000043">to_s (Atom::Element)</a><br />
|
59
|
+
<a href="classes/Atom/Element.html#M000042">to_xml (Atom::Element)</a><br />
|
60
|
+
<a href="classes/Atom/Service.html#M000038">to_xml (Atom::Service)</a><br />
|
61
|
+
<a href="classes/Atom/Feed.html#M000018">update! (Atom::Feed)</a><br />
|
62
|
+
<a href="classes/Atom/Entry.html#M000008">updated! (Atom::Entry)</a><br />
|
63
|
+
<a href="classes/Atom/HTTPResponse.html#M000005">validate_content_type (Atom::HTTPResponse)</a><br />
|
64
|
+
<a href="classes/Atom/HTTP.html#M000027">when_auth (Atom::HTTP)</a><br />
|
65
|
+
<a href="classes/Atom/Text.html#M000022">xml (Atom::Text)</a><br />
|
66
|
+
</div>
|
67
|
+
</div>
|
68
|
+
</body>
|
69
|
+
</html>
|
data/doc/index.html
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
<?xml version="1.0" encoding="iso-8859-1"?>
|
2
|
+
<!DOCTYPE html
|
3
|
+
PUBLIC "-//W3C//DTD XHTML 1.0 Frameset//EN"
|
4
|
+
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-frameset.dtd">
|
5
|
+
|
6
|
+
<!--
|
7
|
+
|
8
|
+
atom-tools documentation
|
9
|
+
|
10
|
+
-->
|
11
|
+
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
|
12
|
+
<head>
|
13
|
+
<title>atom-tools documentation</title>
|
14
|
+
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" />
|
15
|
+
</head>
|
16
|
+
<frameset rows="20%, 80%">
|
17
|
+
<frameset cols="25%,35%,45%">
|
18
|
+
<frame src="fr_file_index.html" title="Files" name="Files" />
|
19
|
+
<frame src="fr_class_index.html" name="Classes" />
|
20
|
+
<frame src="fr_method_index.html" name="Methods" />
|
21
|
+
</frameset>
|
22
|
+
<frame src="files/README.html" name="docwin" />
|
23
|
+
</frameset>
|
24
|
+
</html>
|
data/doc/rdoc-style.css
ADDED
@@ -0,0 +1,208 @@
|
|
1
|
+
|
2
|
+
body {
|
3
|
+
font-family: Verdana,Arial,Helvetica,sans-serif;
|
4
|
+
font-size: 90%;
|
5
|
+
margin: 0;
|
6
|
+
margin-left: 40px;
|
7
|
+
padding: 0;
|
8
|
+
background: white;
|
9
|
+
}
|
10
|
+
|
11
|
+
h1,h2,h3,h4 { margin: 0; color: #efefef; background: transparent; }
|
12
|
+
h1 { font-size: 150%; }
|
13
|
+
h2,h3,h4 { margin-top: 1em; }
|
14
|
+
|
15
|
+
a { background: #eef; color: #039; text-decoration: none; }
|
16
|
+
a:hover { background: #039; color: #eef; }
|
17
|
+
|
18
|
+
/* Override the base stylesheet's Anchor inside a table cell */
|
19
|
+
td > a {
|
20
|
+
background: transparent;
|
21
|
+
color: #039;
|
22
|
+
text-decoration: none;
|
23
|
+
}
|
24
|
+
|
25
|
+
/* and inside a section title */
|
26
|
+
.section-title > a {
|
27
|
+
background: transparent;
|
28
|
+
color: #eee;
|
29
|
+
text-decoration: none;
|
30
|
+
}
|
31
|
+
|
32
|
+
/* === Structural elements =================================== */
|
33
|
+
|
34
|
+
div#index {
|
35
|
+
margin: 0;
|
36
|
+
margin-left: -40px;
|
37
|
+
padding: 0;
|
38
|
+
font-size: 90%;
|
39
|
+
}
|
40
|
+
|
41
|
+
|
42
|
+
div#index a {
|
43
|
+
margin-left: 0.7em;
|
44
|
+
}
|
45
|
+
|
46
|
+
div#index .section-bar {
|
47
|
+
margin-left: 0px;
|
48
|
+
padding-left: 0.7em;
|
49
|
+
background: #ccc;
|
50
|
+
font-size: small;
|
51
|
+
}
|
52
|
+
|
53
|
+
|
54
|
+
div#classHeader, div#fileHeader {
|
55
|
+
width: auto;
|
56
|
+
color: white;
|
57
|
+
padding: 0.5em 1.5em 0.5em 1.5em;
|
58
|
+
margin: 0;
|
59
|
+
margin-left: -40px;
|
60
|
+
border-bottom: 3px solid #006;
|
61
|
+
}
|
62
|
+
|
63
|
+
div#classHeader a, div#fileHeader a {
|
64
|
+
background: inherit;
|
65
|
+
color: white;
|
66
|
+
}
|
67
|
+
|
68
|
+
div#classHeader td, div#fileHeader td {
|
69
|
+
background: inherit;
|
70
|
+
color: white;
|
71
|
+
}
|
72
|
+
|
73
|
+
|
74
|
+
div#fileHeader {
|
75
|
+
background: #057;
|
76
|
+
}
|
77
|
+
|
78
|
+
div#classHeader {
|
79
|
+
background: #048;
|
80
|
+
}
|
81
|
+
|
82
|
+
|
83
|
+
.class-name-in-header {
|
84
|
+
font-size: 180%;
|
85
|
+
font-weight: bold;
|
86
|
+
}
|
87
|
+
|
88
|
+
|
89
|
+
div#bodyContent {
|
90
|
+
padding: 0 1.5em 0 1.5em;
|
91
|
+
}
|
92
|
+
|
93
|
+
div#description {
|
94
|
+
padding: 0.5em 1.5em;
|
95
|
+
background: #efefef;
|
96
|
+
border: 1px dotted #999;
|
97
|
+
}
|
98
|
+
|
99
|
+
div#description h1,h2,h3,h4,h5,h6 {
|
100
|
+
color: #125;;
|
101
|
+
background: transparent;
|
102
|
+
}
|
103
|
+
|
104
|
+
div#validator-badges {
|
105
|
+
text-align: center;
|
106
|
+
}
|
107
|
+
div#validator-badges img { border: 0; }
|
108
|
+
|
109
|
+
div#copyright {
|
110
|
+
color: #333;
|
111
|
+
background: #efefef;
|
112
|
+
font: 0.75em sans-serif;
|
113
|
+
margin-top: 5em;
|
114
|
+
margin-bottom: 0;
|
115
|
+
padding: 0.5em 2em;
|
116
|
+
}
|
117
|
+
|
118
|
+
|
119
|
+
/* === Classes =================================== */
|
120
|
+
|
121
|
+
table.header-table {
|
122
|
+
color: white;
|
123
|
+
font-size: small;
|
124
|
+
}
|
125
|
+
|
126
|
+
.type-note {
|
127
|
+
font-size: small;
|
128
|
+
color: #DEDEDE;
|
129
|
+
}
|
130
|
+
|
131
|
+
.xxsection-bar {
|
132
|
+
background: #eee;
|
133
|
+
color: #333;
|
134
|
+
padding: 3px;
|
135
|
+
}
|
136
|
+
|
137
|
+
.section-bar {
|
138
|
+
color: #333;
|
139
|
+
border-bottom: 1px solid #999;
|
140
|
+
margin-left: -20px;
|
141
|
+
}
|
142
|
+
|
143
|
+
|
144
|
+
.section-title {
|
145
|
+
background: #79a;
|
146
|
+
color: #eee;
|
147
|
+
padding: 3px;
|
148
|
+
margin-top: 2em;
|
149
|
+
margin-left: -30px;
|
150
|
+
border: 1px solid #999;
|
151
|
+
}
|
152
|
+
|
153
|
+
.top-aligned-row { vertical-align: top }
|
154
|
+
.bottom-aligned-row { vertical-align: bottom }
|
155
|
+
|
156
|
+
/* --- Context section classes ----------------------- */
|
157
|
+
|
158
|
+
.context-row { }
|
159
|
+
.context-item-name { font-family: monospace; font-weight: bold; color: black; }
|
160
|
+
.context-item-value { font-size: small; color: #448; }
|
161
|
+
.context-item-desc { color: #333; padding-left: 2em; }
|
162
|
+
|
163
|
+
/* --- Method classes -------------------------- */
|
164
|
+
.method-detail {
|
165
|
+
background: #efefef;
|
166
|
+
padding: 0;
|
167
|
+
margin-top: 0.5em;
|
168
|
+
margin-bottom: 1em;
|
169
|
+
border: 1px dotted #ccc;
|
170
|
+
}
|
171
|
+
.method-heading {
|
172
|
+
color: black;
|
173
|
+
background: #ccc;
|
174
|
+
border-bottom: 1px solid #666;
|
175
|
+
padding: 0.2em 0.5em 0 0.5em;
|
176
|
+
}
|
177
|
+
.method-signature { color: black; background: inherit; }
|
178
|
+
.method-name { font-weight: bold; }
|
179
|
+
.method-args { font-style: italic; }
|
180
|
+
.method-description { padding: 0 0.5em 0 0.5em; }
|
181
|
+
|
182
|
+
/* --- Source code sections -------------------- */
|
183
|
+
|
184
|
+
a.source-toggle { font-size: 90%; }
|
185
|
+
div.method-source-code {
|
186
|
+
background: #262626;
|
187
|
+
color: #ffdead;
|
188
|
+
margin: 1em;
|
189
|
+
padding: 0.5em;
|
190
|
+
border: 1px dashed #999;
|
191
|
+
overflow: hidden;
|
192
|
+
}
|
193
|
+
|
194
|
+
div.method-source-code pre { color: #ffdead; overflow: hidden; }
|
195
|
+
|
196
|
+
/* --- Ruby keyword styles --------------------- */
|
197
|
+
|
198
|
+
.standalone-code { background: #221111; color: #ffdead; overflow: hidden; }
|
199
|
+
|
200
|
+
.ruby-constant { color: #7fffd4; background: transparent; }
|
201
|
+
.ruby-keyword { color: #00ffff; background: transparent; }
|
202
|
+
.ruby-ivar { color: #eedd82; background: transparent; }
|
203
|
+
.ruby-operator { color: #00ffee; background: transparent; }
|
204
|
+
.ruby-identifier { color: #ffdead; background: transparent; }
|
205
|
+
.ruby-node { color: #ffa07a; background: transparent; }
|
206
|
+
.ruby-comment { color: #b22222; font-weight: bold; background: transparent; }
|
207
|
+
.ruby-regexp { color: #ffa07a; background: transparent; }
|
208
|
+
.ruby-value { color: #7fffd4; background: transparent; }
|
data/lib/atom/collection.rb
CHANGED
@@ -51,25 +51,4 @@ module Atom
|
|
51
51
|
@http.put(url, data, headers)
|
52
52
|
end
|
53
53
|
end
|
54
|
-
|
55
|
-
class HTTP
|
56
|
-
# GET a URL and turn it into an Atom::Entry
|
57
|
-
def get_atom_entry(url)
|
58
|
-
res = get(url)
|
59
|
-
|
60
|
-
if res.code != "200" or res.content_type != "application/atom+xml"
|
61
|
-
raise Atom::HTTPException, "expected Atom::Entry, didn't get it"
|
62
|
-
end
|
63
|
-
|
64
|
-
Atom::Entry.parse(res.body, url)
|
65
|
-
end
|
66
|
-
|
67
|
-
# PUT an Atom::Entry to a URL
|
68
|
-
def put_atom_entry(entry, url = entry.edit_url)
|
69
|
-
raise "Cowardly refusing to PUT a non-Atom::Entry (#{entry.class})" unless entry.is_a? Atom::Entry
|
70
|
-
headers = {"Content-Type" => "application/atom+xml" }
|
71
|
-
|
72
|
-
put(url, entry.to_s, headers)
|
73
|
-
end
|
74
|
-
end
|
75
54
|
end
|
data/lib/atom/entry.rb
CHANGED
@@ -89,6 +89,19 @@ module Atom
|
|
89
89
|
end
|
90
90
|
end
|
91
91
|
|
92
|
+
# the @href of an entry's link[@rel="edit"]
|
93
|
+
def edit_url
|
94
|
+
begin
|
95
|
+
edit_link = self.links.find do |link|
|
96
|
+
link["rel"] == "edit"
|
97
|
+
end
|
98
|
+
|
99
|
+
edit_link["href"]
|
100
|
+
rescue
|
101
|
+
nil
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
92
105
|
# XXX this needs a test suite before it can be trusted.
|
93
106
|
=begin
|
94
107
|
# tests the entry's validity
|
data/lib/atom/feed.rb
CHANGED
@@ -5,8 +5,6 @@ require "atom/entry"
|
|
5
5
|
require "atom/http"
|
6
6
|
|
7
7
|
module Atom
|
8
|
-
class HTTPException < RuntimeError # :nodoc:
|
9
|
-
end
|
10
8
|
class FeedGone < RuntimeError # :nodoc:
|
11
9
|
end
|
12
10
|
|
@@ -163,11 +161,17 @@ module Atom
|
|
163
161
|
raise(RuntimeError, "can't fetch without a uri.") unless @uri
|
164
162
|
|
165
163
|
headers = {}
|
164
|
+
headers["Accept"] = "application/atom+xml"
|
166
165
|
headers["If-None-Match"] = @etag if @etag
|
167
166
|
headers["If-Modified-Since"] = @last_modified if @last_modified
|
168
167
|
|
169
168
|
res = @http.get(@uri, headers)
|
170
169
|
|
170
|
+
# we'll be forgiving about feed content types.
|
171
|
+
res.validate_content_type(["application/atom+xml",
|
172
|
+
"application/xml",
|
173
|
+
"text/xml"])
|
174
|
+
|
171
175
|
if res.code == "304"
|
172
176
|
# we're already all up to date
|
173
177
|
return self
|
@@ -177,11 +181,6 @@ module Atom
|
|
177
181
|
raise Atom::HTTPException, "Unexpected HTTP response code: #{res.code}"
|
178
182
|
end
|
179
183
|
|
180
|
-
media_type = res.content_type.split(";").first
|
181
|
-
unless ["application/atom+xml", "application/xml", "text/xml"].member? media_type
|
182
|
-
raise Atom::HTTPException, "An atom:feed shouldn't have Content-Type: #{res.content_type}"
|
183
|
-
end
|
184
|
-
|
185
184
|
@etag = res["Etag"] if res["Etag"]
|
186
185
|
@last_modified = res["Last-Modified"] if res["Last-Modified"]
|
187
186
|
|
data/lib/atom/http.rb
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
require "net/http"
|
2
|
-
require
|
2
|
+
require "net/https"
|
3
|
+
require "uri"
|
3
4
|
|
4
5
|
require "sha1"
|
5
6
|
require "md5"
|
@@ -13,7 +14,7 @@ class String # :nodoc:
|
|
13
14
|
end
|
14
15
|
|
15
16
|
module Atom
|
16
|
-
UA = "atom-tools 0.9.
|
17
|
+
UA = "atom-tools 0.9.2"
|
17
18
|
|
18
19
|
module DigestAuth
|
19
20
|
CNONCE = Digest::MD5.new("%x" % (Time.now.to_i + rand(65535))).hexdigest
|
@@ -92,7 +93,11 @@ module Atom
|
|
92
93
|
end
|
93
94
|
end
|
94
95
|
|
95
|
-
class
|
96
|
+
class HTTPException < RuntimeError # :nodoc:
|
97
|
+
end
|
98
|
+
class Unauthorized < Atom::HTTPException # :nodoc:
|
99
|
+
end
|
100
|
+
class WrongMimetype < Atom::HTTPException # :nodoc:
|
96
101
|
end
|
97
102
|
|
98
103
|
# An object which handles the details of HTTP - particularly
|
@@ -108,10 +113,29 @@ module Atom
|
|
108
113
|
# used by the default #when_auth
|
109
114
|
attr_accessor :user, :pass
|
110
115
|
|
111
|
-
#
|
112
|
-
|
116
|
+
# the token used for Google's AuthSub authentication
|
117
|
+
attr_accessor :token
|
118
|
+
|
119
|
+
# when set to :basic, :wsse or :authsub, this will send an
|
120
|
+
# Authentication header with every request instead of waiting for a
|
121
|
+
# challenge from the server.
|
122
|
+
#
|
123
|
+
# be careful; always_auth :basic will send your username and
|
124
|
+
# password in plain text to every URL this object requests.
|
125
|
+
#
|
126
|
+
# :digest won't work, since Digest authentication requires an
|
127
|
+
# initial challenge to generate a response
|
128
|
+
#
|
129
|
+
# defaults to nil
|
113
130
|
attr_accessor :always_auth
|
114
131
|
|
132
|
+
# automatically handle redirects, even for POST/PUT/DELETE requests?
|
133
|
+
#
|
134
|
+
# defaults to false, which will transparently redirect GET requests
|
135
|
+
# but return a Net::HTTPRedirection object when the server
|
136
|
+
# indicates to redirect a POST/PUT/DELETE
|
137
|
+
attr_accessor :allow_all_redirects
|
138
|
+
|
115
139
|
def initialize # :nodoc:
|
116
140
|
@get_auth_details = lambda do |abs_url, realm|
|
117
141
|
if @user and @pass
|
@@ -158,6 +182,29 @@ module Atom
|
|
158
182
|
@get_auth_details = block
|
159
183
|
end
|
160
184
|
|
185
|
+
# GET a URL and turn it into an Atom::Entry
|
186
|
+
def get_atom_entry(url)
|
187
|
+
res = get(url, "Accept" => "application/atom+xml")
|
188
|
+
|
189
|
+
# be picky for atom:entrys
|
190
|
+
res.validate_content_type( [ "application/atom+xml" ] )
|
191
|
+
|
192
|
+
# XXX handle other HTTP codes
|
193
|
+
if res.code != "200"
|
194
|
+
raise Atom::HTTPException, "expected Atom::Entry, didn't get it"
|
195
|
+
end
|
196
|
+
|
197
|
+
Atom::Entry.parse(res.body, url)
|
198
|
+
end
|
199
|
+
|
200
|
+
# PUT an Atom::Entry to a URL
|
201
|
+
def put_atom_entry(entry, url = entry.edit_url)
|
202
|
+
raise "Cowardly refusing to PUT a non-Atom::Entry (#{entry.class})" unless entry.is_a? Atom::Entry
|
203
|
+
headers = {"Content-Type" => "application/atom+xml" }
|
204
|
+
|
205
|
+
put(url, entry.to_s, headers)
|
206
|
+
end
|
207
|
+
|
161
208
|
private
|
162
209
|
# parses plain quoted-strings
|
163
210
|
def parse_quoted_wwwauth param_string
|
@@ -177,22 +224,25 @@ module Atom
|
|
177
224
|
req.basic_auth user, pass
|
178
225
|
end
|
179
226
|
|
180
|
-
# WSSE authentication
|
227
|
+
# WSSE authentication
|
228
|
+
# <http://www.xml.com/pub/a/2003/12/17/dive.html>
|
181
229
|
def wsse_authenticate(req, url, params = {})
|
182
|
-
# from <http://www.koders.com/ruby/fidFB0C7F9A0F36CB0F30B2280BDDC4F43FF1FA4589.aspx?s=ruby+cgi>.
|
183
|
-
# (thanks midore!)
|
184
230
|
user, pass = username_and_password_for_realm(url, params["realm"])
|
185
231
|
|
186
232
|
nonce = Array.new(10){ rand(0x100000000) }.pack('I*')
|
187
|
-
|
188
|
-
|
233
|
+
nonce_b64 = [nonce].pack("m").chomp
|
234
|
+
|
235
|
+
now = Time.now.iso8601
|
189
236
|
digest = [Digest::SHA1.digest(nonce + now + pass)].pack("m").chomp
|
190
|
-
|
191
|
-
|
192
|
-
req['X-WSSE'] = credentials
|
237
|
+
|
238
|
+
req['X-WSSE'] = %Q<UsernameToken Username="#{user}", PasswordDigest="#{digest}", Nonce="#{nonce_b64}", Created="#{now}">
|
193
239
|
req["Authorization"] = 'WSSE profile="UsernameToken"'
|
194
240
|
end
|
195
241
|
|
242
|
+
def authsub_authenticate req, url
|
243
|
+
req["Authorization"] = %{AuthSub token="#{@token}"}
|
244
|
+
end
|
245
|
+
|
196
246
|
def username_and_password_for_realm(url, realm)
|
197
247
|
abs_url = (url + "/").to_s
|
198
248
|
user, pass = @get_auth_details.call(abs_url, realm)
|
@@ -205,7 +255,7 @@ module Atom
|
|
205
255
|
end
|
206
256
|
|
207
257
|
# performs a generic HTTP request.
|
208
|
-
def http_request(url_s, method, body = nil, init_headers = {}, www_authenticate = nil)
|
258
|
+
def http_request(url_s, method, body = nil, init_headers = {}, www_authenticate = nil, redirect_limit = 5)
|
209
259
|
req, url = new_request(url_s, method, init_headers)
|
210
260
|
|
211
261
|
# two reasons to authenticate;
|
@@ -217,23 +267,38 @@ module Atom
|
|
217
267
|
auth_type = $~[1]
|
218
268
|
self.send("#{auth_type.downcase}_authenticate", req, url, param_string)
|
219
269
|
end
|
220
|
-
|
221
|
-
res = Net::HTTP.start(url.host, url.port) { |h| h.request(req, body) }
|
222
270
|
|
223
|
-
|
271
|
+
http_obj = Net::HTTP.new(url.host, url.port)
|
272
|
+
http_obj.use_ssl = true if url.scheme == "https"
|
273
|
+
|
274
|
+
res = http_obj.start do |h|
|
275
|
+
h.request(req, body)
|
276
|
+
end
|
277
|
+
|
278
|
+
case res
|
279
|
+
when Net::HTTPUnauthorized
|
224
280
|
if @always_auth or www_authenticate # XXX and not stale (Digest only)
|
225
281
|
# we've tried the credentials you gave us once and failed
|
226
|
-
raise Unauthorized, "Your
|
282
|
+
raise Unauthorized, "Your authorization was rejected"
|
227
283
|
else
|
228
284
|
# once more, with authentication
|
229
285
|
res = http_request(url_s, method, body, init_headers, res["WWW-Authenticate"])
|
230
286
|
|
231
287
|
if res.kind_of? Net::HTTPUnauthorized
|
232
|
-
raise Unauthorized, "Your
|
288
|
+
raise Unauthorized, "Your authorization was rejected"
|
233
289
|
end
|
234
290
|
end
|
291
|
+
when Net::HTTPRedirection
|
292
|
+
if res["Location"] and (allow_all_redirects or [Net::HTTP::Get, Net::HTTP::Head].member? method)
|
293
|
+
raise HTTPException, "Too many redirects" if redirect_limit.zero?
|
294
|
+
|
295
|
+
res = http_request res["Location"], method, body, init_headers, nil, (redirect_limit - 1)
|
296
|
+
end
|
235
297
|
end
|
236
298
|
|
299
|
+
# a bit of added convenience
|
300
|
+
res.extend Atom::HTTPResponse
|
301
|
+
|
237
302
|
res
|
238
303
|
end
|
239
304
|
|
@@ -248,4 +313,17 @@ module Atom
|
|
248
313
|
[method.new(rel, headers), url]
|
249
314
|
end
|
250
315
|
end
|
316
|
+
|
317
|
+
module HTTPResponse
|
318
|
+
# this should probably support ranges (eg. text/*)
|
319
|
+
def validate_content_type( valid )
|
320
|
+
raise Atom::HTTPException, "HTTP response contains no Content-Type!" unless self.content_type
|
321
|
+
|
322
|
+
media_type = self.content_type.split(";").first
|
323
|
+
|
324
|
+
unless valid.member? media_type.downcase
|
325
|
+
raise Atom::WrongMimetype, "unexpected response Content-Type: #{media_type.inspect}. should be one of: #{valid.inspect}"
|
326
|
+
end
|
327
|
+
end
|
328
|
+
end
|
251
329
|
end
|