Show More
@@ -1,450 +1,450 | |||||
1 | #!/usr/bin/env python |
|
1 | #!/usr/bin/env python | |
2 | # |
|
2 | # | |
3 | # check-code - a style and portability checker for Mercurial |
|
3 | # check-code - a style and portability checker for Mercurial | |
4 | # |
|
4 | # | |
5 | # Copyright 2010 Matt Mackall <mpm@selenic.com> |
|
5 | # Copyright 2010 Matt Mackall <mpm@selenic.com> | |
6 | # |
|
6 | # | |
7 | # This software may be used and distributed according to the terms of the |
|
7 | # This software may be used and distributed according to the terms of the | |
8 | # GNU General Public License version 2 or any later version. |
|
8 | # GNU General Public License version 2 or any later version. | |
9 |
|
9 | |||
10 | import re, glob, os, sys |
|
10 | import re, glob, os, sys | |
11 | import keyword |
|
11 | import keyword | |
12 | import optparse |
|
12 | import optparse | |
13 |
|
13 | |||
14 | def repquote(m): |
|
14 | def repquote(m): | |
15 | t = re.sub(r"\w", "x", m.group('text')) |
|
15 | t = re.sub(r"\w", "x", m.group('text')) | |
16 | t = re.sub(r"[^\s\nx]", "o", t) |
|
16 | t = re.sub(r"[^\s\nx]", "o", t) | |
17 | return m.group('quote') + t + m.group('quote') |
|
17 | return m.group('quote') + t + m.group('quote') | |
18 |
|
18 | |||
19 | def reppython(m): |
|
19 | def reppython(m): | |
20 | comment = m.group('comment') |
|
20 | comment = m.group('comment') | |
21 | if comment: |
|
21 | if comment: | |
22 | return "#" * len(comment) |
|
22 | return "#" * len(comment) | |
23 | return repquote(m) |
|
23 | return repquote(m) | |
24 |
|
24 | |||
25 | def repcomment(m): |
|
25 | def repcomment(m): | |
26 | return m.group(1) + "#" * len(m.group(2)) |
|
26 | return m.group(1) + "#" * len(m.group(2)) | |
27 |
|
27 | |||
28 | def repccomment(m): |
|
28 | def repccomment(m): | |
29 | t = re.sub(r"((?<=\n) )|\S", "x", m.group(2)) |
|
29 | t = re.sub(r"((?<=\n) )|\S", "x", m.group(2)) | |
30 | return m.group(1) + t + "*/" |
|
30 | return m.group(1) + t + "*/" | |
31 |
|
31 | |||
32 | def repcallspaces(m): |
|
32 | def repcallspaces(m): | |
33 | t = re.sub(r"\n\s+", "\n", m.group(2)) |
|
33 | t = re.sub(r"\n\s+", "\n", m.group(2)) | |
34 | return m.group(1) + t |
|
34 | return m.group(1) + t | |
35 |
|
35 | |||
36 | def repinclude(m): |
|
36 | def repinclude(m): | |
37 | return m.group(1) + "<foo>" |
|
37 | return m.group(1) + "<foo>" | |
38 |
|
38 | |||
39 | def rephere(m): |
|
39 | def rephere(m): | |
40 | t = re.sub(r"\S", "x", m.group(2)) |
|
40 | t = re.sub(r"\S", "x", m.group(2)) | |
41 | return m.group(1) + t |
|
41 | return m.group(1) + t | |
42 |
|
42 | |||
43 |
|
43 | |||
44 | testpats = [ |
|
44 | testpats = [ | |
45 | [ |
|
45 | [ | |
46 | (r'pushd|popd', "don't use 'pushd' or 'popd', use 'cd'"), |
|
46 | (r'pushd|popd', "don't use 'pushd' or 'popd', use 'cd'"), | |
47 | (r'\W\$?\(\([^\)\n]*\)\)', "don't use (()) or $(()), use 'expr'"), |
|
47 | (r'\W\$?\(\([^\)\n]*\)\)', "don't use (()) or $(()), use 'expr'"), | |
48 | (r'grep.*-q', "don't use 'grep -q', redirect to /dev/null"), |
|
48 | (r'grep.*-q', "don't use 'grep -q', redirect to /dev/null"), | |
49 | (r'sed.*-i', "don't use 'sed -i', use a temporary file"), |
|
49 | (r'sed.*-i', "don't use 'sed -i', use a temporary file"), | |
50 | (r'\becho\b.*\\n', "don't use 'echo \\n', use printf"), |
|
50 | (r'\becho\b.*\\n', "don't use 'echo \\n', use printf"), | |
51 | (r'echo -n', "don't use 'echo -n', use printf"), |
|
51 | (r'echo -n', "don't use 'echo -n', use printf"), | |
52 | (r'(^| )wc[^|]*$\n(?!.*\(re\))', "filter wc output"), |
|
52 | (r'(^| )wc[^|]*$\n(?!.*\(re\))', "filter wc output"), | |
53 | (r'head -c', "don't use 'head -c', use 'dd'"), |
|
53 | (r'head -c', "don't use 'head -c', use 'dd'"), | |
54 | (r'sha1sum', "don't use sha1sum, use $TESTDIR/md5sum.py"), |
|
54 | (r'sha1sum', "don't use sha1sum, use $TESTDIR/md5sum.py"), | |
55 | (r'ls.*-\w*R', "don't use 'ls -R', use 'find'"), |
|
55 | (r'ls.*-\w*R', "don't use 'ls -R', use 'find'"), | |
56 | (r'printf.*\\([1-9]|0\d)', "don't use 'printf \NNN', use Python"), |
|
56 | (r'printf.*\\([1-9]|0\d)', "don't use 'printf \NNN', use Python"), | |
57 | (r'printf.*\\x', "don't use printf \\x, use Python"), |
|
57 | (r'printf.*\\x', "don't use printf \\x, use Python"), | |
58 | (r'\$\(.*\)', "don't use $(expr), use `expr`"), |
|
58 | (r'\$\(.*\)', "don't use $(expr), use `expr`"), | |
59 | (r'rm -rf \*', "don't use naked rm -rf, target a directory"), |
|
59 | (r'rm -rf \*', "don't use naked rm -rf, target a directory"), | |
60 | (r'(^|\|\s*)grep (-\w\s+)*[^|]*[(|]\w', |
|
60 | (r'(^|\|\s*)grep (-\w\s+)*[^|]*[(|]\w', | |
61 | "use egrep for extended grep syntax"), |
|
61 | "use egrep for extended grep syntax"), | |
62 | (r'/bin/', "don't use explicit paths for tools"), |
|
62 | (r'/bin/', "don't use explicit paths for tools"), | |
63 | (r'[^\n]\Z', "no trailing newline"), |
|
63 | (r'[^\n]\Z', "no trailing newline"), | |
64 | (r'export.*=', "don't export and assign at once"), |
|
64 | (r'export.*=', "don't export and assign at once"), | |
65 | (r'^source\b', "don't use 'source', use '.'"), |
|
65 | (r'^source\b', "don't use 'source', use '.'"), | |
66 | (r'touch -d', "don't use 'touch -d', use 'touch -t' instead"), |
|
66 | (r'touch -d', "don't use 'touch -d', use 'touch -t' instead"), | |
67 | (r'ls +[^|\n-]+ +-', "options to 'ls' must come before filenames"), |
|
67 | (r'ls +[^|\n-]+ +-', "options to 'ls' must come before filenames"), | |
68 | (r'[^>\n]>\s*\$HGRCPATH', "don't overwrite $HGRCPATH, append to it"), |
|
68 | (r'[^>\n]>\s*\$HGRCPATH', "don't overwrite $HGRCPATH, append to it"), | |
69 | (r'^stop\(\)', "don't use 'stop' as a shell function name"), |
|
69 | (r'^stop\(\)', "don't use 'stop' as a shell function name"), | |
70 | (r'(\[|\btest\b).*-e ', "don't use 'test -e', use 'test -f'"), |
|
70 | (r'(\[|\btest\b).*-e ', "don't use 'test -e', use 'test -f'"), | |
71 | (r'^alias\b.*=', "don't use alias, use a function"), |
|
71 | (r'^alias\b.*=', "don't use alias, use a function"), | |
72 | (r'if\s*!', "don't use '!' to negate exit status"), |
|
72 | (r'if\s*!', "don't use '!' to negate exit status"), | |
73 | (r'/dev/u?random', "don't use entropy, use /dev/zero"), |
|
73 | (r'/dev/u?random', "don't use entropy, use /dev/zero"), | |
74 | (r'do\s*true;\s*done', "don't use true as loop body, use sleep 0"), |
|
74 | (r'do\s*true;\s*done', "don't use true as loop body, use sleep 0"), | |
75 | (r'^( *)\t', "don't use tabs to indent"), |
|
75 | (r'^( *)\t', "don't use tabs to indent"), | |
76 | ], |
|
76 | ], | |
77 | # warnings |
|
77 | # warnings | |
78 | [ |
|
78 | [ | |
79 | (r'^function', "don't use 'function', use old style"), |
|
79 | (r'^function', "don't use 'function', use old style"), | |
80 | (r'^diff.*-\w*N', "don't use 'diff -N'"), |
|
80 | (r'^diff.*-\w*N', "don't use 'diff -N'"), | |
81 | (r'\$PWD', "don't use $PWD, use `pwd`"), |
|
81 | (r'\$PWD', "don't use $PWD, use `pwd`"), | |
82 | (r'^([^"\'\n]|("[^"\n]*")|(\'[^\'\n]*\'))*\^', "^ must be quoted"), |
|
82 | (r'^([^"\'\n]|("[^"\n]*")|(\'[^\'\n]*\'))*\^', "^ must be quoted"), | |
83 | ] |
|
83 | ] | |
84 | ] |
|
84 | ] | |
85 |
|
85 | |||
86 | testfilters = [ |
|
86 | testfilters = [ | |
87 | (r"( *)(#([^\n]*\S)?)", repcomment), |
|
87 | (r"( *)(#([^\n]*\S)?)", repcomment), | |
88 | (r"<<(\S+)((.|\n)*?\n\1)", rephere), |
|
88 | (r"<<(\S+)((.|\n)*?\n\1)", rephere), | |
89 | ] |
|
89 | ] | |
90 |
|
90 | |||
91 | uprefix = r"^ \$ " |
|
91 | uprefix = r"^ \$ " | |
92 | utestpats = [ |
|
92 | utestpats = [ | |
93 | [ |
|
93 | [ | |
94 | (r'^(\S.*|| [$>] .*)[ \t]\n', "trailing whitespace on non-output"), |
|
94 | (r'^(\S.*|| [$>] .*)[ \t]\n', "trailing whitespace on non-output"), | |
95 | (uprefix + r'.*\|\s*sed[^|>\n]*\n', |
|
95 | (uprefix + r'.*\|\s*sed[^|>\n]*\n', | |
96 | "use regex test output patterns instead of sed"), |
|
96 | "use regex test output patterns instead of sed"), | |
97 | (uprefix + r'(true|exit 0)', "explicit zero exit unnecessary"), |
|
97 | (uprefix + r'(true|exit 0)', "explicit zero exit unnecessary"), | |
98 | (uprefix + r'.*(?<!\[)\$\?', "explicit exit code checks unnecessary"), |
|
98 | (uprefix + r'.*(?<!\[)\$\?', "explicit exit code checks unnecessary"), | |
99 | (uprefix + r'.*\|\| echo.*(fail|error)', |
|
99 | (uprefix + r'.*\|\| echo.*(fail|error)', | |
100 | "explicit exit code checks unnecessary"), |
|
100 | "explicit exit code checks unnecessary"), | |
101 | (uprefix + r'set -e', "don't use set -e"), |
|
101 | (uprefix + r'set -e', "don't use set -e"), | |
102 | (uprefix + r'\s', "don't indent commands, use > for continued lines"), |
|
102 | (uprefix + r'\s', "don't indent commands, use > for continued lines"), | |
103 | (r'^ saved backup bundle to \$TESTTMP.*\.hg$', |
|
103 | (r'^ saved backup bundle to \$TESTTMP.*\.hg$', | |
104 | "use (glob) to match Windows paths too"), |
|
104 | "use (glob) to match Windows paths too"), | |
105 | ], |
|
105 | ], | |
106 | # warnings |
|
106 | # warnings | |
107 | [] |
|
107 | [] | |
108 | ] |
|
108 | ] | |
109 |
|
109 | |||
110 | for i in [0, 1]: |
|
110 | for i in [0, 1]: | |
111 | for p, m in testpats[i]: |
|
111 | for p, m in testpats[i]: | |
112 | if p.startswith(r'^'): |
|
112 | if p.startswith(r'^'): | |
113 | p = r"^ [$>] (%s)" % p[1:] |
|
113 | p = r"^ [$>] (%s)" % p[1:] | |
114 | else: |
|
114 | else: | |
115 | p = r"^ [$>] .*(%s)" % p |
|
115 | p = r"^ [$>] .*(%s)" % p | |
116 | utestpats[i].append((p, m)) |
|
116 | utestpats[i].append((p, m)) | |
117 |
|
117 | |||
118 | utestfilters = [ |
|
118 | utestfilters = [ | |
119 | (r"( *)(#([^\n]*\S)?)", repcomment), |
|
119 | (r"( *)(#([^\n]*\S)?)", repcomment), | |
120 | ] |
|
120 | ] | |
121 |
|
121 | |||
122 | pypats = [ |
|
122 | pypats = [ | |
123 | [ |
|
123 | [ | |
124 | (r'^\s*def\s*\w+\s*\(.*,\s*\(', |
|
124 | (r'^\s*def\s*\w+\s*\(.*,\s*\(', | |
125 | "tuple parameter unpacking not available in Python 3+"), |
|
125 | "tuple parameter unpacking not available in Python 3+"), | |
126 | (r'lambda\s*\(.*,.*\)', |
|
126 | (r'lambda\s*\(.*,.*\)', | |
127 | "tuple parameter unpacking not available in Python 3+"), |
|
127 | "tuple parameter unpacking not available in Python 3+"), | |
128 | (r'(?<!def)\s+(cmp)\(', "cmp is not available in Python 3+"), |
|
128 | (r'(?<!def)\s+(cmp)\(', "cmp is not available in Python 3+"), | |
129 | (r'\breduce\s*\(.*', "reduce is not available in Python 3+"), |
|
129 | (r'\breduce\s*\(.*', "reduce is not available in Python 3+"), | |
130 | (r'\.has_key\b', "dict.has_key is not available in Python 3+"), |
|
130 | (r'\.has_key\b', "dict.has_key is not available in Python 3+"), | |
131 | (r'^\s*\t', "don't use tabs"), |
|
131 | (r'^\s*\t', "don't use tabs"), | |
132 | (r'\S;\s*\n', "semicolon"), |
|
132 | (r'\S;\s*\n', "semicolon"), | |
133 | (r'[^_]_\("[^"]+"\s*%', "don't use % inside _()"), |
|
133 | (r'[^_]_\("[^"]+"\s*%', "don't use % inside _()"), | |
134 | (r"[^_]_\('[^']+'\s*%", "don't use % inside _()"), |
|
134 | (r"[^_]_\('[^']+'\s*%", "don't use % inside _()"), | |
135 | (r'\w,\w', "missing whitespace after ,"), |
|
135 | (r'\w,\w', "missing whitespace after ,"), | |
136 | (r'\w[+/*\-<>]\w', "missing whitespace in expression"), |
|
136 | (r'\w[+/*\-<>]\w', "missing whitespace in expression"), | |
137 | (r'^\s+\w+=\w+[^,)\n]$', "missing whitespace in assignment"), |
|
137 | (r'^\s+\w+=\w+[^,)\n]$', "missing whitespace in assignment"), | |
138 | (r'(\s+)try:\n((?:\n|\1\s.*\n)+?)\1except.*?:\n' |
|
138 | (r'(\s+)try:\n((?:\n|\1\s.*\n)+?)\1except.*?:\n' | |
139 | r'((?:\n|\1\s.*\n)+?)\1finally:', 'no try/except/finally in Py2.4'), |
|
139 | r'((?:\n|\1\s.*\n)+?)\1finally:', 'no try/except/finally in Python 2.4'), | |
140 | (r'.{81}', "line too long"), |
|
140 | (r'.{81}', "line too long"), | |
141 | (r' x+[xo][\'"]\n\s+[\'"]x', 'string join across lines with no space'), |
|
141 | (r' x+[xo][\'"]\n\s+[\'"]x', 'string join across lines with no space'), | |
142 | (r'[^\n]\Z', "no trailing newline"), |
|
142 | (r'[^\n]\Z', "no trailing newline"), | |
143 | (r'(\S[ \t]+|^[ \t]+)\n', "trailing whitespace"), |
|
143 | (r'(\S[ \t]+|^[ \t]+)\n', "trailing whitespace"), | |
144 | # (r'^\s+[^_ \n][^_. \n]+_[^_\n]+\s*=', |
|
144 | # (r'^\s+[^_ \n][^_. \n]+_[^_\n]+\s*=', | |
145 | # "don't use underbars in identifiers"), |
|
145 | # "don't use underbars in identifiers"), | |
146 | (r'^\s+(self\.)?[A-za-z][a-z0-9]+[A-Z]\w* = ', |
|
146 | (r'^\s+(self\.)?[A-za-z][a-z0-9]+[A-Z]\w* = ', | |
147 | "don't use camelcase in identifiers"), |
|
147 | "don't use camelcase in identifiers"), | |
148 | (r'^\s*(if|while|def|class|except|try)\s[^[\n]*:\s*[^\\n]#\s]+', |
|
148 | (r'^\s*(if|while|def|class|except|try)\s[^[\n]*:\s*[^\\n]#\s]+', | |
149 | "linebreak after :"), |
|
149 | "linebreak after :"), | |
150 | (r'class\s[^( \n]+:', "old-style class, use class foo(object)"), |
|
150 | (r'class\s[^( \n]+:', "old-style class, use class foo(object)"), | |
151 | (r'class\s[^( \n]+\(\):', |
|
151 | (r'class\s[^( \n]+\(\):', | |
152 | "class foo() not available in Python 2.4, use class foo(object)"), |
|
152 | "class foo() not available in Python 2.4, use class foo(object)"), | |
153 | (r'\b(%s)\(' % '|'.join(keyword.kwlist), |
|
153 | (r'\b(%s)\(' % '|'.join(keyword.kwlist), | |
154 | "Python keyword is not a function"), |
|
154 | "Python keyword is not a function"), | |
155 | (r',]', "unneeded trailing ',' in list"), |
|
155 | (r',]', "unneeded trailing ',' in list"), | |
156 | # (r'class\s[A-Z][^\(]*\((?!Exception)', |
|
156 | # (r'class\s[A-Z][^\(]*\((?!Exception)', | |
157 | # "don't capitalize non-exception classes"), |
|
157 | # "don't capitalize non-exception classes"), | |
158 | # (r'in range\(', "use xrange"), |
|
158 | # (r'in range\(', "use xrange"), | |
159 | # (r'^\s*print\s+', "avoid using print in core and extensions"), |
|
159 | # (r'^\s*print\s+', "avoid using print in core and extensions"), | |
160 | (r'[\x80-\xff]', "non-ASCII character literal"), |
|
160 | (r'[\x80-\xff]', "non-ASCII character literal"), | |
161 | (r'("\')\.format\(', "str.format() not available in Python 2.4"), |
|
161 | (r'("\')\.format\(', "str.format() not available in Python 2.4"), | |
162 | (r'^\s*with\s+', "with not available in Python 2.4"), |
|
162 | (r'^\s*with\s+', "with not available in Python 2.4"), | |
163 | (r'\.isdisjoint\(', "set.isdisjoint not available in Python 2.4"), |
|
163 | (r'\.isdisjoint\(', "set.isdisjoint not available in Python 2.4"), | |
164 | (r'^\s*except.* as .*:', "except as not available in Python 2.4"), |
|
164 | (r'^\s*except.* as .*:', "except as not available in Python 2.4"), | |
165 | (r'^\s*os\.path\.relpath', "relpath not available in Python 2.4"), |
|
165 | (r'^\s*os\.path\.relpath', "relpath not available in Python 2.4"), | |
166 | (r'(?<!def)\s+(any|all|format)\(', |
|
166 | (r'(?<!def)\s+(any|all|format)\(', | |
167 | "any/all/format not available in Python 2.4"), |
|
167 | "any/all/format not available in Python 2.4"), | |
168 | (r'(?<!def)\s+(callable)\(', |
|
168 | (r'(?<!def)\s+(callable)\(', | |
169 | "callable not available in Python 3, use getattr(f, '__call__', None)"), |
|
169 | "callable not available in Python 3, use getattr(f, '__call__', None)"), | |
170 | (r'if\s.*\selse', "if ... else form not available in Python 2.4"), |
|
170 | (r'if\s.*\selse', "if ... else form not available in Python 2.4"), | |
171 | (r'^\s*(%s)\s\s' % '|'.join(keyword.kwlist), |
|
171 | (r'^\s*(%s)\s\s' % '|'.join(keyword.kwlist), | |
172 | "gratuitous whitespace after Python keyword"), |
|
172 | "gratuitous whitespace after Python keyword"), | |
173 | (r'([\(\[][ \t]\S)|(\S[ \t][\)\]])', "gratuitous whitespace in () or []"), |
|
173 | (r'([\(\[][ \t]\S)|(\S[ \t][\)\]])', "gratuitous whitespace in () or []"), | |
174 | # (r'\s\s=', "gratuitous whitespace before ="), |
|
174 | # (r'\s\s=', "gratuitous whitespace before ="), | |
175 | (r'[^>< ](\+=|-=|!=|<>|<=|>=|<<=|>>=|%=)\S', |
|
175 | (r'[^>< ](\+=|-=|!=|<>|<=|>=|<<=|>>=|%=)\S', | |
176 | "missing whitespace around operator"), |
|
176 | "missing whitespace around operator"), | |
177 | (r'[^>< ](\+=|-=|!=|<>|<=|>=|<<=|>>=|%=)\s', |
|
177 | (r'[^>< ](\+=|-=|!=|<>|<=|>=|<<=|>>=|%=)\s', | |
178 | "missing whitespace around operator"), |
|
178 | "missing whitespace around operator"), | |
179 | (r'\s(\+=|-=|!=|<>|<=|>=|<<=|>>=|%=)\S', |
|
179 | (r'\s(\+=|-=|!=|<>|<=|>=|<<=|>>=|%=)\S', | |
180 | "missing whitespace around operator"), |
|
180 | "missing whitespace around operator"), | |
181 | (r'[^^+=*/!<>&| %-](\s=|=\s)[^= ]', |
|
181 | (r'[^^+=*/!<>&| %-](\s=|=\s)[^= ]', | |
182 | "wrong whitespace around ="), |
|
182 | "wrong whitespace around ="), | |
183 | (r'raise Exception', "don't raise generic exceptions"), |
|
183 | (r'raise Exception', "don't raise generic exceptions"), | |
184 | (r' is\s+(not\s+)?["\'0-9-]', "object comparison with literal"), |
|
184 | (r' is\s+(not\s+)?["\'0-9-]', "object comparison with literal"), | |
185 | (r' [=!]=\s+(True|False|None)', |
|
185 | (r' [=!]=\s+(True|False|None)', | |
186 | "comparison with singleton, use 'is' or 'is not' instead"), |
|
186 | "comparison with singleton, use 'is' or 'is not' instead"), | |
187 | (r'^\s*(while|if) [01]:', |
|
187 | (r'^\s*(while|if) [01]:', | |
188 | "use True/False for constant Boolean expression"), |
|
188 | "use True/False for constant Boolean expression"), | |
189 | (r'(?:(?<!def)\s+|\()hasattr', |
|
189 | (r'(?:(?<!def)\s+|\()hasattr', | |
190 | 'hasattr(foo, bar) is broken, use util.safehasattr(foo, bar) instead'), |
|
190 | 'hasattr(foo, bar) is broken, use util.safehasattr(foo, bar) instead'), | |
191 | (r'opener\([^)]*\).read\(', |
|
191 | (r'opener\([^)]*\).read\(', | |
192 | "use opener.read() instead"), |
|
192 | "use opener.read() instead"), | |
193 | (r'BaseException', 'not in Py2.4, use Exception'), |
|
193 | (r'BaseException', 'not in Python 2.4, use Exception'), | |
194 | (r'os\.path\.relpath', 'os.path.relpath is not in Py2.5'), |
|
194 | (r'os\.path\.relpath', 'os.path.relpath is not in Python 2.5'), | |
195 | (r'opener\([^)]*\).write\(', |
|
195 | (r'opener\([^)]*\).write\(', | |
196 | "use opener.write() instead"), |
|
196 | "use opener.write() instead"), | |
197 | (r'[\s\(](open|file)\([^)]*\)\.read\(', |
|
197 | (r'[\s\(](open|file)\([^)]*\)\.read\(', | |
198 | "use util.readfile() instead"), |
|
198 | "use util.readfile() instead"), | |
199 | (r'[\s\(](open|file)\([^)]*\)\.write\(', |
|
199 | (r'[\s\(](open|file)\([^)]*\)\.write\(', | |
200 | "use util.readfile() instead"), |
|
200 | "use util.readfile() instead"), | |
201 | (r'^[\s\(]*(open(er)?|file)\([^)]*\)', |
|
201 | (r'^[\s\(]*(open(er)?|file)\([^)]*\)', | |
202 | "always assign an opened file to a variable, and close it afterwards"), |
|
202 | "always assign an opened file to a variable, and close it afterwards"), | |
203 | (r'[\s\(](open|file)\([^)]*\)\.', |
|
203 | (r'[\s\(](open|file)\([^)]*\)\.', | |
204 | "always assign an opened file to a variable, and close it afterwards"), |
|
204 | "always assign an opened file to a variable, and close it afterwards"), | |
205 | (r'(?i)descendent', "the proper spelling is descendAnt"), |
|
205 | (r'(?i)descendent', "the proper spelling is descendAnt"), | |
206 | (r'\.debug\(\_', "don't mark debug messages for translation"), |
|
206 | (r'\.debug\(\_', "don't mark debug messages for translation"), | |
207 | (r'\.strip\(\)\.split\(\)', "no need to strip before splitting"), |
|
207 | (r'\.strip\(\)\.split\(\)', "no need to strip before splitting"), | |
208 | (r'^\s*except\s*:', "warning: naked except clause", r'#.*re-raises'), |
|
208 | (r'^\s*except\s*:', "warning: naked except clause", r'#.*re-raises'), | |
209 | (r':\n( )*( ){1,3}[^ ]', "must indent 4 spaces"), |
|
209 | (r':\n( )*( ){1,3}[^ ]', "must indent 4 spaces"), | |
210 | ], |
|
210 | ], | |
211 | # warnings |
|
211 | # warnings | |
212 | [ |
|
212 | [ | |
213 | (r'ui\.(status|progress|write|note|warn)\([\'\"]x', |
|
213 | (r'ui\.(status|progress|write|note|warn)\([\'\"]x', | |
214 | "warning: unwrapped ui message"), |
|
214 | "warning: unwrapped ui message"), | |
215 | ] |
|
215 | ] | |
216 | ] |
|
216 | ] | |
217 |
|
217 | |||
218 | pyfilters = [ |
|
218 | pyfilters = [ | |
219 | (r"""(?msx)(?P<comment>\#.*?$)| |
|
219 | (r"""(?msx)(?P<comment>\#.*?$)| | |
220 | ((?P<quote>('''|\"\"\"|(?<!')'(?!')|(?<!")"(?!"))) |
|
220 | ((?P<quote>('''|\"\"\"|(?<!')'(?!')|(?<!")"(?!"))) | |
221 | (?P<text>(([^\\]|\\.)*?)) |
|
221 | (?P<text>(([^\\]|\\.)*?)) | |
222 | (?P=quote))""", reppython), |
|
222 | (?P=quote))""", reppython), | |
223 | ] |
|
223 | ] | |
224 |
|
224 | |||
225 | cpats = [ |
|
225 | cpats = [ | |
226 | [ |
|
226 | [ | |
227 | (r'//', "don't use //-style comments"), |
|
227 | (r'//', "don't use //-style comments"), | |
228 | (r'^ ', "don't use spaces to indent"), |
|
228 | (r'^ ', "don't use spaces to indent"), | |
229 | (r'\S\t', "don't use tabs except for indent"), |
|
229 | (r'\S\t', "don't use tabs except for indent"), | |
230 | (r'(\S[ \t]+|^[ \t]+)\n', "trailing whitespace"), |
|
230 | (r'(\S[ \t]+|^[ \t]+)\n', "trailing whitespace"), | |
231 | (r'.{81}', "line too long"), |
|
231 | (r'.{81}', "line too long"), | |
232 | (r'(while|if|do|for)\(', "use space after while/if/do/for"), |
|
232 | (r'(while|if|do|for)\(', "use space after while/if/do/for"), | |
233 | (r'return\(', "return is not a function"), |
|
233 | (r'return\(', "return is not a function"), | |
234 | (r' ;', "no space before ;"), |
|
234 | (r' ;', "no space before ;"), | |
235 | (r'\w+\* \w+', "use int *foo, not int* foo"), |
|
235 | (r'\w+\* \w+', "use int *foo, not int* foo"), | |
236 | (r'\([^\)]+\) \w+', "use (int)foo, not (int) foo"), |
|
236 | (r'\([^\)]+\) \w+', "use (int)foo, not (int) foo"), | |
237 | (r'\w+ (\+\+|--)', "use foo++, not foo ++"), |
|
237 | (r'\w+ (\+\+|--)', "use foo++, not foo ++"), | |
238 | (r'\w,\w', "missing whitespace after ,"), |
|
238 | (r'\w,\w', "missing whitespace after ,"), | |
239 | (r'^[^#]\w[+/*]\w', "missing whitespace in expression"), |
|
239 | (r'^[^#]\w[+/*]\w', "missing whitespace in expression"), | |
240 | (r'^#\s+\w', "use #foo, not # foo"), |
|
240 | (r'^#\s+\w', "use #foo, not # foo"), | |
241 | (r'[^\n]\Z', "no trailing newline"), |
|
241 | (r'[^\n]\Z', "no trailing newline"), | |
242 | (r'^\s*#import\b', "use only #include in standard C code"), |
|
242 | (r'^\s*#import\b', "use only #include in standard C code"), | |
243 | ], |
|
243 | ], | |
244 | # warnings |
|
244 | # warnings | |
245 | [] |
|
245 | [] | |
246 | ] |
|
246 | ] | |
247 |
|
247 | |||
248 | cfilters = [ |
|
248 | cfilters = [ | |
249 | (r'(/\*)(((\*(?!/))|[^*])*)\*/', repccomment), |
|
249 | (r'(/\*)(((\*(?!/))|[^*])*)\*/', repccomment), | |
250 | (r'''(?P<quote>(?<!")")(?P<text>([^"]|\\")+)"(?!")''', repquote), |
|
250 | (r'''(?P<quote>(?<!")")(?P<text>([^"]|\\")+)"(?!")''', repquote), | |
251 | (r'''(#\s*include\s+<)([^>]+)>''', repinclude), |
|
251 | (r'''(#\s*include\s+<)([^>]+)>''', repinclude), | |
252 | (r'(\()([^)]+\))', repcallspaces), |
|
252 | (r'(\()([^)]+\))', repcallspaces), | |
253 | ] |
|
253 | ] | |
254 |
|
254 | |||
255 | inutilpats = [ |
|
255 | inutilpats = [ | |
256 | [ |
|
256 | [ | |
257 | (r'\bui\.', "don't use ui in util"), |
|
257 | (r'\bui\.', "don't use ui in util"), | |
258 | ], |
|
258 | ], | |
259 | # warnings |
|
259 | # warnings | |
260 | [] |
|
260 | [] | |
261 | ] |
|
261 | ] | |
262 |
|
262 | |||
263 | inrevlogpats = [ |
|
263 | inrevlogpats = [ | |
264 | [ |
|
264 | [ | |
265 | (r'\brepo\.', "don't use repo in revlog"), |
|
265 | (r'\brepo\.', "don't use repo in revlog"), | |
266 | ], |
|
266 | ], | |
267 | # warnings |
|
267 | # warnings | |
268 | [] |
|
268 | [] | |
269 | ] |
|
269 | ] | |
270 |
|
270 | |||
271 | checks = [ |
|
271 | checks = [ | |
272 | ('python', r'.*\.(py|cgi)$', pyfilters, pypats), |
|
272 | ('python', r'.*\.(py|cgi)$', pyfilters, pypats), | |
273 | ('test script', r'(.*/)?test-[^.~]*$', testfilters, testpats), |
|
273 | ('test script', r'(.*/)?test-[^.~]*$', testfilters, testpats), | |
274 | ('c', r'.*\.c$', cfilters, cpats), |
|
274 | ('c', r'.*\.c$', cfilters, cpats), | |
275 | ('unified test', r'.*\.t$', utestfilters, utestpats), |
|
275 | ('unified test', r'.*\.t$', utestfilters, utestpats), | |
276 | ('layering violation repo in revlog', r'mercurial/revlog\.py', pyfilters, |
|
276 | ('layering violation repo in revlog', r'mercurial/revlog\.py', pyfilters, | |
277 | inrevlogpats), |
|
277 | inrevlogpats), | |
278 | ('layering violation ui in util', r'mercurial/util\.py', pyfilters, |
|
278 | ('layering violation ui in util', r'mercurial/util\.py', pyfilters, | |
279 | inutilpats), |
|
279 | inutilpats), | |
280 | ] |
|
280 | ] | |
281 |
|
281 | |||
282 | class norepeatlogger(object): |
|
282 | class norepeatlogger(object): | |
283 | def __init__(self): |
|
283 | def __init__(self): | |
284 | self._lastseen = None |
|
284 | self._lastseen = None | |
285 |
|
285 | |||
286 | def log(self, fname, lineno, line, msg, blame): |
|
286 | def log(self, fname, lineno, line, msg, blame): | |
287 | """print error related a to given line of a given file. |
|
287 | """print error related a to given line of a given file. | |
288 |
|
288 | |||
289 | The faulty line will also be printed but only once in the case |
|
289 | The faulty line will also be printed but only once in the case | |
290 | of multiple errors. |
|
290 | of multiple errors. | |
291 |
|
291 | |||
292 | :fname: filename |
|
292 | :fname: filename | |
293 | :lineno: line number |
|
293 | :lineno: line number | |
294 | :line: actual content of the line |
|
294 | :line: actual content of the line | |
295 | :msg: error message |
|
295 | :msg: error message | |
296 | """ |
|
296 | """ | |
297 | msgid = fname, lineno, line |
|
297 | msgid = fname, lineno, line | |
298 | if msgid != self._lastseen: |
|
298 | if msgid != self._lastseen: | |
299 | if blame: |
|
299 | if blame: | |
300 | print "%s:%d (%s):" % (fname, lineno, blame) |
|
300 | print "%s:%d (%s):" % (fname, lineno, blame) | |
301 | else: |
|
301 | else: | |
302 | print "%s:%d:" % (fname, lineno) |
|
302 | print "%s:%d:" % (fname, lineno) | |
303 | print " > %s" % line |
|
303 | print " > %s" % line | |
304 | self._lastseen = msgid |
|
304 | self._lastseen = msgid | |
305 | print " " + msg |
|
305 | print " " + msg | |
306 |
|
306 | |||
307 | _defaultlogger = norepeatlogger() |
|
307 | _defaultlogger = norepeatlogger() | |
308 |
|
308 | |||
309 | def getblame(f): |
|
309 | def getblame(f): | |
310 | lines = [] |
|
310 | lines = [] | |
311 | for l in os.popen('hg annotate -un %s' % f): |
|
311 | for l in os.popen('hg annotate -un %s' % f): | |
312 | start, line = l.split(':', 1) |
|
312 | start, line = l.split(':', 1) | |
313 | user, rev = start.split() |
|
313 | user, rev = start.split() | |
314 | lines.append((line[1:-1], user, rev)) |
|
314 | lines.append((line[1:-1], user, rev)) | |
315 | return lines |
|
315 | return lines | |
316 |
|
316 | |||
317 | def checkfile(f, logfunc=_defaultlogger.log, maxerr=None, warnings=False, |
|
317 | def checkfile(f, logfunc=_defaultlogger.log, maxerr=None, warnings=False, | |
318 | blame=False, debug=False, lineno=True): |
|
318 | blame=False, debug=False, lineno=True): | |
319 | """checks style and portability of a given file |
|
319 | """checks style and portability of a given file | |
320 |
|
320 | |||
321 | :f: filepath |
|
321 | :f: filepath | |
322 | :logfunc: function used to report error |
|
322 | :logfunc: function used to report error | |
323 | logfunc(filename, linenumber, linecontent, errormessage) |
|
323 | logfunc(filename, linenumber, linecontent, errormessage) | |
324 | :maxerr: number of error to display before aborting. |
|
324 | :maxerr: number of error to display before aborting. | |
325 | Set to false (default) to report all errors |
|
325 | Set to false (default) to report all errors | |
326 |
|
326 | |||
327 | return True if no error is found, False otherwise. |
|
327 | return True if no error is found, False otherwise. | |
328 | """ |
|
328 | """ | |
329 | blamecache = None |
|
329 | blamecache = None | |
330 | result = True |
|
330 | result = True | |
331 | for name, match, filters, pats in checks: |
|
331 | for name, match, filters, pats in checks: | |
332 | if debug: |
|
332 | if debug: | |
333 | print name, f |
|
333 | print name, f | |
334 | fc = 0 |
|
334 | fc = 0 | |
335 | if not re.match(match, f): |
|
335 | if not re.match(match, f): | |
336 | if debug: |
|
336 | if debug: | |
337 | print "Skipping %s for %s it doesn't match %s" % ( |
|
337 | print "Skipping %s for %s it doesn't match %s" % ( | |
338 | name, match, f) |
|
338 | name, match, f) | |
339 | continue |
|
339 | continue | |
340 | fp = open(f) |
|
340 | fp = open(f) | |
341 | pre = post = fp.read() |
|
341 | pre = post = fp.read() | |
342 | fp.close() |
|
342 | fp.close() | |
343 | if "no-" + "check-code" in pre: |
|
343 | if "no-" + "check-code" in pre: | |
344 | if debug: |
|
344 | if debug: | |
345 | print "Skipping %s for %s it has no- and check-code" % ( |
|
345 | print "Skipping %s for %s it has no- and check-code" % ( | |
346 | name, f) |
|
346 | name, f) | |
347 | break |
|
347 | break | |
348 | for p, r in filters: |
|
348 | for p, r in filters: | |
349 | post = re.sub(p, r, post) |
|
349 | post = re.sub(p, r, post) | |
350 | if warnings: |
|
350 | if warnings: | |
351 | pats = pats[0] + pats[1] |
|
351 | pats = pats[0] + pats[1] | |
352 | else: |
|
352 | else: | |
353 | pats = pats[0] |
|
353 | pats = pats[0] | |
354 | # print post # uncomment to show filtered version |
|
354 | # print post # uncomment to show filtered version | |
355 |
|
355 | |||
356 | if debug: |
|
356 | if debug: | |
357 | print "Checking %s for %s" % (name, f) |
|
357 | print "Checking %s for %s" % (name, f) | |
358 |
|
358 | |||
359 | prelines = None |
|
359 | prelines = None | |
360 | errors = [] |
|
360 | errors = [] | |
361 | for pat in pats: |
|
361 | for pat in pats: | |
362 | if len(pat) == 3: |
|
362 | if len(pat) == 3: | |
363 | p, msg, ignore = pat |
|
363 | p, msg, ignore = pat | |
364 | else: |
|
364 | else: | |
365 | p, msg = pat |
|
365 | p, msg = pat | |
366 | ignore = None |
|
366 | ignore = None | |
367 |
|
367 | |||
368 | # fix-up regexes for multi-line searches |
|
368 | # fix-up regexes for multi-line searches | |
369 | po = p |
|
369 | po = p | |
370 | # \s doesn't match \n |
|
370 | # \s doesn't match \n | |
371 | p = re.sub(r'(?<!\\)\\s', r'[ \\t]', p) |
|
371 | p = re.sub(r'(?<!\\)\\s', r'[ \\t]', p) | |
372 | # [^...] doesn't match newline |
|
372 | # [^...] doesn't match newline | |
373 | p = re.sub(r'(?<!\\)\[\^', r'[^\\n', p) |
|
373 | p = re.sub(r'(?<!\\)\[\^', r'[^\\n', p) | |
374 |
|
374 | |||
375 | #print po, '=>', p |
|
375 | #print po, '=>', p | |
376 |
|
376 | |||
377 | pos = 0 |
|
377 | pos = 0 | |
378 | n = 0 |
|
378 | n = 0 | |
379 | for m in re.finditer(p, post, re.MULTILINE): |
|
379 | for m in re.finditer(p, post, re.MULTILINE): | |
380 | if prelines is None: |
|
380 | if prelines is None: | |
381 | prelines = pre.splitlines() |
|
381 | prelines = pre.splitlines() | |
382 | postlines = post.splitlines(True) |
|
382 | postlines = post.splitlines(True) | |
383 |
|
383 | |||
384 | start = m.start() |
|
384 | start = m.start() | |
385 | while n < len(postlines): |
|
385 | while n < len(postlines): | |
386 | step = len(postlines[n]) |
|
386 | step = len(postlines[n]) | |
387 | if pos + step > start: |
|
387 | if pos + step > start: | |
388 | break |
|
388 | break | |
389 | pos += step |
|
389 | pos += step | |
390 | n += 1 |
|
390 | n += 1 | |
391 | l = prelines[n] |
|
391 | l = prelines[n] | |
392 |
|
392 | |||
393 | if "check-code" + "-ignore" in l: |
|
393 | if "check-code" + "-ignore" in l: | |
394 | if debug: |
|
394 | if debug: | |
395 | print "Skipping %s for %s:%s (check-code -ignore)" % ( |
|
395 | print "Skipping %s for %s:%s (check-code -ignore)" % ( | |
396 | name, f, n) |
|
396 | name, f, n) | |
397 | continue |
|
397 | continue | |
398 | elif ignore and re.search(ignore, l, re.MULTILINE): |
|
398 | elif ignore and re.search(ignore, l, re.MULTILINE): | |
399 | continue |
|
399 | continue | |
400 | bd = "" |
|
400 | bd = "" | |
401 | if blame: |
|
401 | if blame: | |
402 | bd = 'working directory' |
|
402 | bd = 'working directory' | |
403 | if not blamecache: |
|
403 | if not blamecache: | |
404 | blamecache = getblame(f) |
|
404 | blamecache = getblame(f) | |
405 | if n < len(blamecache): |
|
405 | if n < len(blamecache): | |
406 | bl, bu, br = blamecache[n] |
|
406 | bl, bu, br = blamecache[n] | |
407 | if bl == l: |
|
407 | if bl == l: | |
408 | bd = '%s@%s' % (bu, br) |
|
408 | bd = '%s@%s' % (bu, br) | |
409 | errors.append((f, lineno and n + 1, l, msg, bd)) |
|
409 | errors.append((f, lineno and n + 1, l, msg, bd)) | |
410 | result = False |
|
410 | result = False | |
411 |
|
411 | |||
412 | errors.sort() |
|
412 | errors.sort() | |
413 | for e in errors: |
|
413 | for e in errors: | |
414 | logfunc(*e) |
|
414 | logfunc(*e) | |
415 | fc += 1 |
|
415 | fc += 1 | |
416 | if maxerr and fc >= maxerr: |
|
416 | if maxerr and fc >= maxerr: | |
417 | print " (too many errors, giving up)" |
|
417 | print " (too many errors, giving up)" | |
418 | break |
|
418 | break | |
419 |
|
419 | |||
420 | return result |
|
420 | return result | |
421 |
|
421 | |||
422 | if __name__ == "__main__": |
|
422 | if __name__ == "__main__": | |
423 | parser = optparse.OptionParser("%prog [options] [files]") |
|
423 | parser = optparse.OptionParser("%prog [options] [files]") | |
424 | parser.add_option("-w", "--warnings", action="store_true", |
|
424 | parser.add_option("-w", "--warnings", action="store_true", | |
425 | help="include warning-level checks") |
|
425 | help="include warning-level checks") | |
426 | parser.add_option("-p", "--per-file", type="int", |
|
426 | parser.add_option("-p", "--per-file", type="int", | |
427 | help="max warnings per file") |
|
427 | help="max warnings per file") | |
428 | parser.add_option("-b", "--blame", action="store_true", |
|
428 | parser.add_option("-b", "--blame", action="store_true", | |
429 | help="use annotate to generate blame info") |
|
429 | help="use annotate to generate blame info") | |
430 | parser.add_option("", "--debug", action="store_true", |
|
430 | parser.add_option("", "--debug", action="store_true", | |
431 | help="show debug information") |
|
431 | help="show debug information") | |
432 | parser.add_option("", "--nolineno", action="store_false", |
|
432 | parser.add_option("", "--nolineno", action="store_false", | |
433 | dest='lineno', help="don't show line numbers") |
|
433 | dest='lineno', help="don't show line numbers") | |
434 |
|
434 | |||
435 | parser.set_defaults(per_file=15, warnings=False, blame=False, debug=False, |
|
435 | parser.set_defaults(per_file=15, warnings=False, blame=False, debug=False, | |
436 | lineno=True) |
|
436 | lineno=True) | |
437 | (options, args) = parser.parse_args() |
|
437 | (options, args) = parser.parse_args() | |
438 |
|
438 | |||
439 | if len(args) == 0: |
|
439 | if len(args) == 0: | |
440 | check = glob.glob("*") |
|
440 | check = glob.glob("*") | |
441 | else: |
|
441 | else: | |
442 | check = args |
|
442 | check = args | |
443 |
|
443 | |||
444 | ret = 0 |
|
444 | ret = 0 | |
445 | for f in check: |
|
445 | for f in check: | |
446 | if not checkfile(f, maxerr=options.per_file, warnings=options.warnings, |
|
446 | if not checkfile(f, maxerr=options.per_file, warnings=options.warnings, | |
447 | blame=options.blame, debug=options.debug, |
|
447 | blame=options.blame, debug=options.debug, | |
448 | lineno=options.lineno): |
|
448 | lineno=options.lineno): | |
449 | ret = 1 |
|
449 | ret = 1 | |
450 | sys.exit(ret) |
|
450 | sys.exit(ret) |
@@ -1,167 +1,167 | |||||
1 | # win32mbcs.py -- MBCS filename support for Mercurial |
|
1 | # win32mbcs.py -- MBCS filename support for Mercurial | |
2 | # |
|
2 | # | |
3 | # Copyright (c) 2008 Shun-ichi Goto <shunichi.goto@gmail.com> |
|
3 | # Copyright (c) 2008 Shun-ichi Goto <shunichi.goto@gmail.com> | |
4 | # |
|
4 | # | |
5 | # Version: 0.3 |
|
5 | # Version: 0.3 | |
6 | # Author: Shun-ichi Goto <shunichi.goto@gmail.com> |
|
6 | # Author: Shun-ichi Goto <shunichi.goto@gmail.com> | |
7 | # |
|
7 | # | |
8 | # This software may be used and distributed according to the terms of the |
|
8 | # This software may be used and distributed according to the terms of the | |
9 | # GNU General Public License version 2 or any later version. |
|
9 | # GNU General Public License version 2 or any later version. | |
10 | # |
|
10 | # | |
11 |
|
11 | |||
12 | '''allow the use of MBCS paths with problematic encodings |
|
12 | '''allow the use of MBCS paths with problematic encodings | |
13 |
|
13 | |||
14 | Some MBCS encodings are not good for some path operations (i.e. |
|
14 | Some MBCS encodings are not good for some path operations (i.e. | |
15 | splitting path, case conversion, etc.) with its encoded bytes. We call |
|
15 | splitting path, case conversion, etc.) with its encoded bytes. We call | |
16 | such a encoding (i.e. shift_jis and big5) as "problematic encoding". |
|
16 | such a encoding (i.e. shift_jis and big5) as "problematic encoding". | |
17 | This extension can be used to fix the issue with those encodings by |
|
17 | This extension can be used to fix the issue with those encodings by | |
18 | wrapping some functions to convert to Unicode string before path |
|
18 | wrapping some functions to convert to Unicode string before path | |
19 | operation. |
|
19 | operation. | |
20 |
|
20 | |||
21 | This extension is useful for: |
|
21 | This extension is useful for: | |
22 |
|
22 | |||
23 | - Japanese Windows users using shift_jis encoding. |
|
23 | - Japanese Windows users using shift_jis encoding. | |
24 | - Chinese Windows users using big5 encoding. |
|
24 | - Chinese Windows users using big5 encoding. | |
25 | - All users who use a repository with one of problematic encodings on |
|
25 | - All users who use a repository with one of problematic encodings on | |
26 | case-insensitive file system. |
|
26 | case-insensitive file system. | |
27 |
|
27 | |||
28 | This extension is not needed for: |
|
28 | This extension is not needed for: | |
29 |
|
29 | |||
30 | - Any user who use only ASCII chars in path. |
|
30 | - Any user who use only ASCII chars in path. | |
31 | - Any user who do not use any of problematic encodings. |
|
31 | - Any user who do not use any of problematic encodings. | |
32 |
|
32 | |||
33 | Note that there are some limitations on using this extension: |
|
33 | Note that there are some limitations on using this extension: | |
34 |
|
34 | |||
35 | - You should use single encoding in one repository. |
|
35 | - You should use single encoding in one repository. | |
36 | - If the repository path ends with 0x5c, .hg/hgrc cannot be read. |
|
36 | - If the repository path ends with 0x5c, .hg/hgrc cannot be read. | |
37 | - win32mbcs is not compatible with fixutf8 extension. |
|
37 | - win32mbcs is not compatible with fixutf8 extension. | |
38 |
|
38 | |||
39 | By default, win32mbcs uses encoding.encoding decided by Mercurial. |
|
39 | By default, win32mbcs uses encoding.encoding decided by Mercurial. | |
40 | You can specify the encoding by config option:: |
|
40 | You can specify the encoding by config option:: | |
41 |
|
41 | |||
42 | [win32mbcs] |
|
42 | [win32mbcs] | |
43 | encoding = sjis |
|
43 | encoding = sjis | |
44 |
|
44 | |||
45 | It is useful for the users who want to commit with UTF-8 log message. |
|
45 | It is useful for the users who want to commit with UTF-8 log message. | |
46 | ''' |
|
46 | ''' | |
47 |
|
47 | |||
48 | import os, sys |
|
48 | import os, sys | |
49 | from mercurial.i18n import _ |
|
49 | from mercurial.i18n import _ | |
50 | from mercurial import util, encoding |
|
50 | from mercurial import util, encoding | |
51 | testedwith = 'internal' |
|
51 | testedwith = 'internal' | |
52 |
|
52 | |||
53 | _encoding = None # see extsetup |
|
53 | _encoding = None # see extsetup | |
54 |
|
54 | |||
55 | def decode(arg): |
|
55 | def decode(arg): | |
56 | if isinstance(arg, str): |
|
56 | if isinstance(arg, str): | |
57 | uarg = arg.decode(_encoding) |
|
57 | uarg = arg.decode(_encoding) | |
58 | if arg == uarg.encode(_encoding): |
|
58 | if arg == uarg.encode(_encoding): | |
59 | return uarg |
|
59 | return uarg | |
60 | raise UnicodeError("Not local encoding") |
|
60 | raise UnicodeError("Not local encoding") | |
61 | elif isinstance(arg, tuple): |
|
61 | elif isinstance(arg, tuple): | |
62 | return tuple(map(decode, arg)) |
|
62 | return tuple(map(decode, arg)) | |
63 | elif isinstance(arg, list): |
|
63 | elif isinstance(arg, list): | |
64 | return map(decode, arg) |
|
64 | return map(decode, arg) | |
65 | elif isinstance(arg, dict): |
|
65 | elif isinstance(arg, dict): | |
66 | for k, v in arg.items(): |
|
66 | for k, v in arg.items(): | |
67 | arg[k] = decode(v) |
|
67 | arg[k] = decode(v) | |
68 | return arg |
|
68 | return arg | |
69 |
|
69 | |||
70 | def encode(arg): |
|
70 | def encode(arg): | |
71 | if isinstance(arg, unicode): |
|
71 | if isinstance(arg, unicode): | |
72 | return arg.encode(_encoding) |
|
72 | return arg.encode(_encoding) | |
73 | elif isinstance(arg, tuple): |
|
73 | elif isinstance(arg, tuple): | |
74 | return tuple(map(encode, arg)) |
|
74 | return tuple(map(encode, arg)) | |
75 | elif isinstance(arg, list): |
|
75 | elif isinstance(arg, list): | |
76 | return map(encode, arg) |
|
76 | return map(encode, arg) | |
77 | elif isinstance(arg, dict): |
|
77 | elif isinstance(arg, dict): | |
78 | for k, v in arg.items(): |
|
78 | for k, v in arg.items(): | |
79 | arg[k] = encode(v) |
|
79 | arg[k] = encode(v) | |
80 | return arg |
|
80 | return arg | |
81 |
|
81 | |||
82 | def appendsep(s): |
|
82 | def appendsep(s): | |
83 | # ensure the path ends with os.sep, appending it if necessary. |
|
83 | # ensure the path ends with os.sep, appending it if necessary. | |
84 | try: |
|
84 | try: | |
85 | us = decode(s) |
|
85 | us = decode(s) | |
86 | except UnicodeError: |
|
86 | except UnicodeError: | |
87 | us = s |
|
87 | us = s | |
88 | if us and us[-1] not in ':/\\': |
|
88 | if us and us[-1] not in ':/\\': | |
89 | s += os.sep |
|
89 | s += os.sep | |
90 | return s |
|
90 | return s | |
91 |
|
91 | |||
92 | def wrapper(func, args, kwds): |
|
92 | def wrapper(func, args, kwds): | |
93 | # check argument is unicode, then call original |
|
93 | # check argument is unicode, then call original | |
94 | for arg in args: |
|
94 | for arg in args: | |
95 | if isinstance(arg, unicode): |
|
95 | if isinstance(arg, unicode): | |
96 | return func(*args, **kwds) |
|
96 | return func(*args, **kwds) | |
97 |
|
97 | |||
98 | try: |
|
98 | try: | |
99 | # convert arguments to unicode, call func, then convert back |
|
99 | # convert arguments to unicode, call func, then convert back | |
100 | return encode(func(*decode(args), **decode(kwds))) |
|
100 | return encode(func(*decode(args), **decode(kwds))) | |
101 | except UnicodeError: |
|
101 | except UnicodeError: | |
102 | raise util.Abort(_("[win32mbcs] filename conversion failed with" |
|
102 | raise util.Abort(_("[win32mbcs] filename conversion failed with" | |
103 | " %s encoding\n") % (_encoding)) |
|
103 | " %s encoding\n") % (_encoding)) | |
104 |
|
104 | |||
105 | def wrapperforlistdir(func, args, kwds): |
|
105 | def wrapperforlistdir(func, args, kwds): | |
106 | # Ensure 'path' argument ends with os.sep to avoids |
|
106 | # Ensure 'path' argument ends with os.sep to avoids | |
107 | # misinterpreting last 0x5c of MBCS 2nd byte as path separator. |
|
107 | # misinterpreting last 0x5c of MBCS 2nd byte as path separator. | |
108 | if args: |
|
108 | if args: | |
109 | args = list(args) |
|
109 | args = list(args) | |
110 | args[0] = appendsep(args[0]) |
|
110 | args[0] = appendsep(args[0]) | |
111 | if 'path' in kwds: |
|
111 | if 'path' in kwds: | |
112 | kwds['path'] = appendsep(kwds['path']) |
|
112 | kwds['path'] = appendsep(kwds['path']) | |
113 | return func(*args, **kwds) |
|
113 | return func(*args, **kwds) | |
114 |
|
114 | |||
115 | def wrapname(name, wrapper): |
|
115 | def wrapname(name, wrapper): | |
116 | module, name = name.rsplit('.', 1) |
|
116 | module, name = name.rsplit('.', 1) | |
117 | module = sys.modules[module] |
|
117 | module = sys.modules[module] | |
118 | func = getattr(module, name) |
|
118 | func = getattr(module, name) | |
119 | def f(*args, **kwds): |
|
119 | def f(*args, **kwds): | |
120 | return wrapper(func, args, kwds) |
|
120 | return wrapper(func, args, kwds) | |
121 | try: |
|
121 | try: | |
122 |
f.__name__ = func.__name__ |
|
122 | f.__name__ = func.__name__ # fails with Python 2.3 | |
123 | except Exception: |
|
123 | except Exception: | |
124 | pass |
|
124 | pass | |
125 | setattr(module, name, f) |
|
125 | setattr(module, name, f) | |
126 |
|
126 | |||
127 | # List of functions to be wrapped. |
|
127 | # List of functions to be wrapped. | |
128 | # NOTE: os.path.dirname() and os.path.basename() are safe because |
|
128 | # NOTE: os.path.dirname() and os.path.basename() are safe because | |
129 | # they use result of os.path.split() |
|
129 | # they use result of os.path.split() | |
130 | funcs = '''os.path.join os.path.split os.path.splitext |
|
130 | funcs = '''os.path.join os.path.split os.path.splitext | |
131 | os.path.normpath os.makedirs |
|
131 | os.path.normpath os.makedirs | |
132 | mercurial.util.endswithsep mercurial.util.splitpath mercurial.util.checkcase |
|
132 | mercurial.util.endswithsep mercurial.util.splitpath mercurial.util.checkcase | |
133 | mercurial.util.fspath mercurial.util.pconvert mercurial.util.normpath |
|
133 | mercurial.util.fspath mercurial.util.pconvert mercurial.util.normpath | |
134 | mercurial.util.checkwinfilename mercurial.util.checkosfilename''' |
|
134 | mercurial.util.checkwinfilename mercurial.util.checkosfilename''' | |
135 |
|
135 | |||
136 | # List of Windows specific functions to be wrapped. |
|
136 | # List of Windows specific functions to be wrapped. | |
137 | winfuncs = '''os.path.splitunc''' |
|
137 | winfuncs = '''os.path.splitunc''' | |
138 |
|
138 | |||
139 | # codec and alias names of sjis and big5 to be faked. |
|
139 | # codec and alias names of sjis and big5 to be faked. | |
140 | problematic_encodings = '''big5 big5-tw csbig5 big5hkscs big5-hkscs |
|
140 | problematic_encodings = '''big5 big5-tw csbig5 big5hkscs big5-hkscs | |
141 | hkscs cp932 932 ms932 mskanji ms-kanji shift_jis csshiftjis shiftjis |
|
141 | hkscs cp932 932 ms932 mskanji ms-kanji shift_jis csshiftjis shiftjis | |
142 | sjis s_jis shift_jis_2004 shiftjis2004 sjis_2004 sjis2004 |
|
142 | sjis s_jis shift_jis_2004 shiftjis2004 sjis_2004 sjis2004 | |
143 | shift_jisx0213 shiftjisx0213 sjisx0213 s_jisx0213 950 cp950 ms950 ''' |
|
143 | shift_jisx0213 shiftjisx0213 sjisx0213 s_jisx0213 950 cp950 ms950 ''' | |
144 |
|
144 | |||
145 | def extsetup(ui): |
|
145 | def extsetup(ui): | |
146 | # TODO: decide use of config section for this extension |
|
146 | # TODO: decide use of config section for this extension | |
147 | if ((not os.path.supports_unicode_filenames) and |
|
147 | if ((not os.path.supports_unicode_filenames) and | |
148 | (sys.platform != 'cygwin')): |
|
148 | (sys.platform != 'cygwin')): | |
149 | ui.warn(_("[win32mbcs] cannot activate on this platform.\n")) |
|
149 | ui.warn(_("[win32mbcs] cannot activate on this platform.\n")) | |
150 | return |
|
150 | return | |
151 | # determine encoding for filename |
|
151 | # determine encoding for filename | |
152 | global _encoding |
|
152 | global _encoding | |
153 | _encoding = ui.config('win32mbcs', 'encoding', encoding.encoding) |
|
153 | _encoding = ui.config('win32mbcs', 'encoding', encoding.encoding) | |
154 | # fake is only for relevant environment. |
|
154 | # fake is only for relevant environment. | |
155 | if _encoding.lower() in problematic_encodings.split(): |
|
155 | if _encoding.lower() in problematic_encodings.split(): | |
156 | for f in funcs.split(): |
|
156 | for f in funcs.split(): | |
157 | wrapname(f, wrapper) |
|
157 | wrapname(f, wrapper) | |
158 | if os.name == 'nt': |
|
158 | if os.name == 'nt': | |
159 | for f in winfuncs.split(): |
|
159 | for f in winfuncs.split(): | |
160 | wrapname(f, wrapper) |
|
160 | wrapname(f, wrapper) | |
161 | wrapname("mercurial.osutil.listdir", wrapperforlistdir) |
|
161 | wrapname("mercurial.osutil.listdir", wrapperforlistdir) | |
162 | # Check sys.args manually instead of using ui.debug() because |
|
162 | # Check sys.args manually instead of using ui.debug() because | |
163 | # command line options is not yet applied when |
|
163 | # command line options is not yet applied when | |
164 | # extensions.loadall() is called. |
|
164 | # extensions.loadall() is called. | |
165 | if '--debug' in sys.argv: |
|
165 | if '--debug' in sys.argv: | |
166 | ui.write("[win32mbcs] activated with encoding: %s\n" |
|
166 | ui.write("[win32mbcs] activated with encoding: %s\n" | |
167 | % _encoding) |
|
167 | % _encoding) |
@@ -1,1581 +1,1580 | |||||
1 | """ Multicast DNS Service Discovery for Python, v0.12 |
|
1 | """ Multicast DNS Service Discovery for Python, v0.12 | |
2 | Copyright (C) 2003, Paul Scott-Murphy |
|
2 | Copyright (C) 2003, Paul Scott-Murphy | |
3 |
|
3 | |||
4 | This module provides a framework for the use of DNS Service Discovery |
|
4 | This module provides a framework for the use of DNS Service Discovery | |
5 | using IP multicast. It has been tested against the JRendezvous |
|
5 | using IP multicast. It has been tested against the JRendezvous | |
6 | implementation from <a href="http://strangeberry.com">StrangeBerry</a>, |
|
6 | implementation from <a href="http://strangeberry.com">StrangeBerry</a>, | |
7 | and against the mDNSResponder from Mac OS X 10.3.8. |
|
7 | and against the mDNSResponder from Mac OS X 10.3.8. | |
8 |
|
8 | |||
9 | This library is free software; you can redistribute it and/or |
|
9 | This library is free software; you can redistribute it and/or | |
10 | modify it under the terms of the GNU Lesser General Public |
|
10 | modify it under the terms of the GNU Lesser General Public | |
11 | License as published by the Free Software Foundation; either |
|
11 | License as published by the Free Software Foundation; either | |
12 | version 2.1 of the License, or (at your option) any later version. |
|
12 | version 2.1 of the License, or (at your option) any later version. | |
13 |
|
13 | |||
14 | This library is distributed in the hope that it will be useful, |
|
14 | This library is distributed in the hope that it will be useful, | |
15 | but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
15 | but WITHOUT ANY WARRANTY; without even the implied warranty of | |
16 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
|
16 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
17 | Lesser General Public License for more details. |
|
17 | Lesser General Public License for more details. | |
18 |
|
18 | |||
19 | You should have received a copy of the GNU Lesser General Public |
|
19 | You should have received a copy of the GNU Lesser General Public | |
20 | License along with this library; if not, see |
|
20 | License along with this library; if not, see | |
21 | <http://www.gnu.org/licenses/>. |
|
21 | <http://www.gnu.org/licenses/>. | |
22 |
|
22 | |||
23 | """ |
|
23 | """ | |
24 |
|
24 | |||
25 | """0.12 update - allow selection of binding interface |
|
25 | """0.12 update - allow selection of binding interface | |
26 | typo fix - Thanks A. M. Kuchlingi |
|
26 | typo fix - Thanks A. M. Kuchlingi | |
27 | removed all use of word 'Rendezvous' - this is an API change""" |
|
27 | removed all use of word 'Rendezvous' - this is an API change""" | |
28 |
|
28 | |||
29 | """0.11 update - correction to comments for addListener method |
|
29 | """0.11 update - correction to comments for addListener method | |
30 | support for new record types seen from OS X |
|
30 | support for new record types seen from OS X | |
31 | - IPv6 address |
|
31 | - IPv6 address | |
32 | - hostinfo |
|
32 | - hostinfo | |
33 | ignore unknown DNS record types |
|
33 | ignore unknown DNS record types | |
34 | fixes to name decoding |
|
34 | fixes to name decoding | |
35 | works alongside other processes using port 5353 (e.g. on Mac OS X) |
|
35 | works alongside other processes using port 5353 (e.g. on Mac OS X) | |
36 | tested against Mac OS X 10.3.2's mDNSResponder |
|
36 | tested against Mac OS X 10.3.2's mDNSResponder | |
37 | corrections to removal of list entries for service browser""" |
|
37 | corrections to removal of list entries for service browser""" | |
38 |
|
38 | |||
39 | """0.10 update - Jonathon Paisley contributed these corrections: |
|
39 | """0.10 update - Jonathon Paisley contributed these corrections: | |
40 | always multicast replies, even when query is unicast |
|
40 | always multicast replies, even when query is unicast | |
41 | correct a pointer encoding problem |
|
41 | correct a pointer encoding problem | |
42 | can now write records in any order |
|
42 | can now write records in any order | |
43 | traceback shown on failure |
|
43 | traceback shown on failure | |
44 | better TXT record parsing |
|
44 | better TXT record parsing | |
45 | server is now separate from name |
|
45 | server is now separate from name | |
46 | can cancel a service browser |
|
46 | can cancel a service browser | |
47 |
|
47 | |||
48 | modified some unit tests to accommodate these changes""" |
|
48 | modified some unit tests to accommodate these changes""" | |
49 |
|
49 | |||
50 | """0.09 update - remove all records on service unregistration |
|
50 | """0.09 update - remove all records on service unregistration | |
51 | fix DOS security problem with readName""" |
|
51 | fix DOS security problem with readName""" | |
52 |
|
52 | |||
53 | """0.08 update - changed licensing to LGPL""" |
|
53 | """0.08 update - changed licensing to LGPL""" | |
54 |
|
54 | |||
55 | """0.07 update - faster shutdown on engine |
|
55 | """0.07 update - faster shutdown on engine | |
56 | pointer encoding of outgoing names |
|
56 | pointer encoding of outgoing names | |
57 | ServiceBrowser now works |
|
57 | ServiceBrowser now works | |
58 | new unit tests""" |
|
58 | new unit tests""" | |
59 |
|
59 | |||
60 | """0.06 update - small improvements with unit tests |
|
60 | """0.06 update - small improvements with unit tests | |
61 | added defined exception types |
|
61 | added defined exception types | |
62 | new style objects |
|
62 | new style objects | |
63 | fixed hostname/interface problem |
|
63 | fixed hostname/interface problem | |
64 | fixed socket timeout problem |
|
64 | fixed socket timeout problem | |
65 | fixed addServiceListener() typo bug |
|
65 | fixed addServiceListener() typo bug | |
66 | using select() for socket reads |
|
66 | using select() for socket reads | |
67 | tested on Debian unstable with Python 2.2.2""" |
|
67 | tested on Debian unstable with Python 2.2.2""" | |
68 |
|
68 | |||
69 | """0.05 update - ensure case insensitivity on domain names |
|
69 | """0.05 update - ensure case insensitivity on domain names | |
70 | support for unicast DNS queries""" |
|
70 | support for unicast DNS queries""" | |
71 |
|
71 | |||
72 | """0.04 update - added some unit tests |
|
72 | """0.04 update - added some unit tests | |
73 | added __ne__ adjuncts where required |
|
73 | added __ne__ adjuncts where required | |
74 | ensure names end in '.local.' |
|
74 | ensure names end in '.local.' | |
75 | timeout on receiving socket for clean shutdown""" |
|
75 | timeout on receiving socket for clean shutdown""" | |
76 |
|
76 | |||
77 | __author__ = "Paul Scott-Murphy" |
|
77 | __author__ = "Paul Scott-Murphy" | |
78 | __email__ = "paul at scott dash murphy dot com" |
|
78 | __email__ = "paul at scott dash murphy dot com" | |
79 | __version__ = "0.12" |
|
79 | __version__ = "0.12" | |
80 |
|
80 | |||
81 | import string |
|
81 | import string | |
82 | import time |
|
82 | import time | |
83 | import struct |
|
83 | import struct | |
84 | import socket |
|
84 | import socket | |
85 | import threading |
|
85 | import threading | |
86 | import select |
|
86 | import select | |
87 | import traceback |
|
87 | import traceback | |
88 |
|
88 | |||
89 | __all__ = ["Zeroconf", "ServiceInfo", "ServiceBrowser"] |
|
89 | __all__ = ["Zeroconf", "ServiceInfo", "ServiceBrowser"] | |
90 |
|
90 | |||
91 | # hook for threads |
|
91 | # hook for threads | |
92 |
|
92 | |||
93 | globals()['_GLOBAL_DONE'] = 0 |
|
93 | globals()['_GLOBAL_DONE'] = 0 | |
94 |
|
94 | |||
95 | # Some timing constants |
|
95 | # Some timing constants | |
96 |
|
96 | |||
97 | _UNREGISTER_TIME = 125 |
|
97 | _UNREGISTER_TIME = 125 | |
98 | _CHECK_TIME = 175 |
|
98 | _CHECK_TIME = 175 | |
99 | _REGISTER_TIME = 225 |
|
99 | _REGISTER_TIME = 225 | |
100 | _LISTENER_TIME = 200 |
|
100 | _LISTENER_TIME = 200 | |
101 | _BROWSER_TIME = 500 |
|
101 | _BROWSER_TIME = 500 | |
102 |
|
102 | |||
103 | # Some DNS constants |
|
103 | # Some DNS constants | |
104 |
|
104 | |||
105 | _MDNS_ADDR = '224.0.0.251' |
|
105 | _MDNS_ADDR = '224.0.0.251' | |
106 | _MDNS_PORT = 5353; |
|
106 | _MDNS_PORT = 5353; | |
107 | _DNS_PORT = 53; |
|
107 | _DNS_PORT = 53; | |
108 | _DNS_TTL = 60 * 60; # one hour default TTL |
|
108 | _DNS_TTL = 60 * 60; # one hour default TTL | |
109 |
|
109 | |||
110 | _MAX_MSG_TYPICAL = 1460 # unused |
|
110 | _MAX_MSG_TYPICAL = 1460 # unused | |
111 | _MAX_MSG_ABSOLUTE = 8972 |
|
111 | _MAX_MSG_ABSOLUTE = 8972 | |
112 |
|
112 | |||
113 | _FLAGS_QR_MASK = 0x8000 # query response mask |
|
113 | _FLAGS_QR_MASK = 0x8000 # query response mask | |
114 | _FLAGS_QR_QUERY = 0x0000 # query |
|
114 | _FLAGS_QR_QUERY = 0x0000 # query | |
115 | _FLAGS_QR_RESPONSE = 0x8000 # response |
|
115 | _FLAGS_QR_RESPONSE = 0x8000 # response | |
116 |
|
116 | |||
117 | _FLAGS_AA = 0x0400 # Authoritative answer |
|
117 | _FLAGS_AA = 0x0400 # Authoritative answer | |
118 | _FLAGS_TC = 0x0200 # Truncated |
|
118 | _FLAGS_TC = 0x0200 # Truncated | |
119 | _FLAGS_RD = 0x0100 # Recursion desired |
|
119 | _FLAGS_RD = 0x0100 # Recursion desired | |
120 | _FLAGS_RA = 0x8000 # Recursion available |
|
120 | _FLAGS_RA = 0x8000 # Recursion available | |
121 |
|
121 | |||
122 | _FLAGS_Z = 0x0040 # Zero |
|
122 | _FLAGS_Z = 0x0040 # Zero | |
123 | _FLAGS_AD = 0x0020 # Authentic data |
|
123 | _FLAGS_AD = 0x0020 # Authentic data | |
124 | _FLAGS_CD = 0x0010 # Checking disabled |
|
124 | _FLAGS_CD = 0x0010 # Checking disabled | |
125 |
|
125 | |||
126 | _CLASS_IN = 1 |
|
126 | _CLASS_IN = 1 | |
127 | _CLASS_CS = 2 |
|
127 | _CLASS_CS = 2 | |
128 | _CLASS_CH = 3 |
|
128 | _CLASS_CH = 3 | |
129 | _CLASS_HS = 4 |
|
129 | _CLASS_HS = 4 | |
130 | _CLASS_NONE = 254 |
|
130 | _CLASS_NONE = 254 | |
131 | _CLASS_ANY = 255 |
|
131 | _CLASS_ANY = 255 | |
132 | _CLASS_MASK = 0x7FFF |
|
132 | _CLASS_MASK = 0x7FFF | |
133 | _CLASS_UNIQUE = 0x8000 |
|
133 | _CLASS_UNIQUE = 0x8000 | |
134 |
|
134 | |||
135 | _TYPE_A = 1 |
|
135 | _TYPE_A = 1 | |
136 | _TYPE_NS = 2 |
|
136 | _TYPE_NS = 2 | |
137 | _TYPE_MD = 3 |
|
137 | _TYPE_MD = 3 | |
138 | _TYPE_MF = 4 |
|
138 | _TYPE_MF = 4 | |
139 | _TYPE_CNAME = 5 |
|
139 | _TYPE_CNAME = 5 | |
140 | _TYPE_SOA = 6 |
|
140 | _TYPE_SOA = 6 | |
141 | _TYPE_MB = 7 |
|
141 | _TYPE_MB = 7 | |
142 | _TYPE_MG = 8 |
|
142 | _TYPE_MG = 8 | |
143 | _TYPE_MR = 9 |
|
143 | _TYPE_MR = 9 | |
144 | _TYPE_NULL = 10 |
|
144 | _TYPE_NULL = 10 | |
145 | _TYPE_WKS = 11 |
|
145 | _TYPE_WKS = 11 | |
146 | _TYPE_PTR = 12 |
|
146 | _TYPE_PTR = 12 | |
147 | _TYPE_HINFO = 13 |
|
147 | _TYPE_HINFO = 13 | |
148 | _TYPE_MINFO = 14 |
|
148 | _TYPE_MINFO = 14 | |
149 | _TYPE_MX = 15 |
|
149 | _TYPE_MX = 15 | |
150 | _TYPE_TXT = 16 |
|
150 | _TYPE_TXT = 16 | |
151 | _TYPE_AAAA = 28 |
|
151 | _TYPE_AAAA = 28 | |
152 | _TYPE_SRV = 33 |
|
152 | _TYPE_SRV = 33 | |
153 | _TYPE_ANY = 255 |
|
153 | _TYPE_ANY = 255 | |
154 |
|
154 | |||
155 | # Mapping constants to names |
|
155 | # Mapping constants to names | |
156 |
|
156 | |||
157 | _CLASSES = { _CLASS_IN : "in", |
|
157 | _CLASSES = { _CLASS_IN : "in", | |
158 | _CLASS_CS : "cs", |
|
158 | _CLASS_CS : "cs", | |
159 | _CLASS_CH : "ch", |
|
159 | _CLASS_CH : "ch", | |
160 | _CLASS_HS : "hs", |
|
160 | _CLASS_HS : "hs", | |
161 | _CLASS_NONE : "none", |
|
161 | _CLASS_NONE : "none", | |
162 | _CLASS_ANY : "any" } |
|
162 | _CLASS_ANY : "any" } | |
163 |
|
163 | |||
164 | _TYPES = { _TYPE_A : "a", |
|
164 | _TYPES = { _TYPE_A : "a", | |
165 | _TYPE_NS : "ns", |
|
165 | _TYPE_NS : "ns", | |
166 | _TYPE_MD : "md", |
|
166 | _TYPE_MD : "md", | |
167 | _TYPE_MF : "mf", |
|
167 | _TYPE_MF : "mf", | |
168 | _TYPE_CNAME : "cname", |
|
168 | _TYPE_CNAME : "cname", | |
169 | _TYPE_SOA : "soa", |
|
169 | _TYPE_SOA : "soa", | |
170 | _TYPE_MB : "mb", |
|
170 | _TYPE_MB : "mb", | |
171 | _TYPE_MG : "mg", |
|
171 | _TYPE_MG : "mg", | |
172 | _TYPE_MR : "mr", |
|
172 | _TYPE_MR : "mr", | |
173 | _TYPE_NULL : "null", |
|
173 | _TYPE_NULL : "null", | |
174 | _TYPE_WKS : "wks", |
|
174 | _TYPE_WKS : "wks", | |
175 | _TYPE_PTR : "ptr", |
|
175 | _TYPE_PTR : "ptr", | |
176 | _TYPE_HINFO : "hinfo", |
|
176 | _TYPE_HINFO : "hinfo", | |
177 | _TYPE_MINFO : "minfo", |
|
177 | _TYPE_MINFO : "minfo", | |
178 | _TYPE_MX : "mx", |
|
178 | _TYPE_MX : "mx", | |
179 | _TYPE_TXT : "txt", |
|
179 | _TYPE_TXT : "txt", | |
180 | _TYPE_AAAA : "quada", |
|
180 | _TYPE_AAAA : "quada", | |
181 | _TYPE_SRV : "srv", |
|
181 | _TYPE_SRV : "srv", | |
182 | _TYPE_ANY : "any" } |
|
182 | _TYPE_ANY : "any" } | |
183 |
|
183 | |||
184 | # utility functions |
|
184 | # utility functions | |
185 |
|
185 | |||
186 | def currentTimeMillis(): |
|
186 | def currentTimeMillis(): | |
187 | """Current system time in milliseconds""" |
|
187 | """Current system time in milliseconds""" | |
188 | return time.time() * 1000 |
|
188 | return time.time() * 1000 | |
189 |
|
189 | |||
190 | # Exceptions |
|
190 | # Exceptions | |
191 |
|
191 | |||
192 | class NonLocalNameException(Exception): |
|
192 | class NonLocalNameException(Exception): | |
193 | pass |
|
193 | pass | |
194 |
|
194 | |||
195 | class NonUniqueNameException(Exception): |
|
195 | class NonUniqueNameException(Exception): | |
196 | pass |
|
196 | pass | |
197 |
|
197 | |||
198 | class NamePartTooLongException(Exception): |
|
198 | class NamePartTooLongException(Exception): | |
199 | pass |
|
199 | pass | |
200 |
|
200 | |||
201 | class AbstractMethodException(Exception): |
|
201 | class AbstractMethodException(Exception): | |
202 | pass |
|
202 | pass | |
203 |
|
203 | |||
204 | class BadTypeInNameException(Exception): |
|
204 | class BadTypeInNameException(Exception): | |
205 | pass |
|
205 | pass | |
206 |
|
206 | |||
207 | class BadDomainName(Exception): |
|
207 | class BadDomainName(Exception): | |
208 | def __init__(self, pos): |
|
208 | def __init__(self, pos): | |
209 | Exception.__init__(self, "at position %s" % pos) |
|
209 | Exception.__init__(self, "at position %s" % pos) | |
210 |
|
210 | |||
211 | class BadDomainNameCircular(BadDomainName): |
|
211 | class BadDomainNameCircular(BadDomainName): | |
212 | pass |
|
212 | pass | |
213 |
|
213 | |||
214 | # implementation classes |
|
214 | # implementation classes | |
215 |
|
215 | |||
216 | class DNSEntry(object): |
|
216 | class DNSEntry(object): | |
217 | """A DNS entry""" |
|
217 | """A DNS entry""" | |
218 |
|
218 | |||
219 | def __init__(self, name, type, clazz): |
|
219 | def __init__(self, name, type, clazz): | |
220 | self.key = string.lower(name) |
|
220 | self.key = string.lower(name) | |
221 | self.name = name |
|
221 | self.name = name | |
222 | self.type = type |
|
222 | self.type = type | |
223 | self.clazz = clazz & _CLASS_MASK |
|
223 | self.clazz = clazz & _CLASS_MASK | |
224 | self.unique = (clazz & _CLASS_UNIQUE) != 0 |
|
224 | self.unique = (clazz & _CLASS_UNIQUE) != 0 | |
225 |
|
225 | |||
226 | def __eq__(self, other): |
|
226 | def __eq__(self, other): | |
227 | """Equality test on name, type, and class""" |
|
227 | """Equality test on name, type, and class""" | |
228 | if isinstance(other, DNSEntry): |
|
228 | if isinstance(other, DNSEntry): | |
229 | return self.name == other.name and self.type == other.type and self.clazz == other.clazz |
|
229 | return self.name == other.name and self.type == other.type and self.clazz == other.clazz | |
230 | return 0 |
|
230 | return 0 | |
231 |
|
231 | |||
232 | def __ne__(self, other): |
|
232 | def __ne__(self, other): | |
233 | """Non-equality test""" |
|
233 | """Non-equality test""" | |
234 | return not self.__eq__(other) |
|
234 | return not self.__eq__(other) | |
235 |
|
235 | |||
236 | def getClazz(self, clazz): |
|
236 | def getClazz(self, clazz): | |
237 | """Class accessor""" |
|
237 | """Class accessor""" | |
238 | try: |
|
238 | try: | |
239 | return _CLASSES[clazz] |
|
239 | return _CLASSES[clazz] | |
240 | except KeyError: |
|
240 | except KeyError: | |
241 | return "?(%s)" % (clazz) |
|
241 | return "?(%s)" % (clazz) | |
242 |
|
242 | |||
243 | def getType(self, type): |
|
243 | def getType(self, type): | |
244 | """Type accessor""" |
|
244 | """Type accessor""" | |
245 | try: |
|
245 | try: | |
246 | return _TYPES[type] |
|
246 | return _TYPES[type] | |
247 | except KeyError: |
|
247 | except KeyError: | |
248 | return "?(%s)" % (type) |
|
248 | return "?(%s)" % (type) | |
249 |
|
249 | |||
250 | def toString(self, hdr, other): |
|
250 | def toString(self, hdr, other): | |
251 | """String representation with additional information""" |
|
251 | """String representation with additional information""" | |
252 | result = "%s[%s,%s" % (hdr, self.getType(self.type), self.getClazz(self.clazz)) |
|
252 | result = "%s[%s,%s" % (hdr, self.getType(self.type), self.getClazz(self.clazz)) | |
253 | if self.unique: |
|
253 | if self.unique: | |
254 | result += "-unique," |
|
254 | result += "-unique," | |
255 | else: |
|
255 | else: | |
256 | result += "," |
|
256 | result += "," | |
257 | result += self.name |
|
257 | result += self.name | |
258 | if other is not None: |
|
258 | if other is not None: | |
259 | result += ",%s]" % (other) |
|
259 | result += ",%s]" % (other) | |
260 | else: |
|
260 | else: | |
261 | result += "]" |
|
261 | result += "]" | |
262 | return result |
|
262 | return result | |
263 |
|
263 | |||
264 | class DNSQuestion(DNSEntry): |
|
264 | class DNSQuestion(DNSEntry): | |
265 | """A DNS question entry""" |
|
265 | """A DNS question entry""" | |
266 |
|
266 | |||
267 | def __init__(self, name, type, clazz): |
|
267 | def __init__(self, name, type, clazz): | |
268 | if not name.endswith(".local."): |
|
268 | if not name.endswith(".local."): | |
269 | raise NonLocalNameException(name) |
|
269 | raise NonLocalNameException(name) | |
270 | DNSEntry.__init__(self, name, type, clazz) |
|
270 | DNSEntry.__init__(self, name, type, clazz) | |
271 |
|
271 | |||
272 | def answeredBy(self, rec): |
|
272 | def answeredBy(self, rec): | |
273 | """Returns true if the question is answered by the record""" |
|
273 | """Returns true if the question is answered by the record""" | |
274 | return self.clazz == rec.clazz and (self.type == rec.type or self.type == _TYPE_ANY) and self.name == rec.name |
|
274 | return self.clazz == rec.clazz and (self.type == rec.type or self.type == _TYPE_ANY) and self.name == rec.name | |
275 |
|
275 | |||
276 | def __repr__(self): |
|
276 | def __repr__(self): | |
277 | """String representation""" |
|
277 | """String representation""" | |
278 | return DNSEntry.toString(self, "question", None) |
|
278 | return DNSEntry.toString(self, "question", None) | |
279 |
|
279 | |||
280 |
|
280 | |||
281 | class DNSRecord(DNSEntry): |
|
281 | class DNSRecord(DNSEntry): | |
282 | """A DNS record - like a DNS entry, but has a TTL""" |
|
282 | """A DNS record - like a DNS entry, but has a TTL""" | |
283 |
|
283 | |||
284 | def __init__(self, name, type, clazz, ttl): |
|
284 | def __init__(self, name, type, clazz, ttl): | |
285 | DNSEntry.__init__(self, name, type, clazz) |
|
285 | DNSEntry.__init__(self, name, type, clazz) | |
286 | self.ttl = ttl |
|
286 | self.ttl = ttl | |
287 | self.created = currentTimeMillis() |
|
287 | self.created = currentTimeMillis() | |
288 |
|
288 | |||
289 | def __eq__(self, other): |
|
289 | def __eq__(self, other): | |
290 | """Tests equality as per DNSRecord""" |
|
290 | """Tests equality as per DNSRecord""" | |
291 | if isinstance(other, DNSRecord): |
|
291 | if isinstance(other, DNSRecord): | |
292 | return DNSEntry.__eq__(self, other) |
|
292 | return DNSEntry.__eq__(self, other) | |
293 | return 0 |
|
293 | return 0 | |
294 |
|
294 | |||
295 | def suppressedBy(self, msg): |
|
295 | def suppressedBy(self, msg): | |
296 | """Returns true if any answer in a message can suffice for the |
|
296 | """Returns true if any answer in a message can suffice for the | |
297 | information held in this record.""" |
|
297 | information held in this record.""" | |
298 | for record in msg.answers: |
|
298 | for record in msg.answers: | |
299 | if self.suppressedByAnswer(record): |
|
299 | if self.suppressedByAnswer(record): | |
300 | return 1 |
|
300 | return 1 | |
301 | return 0 |
|
301 | return 0 | |
302 |
|
302 | |||
303 | def suppressedByAnswer(self, other): |
|
303 | def suppressedByAnswer(self, other): | |
304 | """Returns true if another record has same name, type and class, |
|
304 | """Returns true if another record has same name, type and class, | |
305 | and if its TTL is at least half of this record's.""" |
|
305 | and if its TTL is at least half of this record's.""" | |
306 | if self == other and other.ttl > (self.ttl / 2): |
|
306 | if self == other and other.ttl > (self.ttl / 2): | |
307 | return 1 |
|
307 | return 1 | |
308 | return 0 |
|
308 | return 0 | |
309 |
|
309 | |||
310 | def getExpirationTime(self, percent): |
|
310 | def getExpirationTime(self, percent): | |
311 | """Returns the time at which this record will have expired |
|
311 | """Returns the time at which this record will have expired | |
312 | by a certain percentage.""" |
|
312 | by a certain percentage.""" | |
313 | return self.created + (percent * self.ttl * 10) |
|
313 | return self.created + (percent * self.ttl * 10) | |
314 |
|
314 | |||
315 | def getRemainingTTL(self, now): |
|
315 | def getRemainingTTL(self, now): | |
316 | """Returns the remaining TTL in seconds.""" |
|
316 | """Returns the remaining TTL in seconds.""" | |
317 | return max(0, (self.getExpirationTime(100) - now) / 1000) |
|
317 | return max(0, (self.getExpirationTime(100) - now) / 1000) | |
318 |
|
318 | |||
319 | def isExpired(self, now): |
|
319 | def isExpired(self, now): | |
320 | """Returns true if this record has expired.""" |
|
320 | """Returns true if this record has expired.""" | |
321 | return self.getExpirationTime(100) <= now |
|
321 | return self.getExpirationTime(100) <= now | |
322 |
|
322 | |||
323 | def isStale(self, now): |
|
323 | def isStale(self, now): | |
324 | """Returns true if this record is at least half way expired.""" |
|
324 | """Returns true if this record is at least half way expired.""" | |
325 | return self.getExpirationTime(50) <= now |
|
325 | return self.getExpirationTime(50) <= now | |
326 |
|
326 | |||
327 | def resetTTL(self, other): |
|
327 | def resetTTL(self, other): | |
328 | """Sets this record's TTL and created time to that of |
|
328 | """Sets this record's TTL and created time to that of | |
329 | another record.""" |
|
329 | another record.""" | |
330 | self.created = other.created |
|
330 | self.created = other.created | |
331 | self.ttl = other.ttl |
|
331 | self.ttl = other.ttl | |
332 |
|
332 | |||
333 | def write(self, out): |
|
333 | def write(self, out): | |
334 | """Abstract method""" |
|
334 | """Abstract method""" | |
335 | raise AbstractMethodException |
|
335 | raise AbstractMethodException | |
336 |
|
336 | |||
337 | def toString(self, other): |
|
337 | def toString(self, other): | |
338 | """String representation with additional information""" |
|
338 | """String representation with additional information""" | |
339 | arg = "%s/%s,%s" % (self.ttl, self.getRemainingTTL(currentTimeMillis()), other) |
|
339 | arg = "%s/%s,%s" % (self.ttl, self.getRemainingTTL(currentTimeMillis()), other) | |
340 | return DNSEntry.toString(self, "record", arg) |
|
340 | return DNSEntry.toString(self, "record", arg) | |
341 |
|
341 | |||
342 | class DNSAddress(DNSRecord): |
|
342 | class DNSAddress(DNSRecord): | |
343 | """A DNS address record""" |
|
343 | """A DNS address record""" | |
344 |
|
344 | |||
345 | def __init__(self, name, type, clazz, ttl, address): |
|
345 | def __init__(self, name, type, clazz, ttl, address): | |
346 | DNSRecord.__init__(self, name, type, clazz, ttl) |
|
346 | DNSRecord.__init__(self, name, type, clazz, ttl) | |
347 | self.address = address |
|
347 | self.address = address | |
348 |
|
348 | |||
349 | def write(self, out): |
|
349 | def write(self, out): | |
350 | """Used in constructing an outgoing packet""" |
|
350 | """Used in constructing an outgoing packet""" | |
351 | out.writeString(self.address, len(self.address)) |
|
351 | out.writeString(self.address, len(self.address)) | |
352 |
|
352 | |||
353 | def __eq__(self, other): |
|
353 | def __eq__(self, other): | |
354 | """Tests equality on address""" |
|
354 | """Tests equality on address""" | |
355 | if isinstance(other, DNSAddress): |
|
355 | if isinstance(other, DNSAddress): | |
356 | return self.address == other.address |
|
356 | return self.address == other.address | |
357 | return 0 |
|
357 | return 0 | |
358 |
|
358 | |||
359 | def __repr__(self): |
|
359 | def __repr__(self): | |
360 | """String representation""" |
|
360 | """String representation""" | |
361 | try: |
|
361 | try: | |
362 | return socket.inet_ntoa(self.address) |
|
362 | return socket.inet_ntoa(self.address) | |
363 | except Exception: |
|
363 | except Exception: | |
364 | return self.address |
|
364 | return self.address | |
365 |
|
365 | |||
366 | class DNSHinfo(DNSRecord): |
|
366 | class DNSHinfo(DNSRecord): | |
367 | """A DNS host information record""" |
|
367 | """A DNS host information record""" | |
368 |
|
368 | |||
369 | def __init__(self, name, type, clazz, ttl, cpu, os): |
|
369 | def __init__(self, name, type, clazz, ttl, cpu, os): | |
370 | DNSRecord.__init__(self, name, type, clazz, ttl) |
|
370 | DNSRecord.__init__(self, name, type, clazz, ttl) | |
371 | self.cpu = cpu |
|
371 | self.cpu = cpu | |
372 | self.os = os |
|
372 | self.os = os | |
373 |
|
373 | |||
374 | def write(self, out): |
|
374 | def write(self, out): | |
375 | """Used in constructing an outgoing packet""" |
|
375 | """Used in constructing an outgoing packet""" | |
376 | out.writeString(self.cpu, len(self.cpu)) |
|
376 | out.writeString(self.cpu, len(self.cpu)) | |
377 | out.writeString(self.os, len(self.os)) |
|
377 | out.writeString(self.os, len(self.os)) | |
378 |
|
378 | |||
379 | def __eq__(self, other): |
|
379 | def __eq__(self, other): | |
380 | """Tests equality on cpu and os""" |
|
380 | """Tests equality on cpu and os""" | |
381 | if isinstance(other, DNSHinfo): |
|
381 | if isinstance(other, DNSHinfo): | |
382 | return self.cpu == other.cpu and self.os == other.os |
|
382 | return self.cpu == other.cpu and self.os == other.os | |
383 | return 0 |
|
383 | return 0 | |
384 |
|
384 | |||
385 | def __repr__(self): |
|
385 | def __repr__(self): | |
386 | """String representation""" |
|
386 | """String representation""" | |
387 | return self.cpu + " " + self.os |
|
387 | return self.cpu + " " + self.os | |
388 |
|
388 | |||
389 | class DNSPointer(DNSRecord): |
|
389 | class DNSPointer(DNSRecord): | |
390 | """A DNS pointer record""" |
|
390 | """A DNS pointer record""" | |
391 |
|
391 | |||
392 | def __init__(self, name, type, clazz, ttl, alias): |
|
392 | def __init__(self, name, type, clazz, ttl, alias): | |
393 | DNSRecord.__init__(self, name, type, clazz, ttl) |
|
393 | DNSRecord.__init__(self, name, type, clazz, ttl) | |
394 | self.alias = alias |
|
394 | self.alias = alias | |
395 |
|
395 | |||
396 | def write(self, out): |
|
396 | def write(self, out): | |
397 | """Used in constructing an outgoing packet""" |
|
397 | """Used in constructing an outgoing packet""" | |
398 | out.writeName(self.alias) |
|
398 | out.writeName(self.alias) | |
399 |
|
399 | |||
400 | def __eq__(self, other): |
|
400 | def __eq__(self, other): | |
401 | """Tests equality on alias""" |
|
401 | """Tests equality on alias""" | |
402 | if isinstance(other, DNSPointer): |
|
402 | if isinstance(other, DNSPointer): | |
403 | return self.alias == other.alias |
|
403 | return self.alias == other.alias | |
404 | return 0 |
|
404 | return 0 | |
405 |
|
405 | |||
406 | def __repr__(self): |
|
406 | def __repr__(self): | |
407 | """String representation""" |
|
407 | """String representation""" | |
408 | return self.toString(self.alias) |
|
408 | return self.toString(self.alias) | |
409 |
|
409 | |||
410 | class DNSText(DNSRecord): |
|
410 | class DNSText(DNSRecord): | |
411 | """A DNS text record""" |
|
411 | """A DNS text record""" | |
412 |
|
412 | |||
413 | def __init__(self, name, type, clazz, ttl, text): |
|
413 | def __init__(self, name, type, clazz, ttl, text): | |
414 | DNSRecord.__init__(self, name, type, clazz, ttl) |
|
414 | DNSRecord.__init__(self, name, type, clazz, ttl) | |
415 | self.text = text |
|
415 | self.text = text | |
416 |
|
416 | |||
417 | def write(self, out): |
|
417 | def write(self, out): | |
418 | """Used in constructing an outgoing packet""" |
|
418 | """Used in constructing an outgoing packet""" | |
419 | out.writeString(self.text, len(self.text)) |
|
419 | out.writeString(self.text, len(self.text)) | |
420 |
|
420 | |||
421 | def __eq__(self, other): |
|
421 | def __eq__(self, other): | |
422 | """Tests equality on text""" |
|
422 | """Tests equality on text""" | |
423 | if isinstance(other, DNSText): |
|
423 | if isinstance(other, DNSText): | |
424 | return self.text == other.text |
|
424 | return self.text == other.text | |
425 | return 0 |
|
425 | return 0 | |
426 |
|
426 | |||
427 | def __repr__(self): |
|
427 | def __repr__(self): | |
428 | """String representation""" |
|
428 | """String representation""" | |
429 | if len(self.text) > 10: |
|
429 | if len(self.text) > 10: | |
430 | return self.toString(self.text[:7] + "...") |
|
430 | return self.toString(self.text[:7] + "...") | |
431 | else: |
|
431 | else: | |
432 | return self.toString(self.text) |
|
432 | return self.toString(self.text) | |
433 |
|
433 | |||
434 | class DNSService(DNSRecord): |
|
434 | class DNSService(DNSRecord): | |
435 | """A DNS service record""" |
|
435 | """A DNS service record""" | |
436 |
|
436 | |||
437 | def __init__(self, name, type, clazz, ttl, priority, weight, port, server): |
|
437 | def __init__(self, name, type, clazz, ttl, priority, weight, port, server): | |
438 | DNSRecord.__init__(self, name, type, clazz, ttl) |
|
438 | DNSRecord.__init__(self, name, type, clazz, ttl) | |
439 | self.priority = priority |
|
439 | self.priority = priority | |
440 | self.weight = weight |
|
440 | self.weight = weight | |
441 | self.port = port |
|
441 | self.port = port | |
442 | self.server = server |
|
442 | self.server = server | |
443 |
|
443 | |||
444 | def write(self, out): |
|
444 | def write(self, out): | |
445 | """Used in constructing an outgoing packet""" |
|
445 | """Used in constructing an outgoing packet""" | |
446 | out.writeShort(self.priority) |
|
446 | out.writeShort(self.priority) | |
447 | out.writeShort(self.weight) |
|
447 | out.writeShort(self.weight) | |
448 | out.writeShort(self.port) |
|
448 | out.writeShort(self.port) | |
449 | out.writeName(self.server) |
|
449 | out.writeName(self.server) | |
450 |
|
450 | |||
451 | def __eq__(self, other): |
|
451 | def __eq__(self, other): | |
452 | """Tests equality on priority, weight, port and server""" |
|
452 | """Tests equality on priority, weight, port and server""" | |
453 | if isinstance(other, DNSService): |
|
453 | if isinstance(other, DNSService): | |
454 | return self.priority == other.priority and self.weight == other.weight and self.port == other.port and self.server == other.server |
|
454 | return self.priority == other.priority and self.weight == other.weight and self.port == other.port and self.server == other.server | |
455 | return 0 |
|
455 | return 0 | |
456 |
|
456 | |||
457 | def __repr__(self): |
|
457 | def __repr__(self): | |
458 | """String representation""" |
|
458 | """String representation""" | |
459 | return self.toString("%s:%s" % (self.server, self.port)) |
|
459 | return self.toString("%s:%s" % (self.server, self.port)) | |
460 |
|
460 | |||
461 | class DNSIncoming(object): |
|
461 | class DNSIncoming(object): | |
462 | """Object representation of an incoming DNS packet""" |
|
462 | """Object representation of an incoming DNS packet""" | |
463 |
|
463 | |||
464 | def __init__(self, data): |
|
464 | def __init__(self, data): | |
465 | """Constructor from string holding bytes of packet""" |
|
465 | """Constructor from string holding bytes of packet""" | |
466 | self.offset = 0 |
|
466 | self.offset = 0 | |
467 | self.data = data |
|
467 | self.data = data | |
468 | self.questions = [] |
|
468 | self.questions = [] | |
469 | self.answers = [] |
|
469 | self.answers = [] | |
470 | self.numQuestions = 0 |
|
470 | self.numQuestions = 0 | |
471 | self.numAnswers = 0 |
|
471 | self.numAnswers = 0 | |
472 | self.numAuthorities = 0 |
|
472 | self.numAuthorities = 0 | |
473 | self.numAdditionals = 0 |
|
473 | self.numAdditionals = 0 | |
474 |
|
474 | |||
475 | self.readHeader() |
|
475 | self.readHeader() | |
476 | self.readQuestions() |
|
476 | self.readQuestions() | |
477 | self.readOthers() |
|
477 | self.readOthers() | |
478 |
|
478 | |||
479 | def readHeader(self): |
|
479 | def readHeader(self): | |
480 | """Reads header portion of packet""" |
|
480 | """Reads header portion of packet""" | |
481 | format = '!HHHHHH' |
|
481 | format = '!HHHHHH' | |
482 | length = struct.calcsize(format) |
|
482 | length = struct.calcsize(format) | |
483 | info = struct.unpack(format, self.data[self.offset:self.offset+length]) |
|
483 | info = struct.unpack(format, self.data[self.offset:self.offset+length]) | |
484 | self.offset += length |
|
484 | self.offset += length | |
485 |
|
485 | |||
486 | self.id = info[0] |
|
486 | self.id = info[0] | |
487 | self.flags = info[1] |
|
487 | self.flags = info[1] | |
488 | self.numQuestions = info[2] |
|
488 | self.numQuestions = info[2] | |
489 | self.numAnswers = info[3] |
|
489 | self.numAnswers = info[3] | |
490 | self.numAuthorities = info[4] |
|
490 | self.numAuthorities = info[4] | |
491 | self.numAdditionals = info[5] |
|
491 | self.numAdditionals = info[5] | |
492 |
|
492 | |||
493 | def readQuestions(self): |
|
493 | def readQuestions(self): | |
494 | """Reads questions section of packet""" |
|
494 | """Reads questions section of packet""" | |
495 | format = '!HH' |
|
495 | format = '!HH' | |
496 | length = struct.calcsize(format) |
|
496 | length = struct.calcsize(format) | |
497 | for i in range(0, self.numQuestions): |
|
497 | for i in range(0, self.numQuestions): | |
498 | name = self.readName() |
|
498 | name = self.readName() | |
499 | info = struct.unpack(format, self.data[self.offset:self.offset+length]) |
|
499 | info = struct.unpack(format, self.data[self.offset:self.offset+length]) | |
500 | self.offset += length |
|
500 | self.offset += length | |
501 |
|
501 | |||
502 | try: |
|
502 | try: | |
503 | question = DNSQuestion(name, info[0], info[1]) |
|
503 | question = DNSQuestion(name, info[0], info[1]) | |
504 | self.questions.append(question) |
|
504 | self.questions.append(question) | |
505 | except NonLocalNameException: |
|
505 | except NonLocalNameException: | |
506 | pass |
|
506 | pass | |
507 |
|
507 | |||
508 | def readInt(self): |
|
508 | def readInt(self): | |
509 | """Reads an integer from the packet""" |
|
509 | """Reads an integer from the packet""" | |
510 | format = '!I' |
|
510 | format = '!I' | |
511 | length = struct.calcsize(format) |
|
511 | length = struct.calcsize(format) | |
512 | info = struct.unpack(format, self.data[self.offset:self.offset+length]) |
|
512 | info = struct.unpack(format, self.data[self.offset:self.offset+length]) | |
513 | self.offset += length |
|
513 | self.offset += length | |
514 | return info[0] |
|
514 | return info[0] | |
515 |
|
515 | |||
516 | def readCharacterString(self): |
|
516 | def readCharacterString(self): | |
517 | """Reads a character string from the packet""" |
|
517 | """Reads a character string from the packet""" | |
518 | length = ord(self.data[self.offset]) |
|
518 | length = ord(self.data[self.offset]) | |
519 | self.offset += 1 |
|
519 | self.offset += 1 | |
520 | return self.readString(length) |
|
520 | return self.readString(length) | |
521 |
|
521 | |||
522 | def readString(self, len): |
|
522 | def readString(self, len): | |
523 | """Reads a string of a given length from the packet""" |
|
523 | """Reads a string of a given length from the packet""" | |
524 | format = '!' + str(len) + 's' |
|
524 | format = '!' + str(len) + 's' | |
525 | length = struct.calcsize(format) |
|
525 | length = struct.calcsize(format) | |
526 | info = struct.unpack(format, self.data[self.offset:self.offset+length]) |
|
526 | info = struct.unpack(format, self.data[self.offset:self.offset+length]) | |
527 | self.offset += length |
|
527 | self.offset += length | |
528 | return info[0] |
|
528 | return info[0] | |
529 |
|
529 | |||
530 | def readUnsignedShort(self): |
|
530 | def readUnsignedShort(self): | |
531 | """Reads an unsigned short from the packet""" |
|
531 | """Reads an unsigned short from the packet""" | |
532 | format = '!H' |
|
532 | format = '!H' | |
533 | length = struct.calcsize(format) |
|
533 | length = struct.calcsize(format) | |
534 | info = struct.unpack(format, self.data[self.offset:self.offset+length]) |
|
534 | info = struct.unpack(format, self.data[self.offset:self.offset+length]) | |
535 | self.offset += length |
|
535 | self.offset += length | |
536 | return info[0] |
|
536 | return info[0] | |
537 |
|
537 | |||
538 | def readOthers(self): |
|
538 | def readOthers(self): | |
539 | """Reads the answers, authorities and additionals section of the packet""" |
|
539 | """Reads the answers, authorities and additionals section of the packet""" | |
540 | format = '!HHiH' |
|
540 | format = '!HHiH' | |
541 | length = struct.calcsize(format) |
|
541 | length = struct.calcsize(format) | |
542 | n = self.numAnswers + self.numAuthorities + self.numAdditionals |
|
542 | n = self.numAnswers + self.numAuthorities + self.numAdditionals | |
543 | for i in range(0, n): |
|
543 | for i in range(0, n): | |
544 | domain = self.readName() |
|
544 | domain = self.readName() | |
545 | info = struct.unpack(format, self.data[self.offset:self.offset+length]) |
|
545 | info = struct.unpack(format, self.data[self.offset:self.offset+length]) | |
546 | self.offset += length |
|
546 | self.offset += length | |
547 |
|
547 | |||
548 | rec = None |
|
548 | rec = None | |
549 | if info[0] == _TYPE_A: |
|
549 | if info[0] == _TYPE_A: | |
550 | rec = DNSAddress(domain, info[0], info[1], info[2], self.readString(4)) |
|
550 | rec = DNSAddress(domain, info[0], info[1], info[2], self.readString(4)) | |
551 | elif info[0] == _TYPE_CNAME or info[0] == _TYPE_PTR: |
|
551 | elif info[0] == _TYPE_CNAME or info[0] == _TYPE_PTR: | |
552 | rec = DNSPointer(domain, info[0], info[1], info[2], self.readName()) |
|
552 | rec = DNSPointer(domain, info[0], info[1], info[2], self.readName()) | |
553 | elif info[0] == _TYPE_TXT: |
|
553 | elif info[0] == _TYPE_TXT: | |
554 | rec = DNSText(domain, info[0], info[1], info[2], self.readString(info[3])) |
|
554 | rec = DNSText(domain, info[0], info[1], info[2], self.readString(info[3])) | |
555 | elif info[0] == _TYPE_SRV: |
|
555 | elif info[0] == _TYPE_SRV: | |
556 | rec = DNSService(domain, info[0], info[1], info[2], self.readUnsignedShort(), self.readUnsignedShort(), self.readUnsignedShort(), self.readName()) |
|
556 | rec = DNSService(domain, info[0], info[1], info[2], self.readUnsignedShort(), self.readUnsignedShort(), self.readUnsignedShort(), self.readName()) | |
557 | elif info[0] == _TYPE_HINFO: |
|
557 | elif info[0] == _TYPE_HINFO: | |
558 | rec = DNSHinfo(domain, info[0], info[1], info[2], self.readCharacterString(), self.readCharacterString()) |
|
558 | rec = DNSHinfo(domain, info[0], info[1], info[2], self.readCharacterString(), self.readCharacterString()) | |
559 | elif info[0] == _TYPE_AAAA: |
|
559 | elif info[0] == _TYPE_AAAA: | |
560 | rec = DNSAddress(domain, info[0], info[1], info[2], self.readString(16)) |
|
560 | rec = DNSAddress(domain, info[0], info[1], info[2], self.readString(16)) | |
561 | else: |
|
561 | else: | |
562 | # Try to ignore types we don't know about |
|
562 | # Try to ignore types we don't know about | |
563 | # this may mean the rest of the name is |
|
563 | # this may mean the rest of the name is | |
564 | # unable to be parsed, and may show errors |
|
564 | # unable to be parsed, and may show errors | |
565 | # so this is left for debugging. New types |
|
565 | # so this is left for debugging. New types | |
566 | # encountered need to be parsed properly. |
|
566 | # encountered need to be parsed properly. | |
567 | # |
|
567 | # | |
568 | #print "UNKNOWN TYPE = " + str(info[0]) |
|
568 | #print "UNKNOWN TYPE = " + str(info[0]) | |
569 | #raise BadTypeInNameException |
|
569 | #raise BadTypeInNameException | |
570 | self.offset += info[3] |
|
570 | self.offset += info[3] | |
571 |
|
571 | |||
572 | if rec is not None: |
|
572 | if rec is not None: | |
573 | self.answers.append(rec) |
|
573 | self.answers.append(rec) | |
574 |
|
574 | |||
575 | def isQuery(self): |
|
575 | def isQuery(self): | |
576 | """Returns true if this is a query""" |
|
576 | """Returns true if this is a query""" | |
577 | return (self.flags & _FLAGS_QR_MASK) == _FLAGS_QR_QUERY |
|
577 | return (self.flags & _FLAGS_QR_MASK) == _FLAGS_QR_QUERY | |
578 |
|
578 | |||
579 | def isResponse(self): |
|
579 | def isResponse(self): | |
580 | """Returns true if this is a response""" |
|
580 | """Returns true if this is a response""" | |
581 | return (self.flags & _FLAGS_QR_MASK) == _FLAGS_QR_RESPONSE |
|
581 | return (self.flags & _FLAGS_QR_MASK) == _FLAGS_QR_RESPONSE | |
582 |
|
582 | |||
583 | def readUTF(self, offset, len): |
|
583 | def readUTF(self, offset, len): | |
584 | """Reads a UTF-8 string of a given length from the packet""" |
|
584 | """Reads a UTF-8 string of a given length from the packet""" | |
585 | return self.data[offset:offset+len].decode('utf-8') |
|
585 | return self.data[offset:offset+len].decode('utf-8') | |
586 |
|
586 | |||
587 | def readName(self): |
|
587 | def readName(self): | |
588 | """Reads a domain name from the packet""" |
|
588 | """Reads a domain name from the packet""" | |
589 | result = '' |
|
589 | result = '' | |
590 | off = self.offset |
|
590 | off = self.offset | |
591 | next = -1 |
|
591 | next = -1 | |
592 | first = off |
|
592 | first = off | |
593 |
|
593 | |||
594 | while True: |
|
594 | while True: | |
595 | len = ord(self.data[off]) |
|
595 | len = ord(self.data[off]) | |
596 | off += 1 |
|
596 | off += 1 | |
597 | if len == 0: |
|
597 | if len == 0: | |
598 | break |
|
598 | break | |
599 | t = len & 0xC0 |
|
599 | t = len & 0xC0 | |
600 | if t == 0x00: |
|
600 | if t == 0x00: | |
601 | result = ''.join((result, self.readUTF(off, len) + '.')) |
|
601 | result = ''.join((result, self.readUTF(off, len) + '.')) | |
602 | off += len |
|
602 | off += len | |
603 | elif t == 0xC0: |
|
603 | elif t == 0xC0: | |
604 | if next < 0: |
|
604 | if next < 0: | |
605 | next = off + 1 |
|
605 | next = off + 1 | |
606 | off = ((len & 0x3F) << 8) | ord(self.data[off]) |
|
606 | off = ((len & 0x3F) << 8) | ord(self.data[off]) | |
607 | if off >= first: |
|
607 | if off >= first: | |
608 | raise BadDomainNameCircular(off) |
|
608 | raise BadDomainNameCircular(off) | |
609 | first = off |
|
609 | first = off | |
610 | else: |
|
610 | else: | |
611 | raise BadDomainName(off) |
|
611 | raise BadDomainName(off) | |
612 |
|
612 | |||
613 | if next >= 0: |
|
613 | if next >= 0: | |
614 | self.offset = next |
|
614 | self.offset = next | |
615 | else: |
|
615 | else: | |
616 | self.offset = off |
|
616 | self.offset = off | |
617 |
|
617 | |||
618 | return result |
|
618 | return result | |
619 |
|
619 | |||
620 |
|
620 | |||
621 | class DNSOutgoing(object): |
|
621 | class DNSOutgoing(object): | |
622 | """Object representation of an outgoing packet""" |
|
622 | """Object representation of an outgoing packet""" | |
623 |
|
623 | |||
624 | def __init__(self, flags, multicast = 1): |
|
624 | def __init__(self, flags, multicast = 1): | |
625 | self.finished = 0 |
|
625 | self.finished = 0 | |
626 | self.id = 0 |
|
626 | self.id = 0 | |
627 | self.multicast = multicast |
|
627 | self.multicast = multicast | |
628 | self.flags = flags |
|
628 | self.flags = flags | |
629 | self.names = {} |
|
629 | self.names = {} | |
630 | self.data = [] |
|
630 | self.data = [] | |
631 | self.size = 12 |
|
631 | self.size = 12 | |
632 |
|
632 | |||
633 | self.questions = [] |
|
633 | self.questions = [] | |
634 | self.answers = [] |
|
634 | self.answers = [] | |
635 | self.authorities = [] |
|
635 | self.authorities = [] | |
636 | self.additionals = [] |
|
636 | self.additionals = [] | |
637 |
|
637 | |||
638 | def addQuestion(self, record): |
|
638 | def addQuestion(self, record): | |
639 | """Adds a question""" |
|
639 | """Adds a question""" | |
640 | self.questions.append(record) |
|
640 | self.questions.append(record) | |
641 |
|
641 | |||
642 | def addAnswer(self, inp, record): |
|
642 | def addAnswer(self, inp, record): | |
643 | """Adds an answer""" |
|
643 | """Adds an answer""" | |
644 | if not record.suppressedBy(inp): |
|
644 | if not record.suppressedBy(inp): | |
645 | self.addAnswerAtTime(record, 0) |
|
645 | self.addAnswerAtTime(record, 0) | |
646 |
|
646 | |||
647 | def addAnswerAtTime(self, record, now): |
|
647 | def addAnswerAtTime(self, record, now): | |
648 | """Adds an answer if if does not expire by a certain time""" |
|
648 | """Adds an answer if if does not expire by a certain time""" | |
649 | if record is not None: |
|
649 | if record is not None: | |
650 | if now == 0 or not record.isExpired(now): |
|
650 | if now == 0 or not record.isExpired(now): | |
651 | self.answers.append((record, now)) |
|
651 | self.answers.append((record, now)) | |
652 |
|
652 | |||
653 | def addAuthoritativeAnswer(self, record): |
|
653 | def addAuthoritativeAnswer(self, record): | |
654 | """Adds an authoritative answer""" |
|
654 | """Adds an authoritative answer""" | |
655 | self.authorities.append(record) |
|
655 | self.authorities.append(record) | |
656 |
|
656 | |||
657 | def addAdditionalAnswer(self, record): |
|
657 | def addAdditionalAnswer(self, record): | |
658 | """Adds an additional answer""" |
|
658 | """Adds an additional answer""" | |
659 | self.additionals.append(record) |
|
659 | self.additionals.append(record) | |
660 |
|
660 | |||
661 | def writeByte(self, value): |
|
661 | def writeByte(self, value): | |
662 | """Writes a single byte to the packet""" |
|
662 | """Writes a single byte to the packet""" | |
663 | format = '!c' |
|
663 | format = '!c' | |
664 | self.data.append(struct.pack(format, chr(value))) |
|
664 | self.data.append(struct.pack(format, chr(value))) | |
665 | self.size += 1 |
|
665 | self.size += 1 | |
666 |
|
666 | |||
667 | def insertShort(self, index, value): |
|
667 | def insertShort(self, index, value): | |
668 | """Inserts an unsigned short in a certain position in the packet""" |
|
668 | """Inserts an unsigned short in a certain position in the packet""" | |
669 | format = '!H' |
|
669 | format = '!H' | |
670 | self.data.insert(index, struct.pack(format, value)) |
|
670 | self.data.insert(index, struct.pack(format, value)) | |
671 | self.size += 2 |
|
671 | self.size += 2 | |
672 |
|
672 | |||
673 | def writeShort(self, value): |
|
673 | def writeShort(self, value): | |
674 | """Writes an unsigned short to the packet""" |
|
674 | """Writes an unsigned short to the packet""" | |
675 | format = '!H' |
|
675 | format = '!H' | |
676 | self.data.append(struct.pack(format, value)) |
|
676 | self.data.append(struct.pack(format, value)) | |
677 | self.size += 2 |
|
677 | self.size += 2 | |
678 |
|
678 | |||
679 | def writeInt(self, value): |
|
679 | def writeInt(self, value): | |
680 | """Writes an unsigned integer to the packet""" |
|
680 | """Writes an unsigned integer to the packet""" | |
681 | format = '!I' |
|
681 | format = '!I' | |
682 | self.data.append(struct.pack(format, int(value))) |
|
682 | self.data.append(struct.pack(format, int(value))) | |
683 | self.size += 4 |
|
683 | self.size += 4 | |
684 |
|
684 | |||
685 | def writeString(self, value, length): |
|
685 | def writeString(self, value, length): | |
686 | """Writes a string to the packet""" |
|
686 | """Writes a string to the packet""" | |
687 | format = '!' + str(length) + 's' |
|
687 | format = '!' + str(length) + 's' | |
688 | self.data.append(struct.pack(format, value)) |
|
688 | self.data.append(struct.pack(format, value)) | |
689 | self.size += length |
|
689 | self.size += length | |
690 |
|
690 | |||
691 | def writeUTF(self, s): |
|
691 | def writeUTF(self, s): | |
692 | """Writes a UTF-8 string of a given length to the packet""" |
|
692 | """Writes a UTF-8 string of a given length to the packet""" | |
693 | utfstr = s.encode('utf-8') |
|
693 | utfstr = s.encode('utf-8') | |
694 | length = len(utfstr) |
|
694 | length = len(utfstr) | |
695 | if length > 64: |
|
695 | if length > 64: | |
696 | raise NamePartTooLongException |
|
696 | raise NamePartTooLongException | |
697 | self.writeByte(length) |
|
697 | self.writeByte(length) | |
698 | self.writeString(utfstr, length) |
|
698 | self.writeString(utfstr, length) | |
699 |
|
699 | |||
700 | def writeName(self, name): |
|
700 | def writeName(self, name): | |
701 | """Writes a domain name to the packet""" |
|
701 | """Writes a domain name to the packet""" | |
702 |
|
702 | |||
703 | try: |
|
703 | try: | |
704 | # Find existing instance of this name in packet |
|
704 | # Find existing instance of this name in packet | |
705 | # |
|
705 | # | |
706 | index = self.names[name] |
|
706 | index = self.names[name] | |
707 | except KeyError: |
|
707 | except KeyError: | |
708 | # No record of this name already, so write it |
|
708 | # No record of this name already, so write it | |
709 | # out as normal, recording the location of the name |
|
709 | # out as normal, recording the location of the name | |
710 | # for future pointers to it. |
|
710 | # for future pointers to it. | |
711 | # |
|
711 | # | |
712 | self.names[name] = self.size |
|
712 | self.names[name] = self.size | |
713 | parts = name.split('.') |
|
713 | parts = name.split('.') | |
714 | if parts[-1] == '': |
|
714 | if parts[-1] == '': | |
715 | parts = parts[:-1] |
|
715 | parts = parts[:-1] | |
716 | for part in parts: |
|
716 | for part in parts: | |
717 | self.writeUTF(part) |
|
717 | self.writeUTF(part) | |
718 | self.writeByte(0) |
|
718 | self.writeByte(0) | |
719 | return |
|
719 | return | |
720 |
|
720 | |||
721 | # An index was found, so write a pointer to it |
|
721 | # An index was found, so write a pointer to it | |
722 | # |
|
722 | # | |
723 | self.writeByte((index >> 8) | 0xC0) |
|
723 | self.writeByte((index >> 8) | 0xC0) | |
724 | self.writeByte(index) |
|
724 | self.writeByte(index) | |
725 |
|
725 | |||
726 | def writeQuestion(self, question): |
|
726 | def writeQuestion(self, question): | |
727 | """Writes a question to the packet""" |
|
727 | """Writes a question to the packet""" | |
728 | self.writeName(question.name) |
|
728 | self.writeName(question.name) | |
729 | self.writeShort(question.type) |
|
729 | self.writeShort(question.type) | |
730 | self.writeShort(question.clazz) |
|
730 | self.writeShort(question.clazz) | |
731 |
|
731 | |||
732 | def writeRecord(self, record, now): |
|
732 | def writeRecord(self, record, now): | |
733 | """Writes a record (answer, authoritative answer, additional) to |
|
733 | """Writes a record (answer, authoritative answer, additional) to | |
734 | the packet""" |
|
734 | the packet""" | |
735 | self.writeName(record.name) |
|
735 | self.writeName(record.name) | |
736 | self.writeShort(record.type) |
|
736 | self.writeShort(record.type) | |
737 | if record.unique and self.multicast: |
|
737 | if record.unique and self.multicast: | |
738 | self.writeShort(record.clazz | _CLASS_UNIQUE) |
|
738 | self.writeShort(record.clazz | _CLASS_UNIQUE) | |
739 | else: |
|
739 | else: | |
740 | self.writeShort(record.clazz) |
|
740 | self.writeShort(record.clazz) | |
741 | if now == 0: |
|
741 | if now == 0: | |
742 | self.writeInt(record.ttl) |
|
742 | self.writeInt(record.ttl) | |
743 | else: |
|
743 | else: | |
744 | self.writeInt(record.getRemainingTTL(now)) |
|
744 | self.writeInt(record.getRemainingTTL(now)) | |
745 | index = len(self.data) |
|
745 | index = len(self.data) | |
746 | # Adjust size for the short we will write before this record |
|
746 | # Adjust size for the short we will write before this record | |
747 | # |
|
747 | # | |
748 | self.size += 2 |
|
748 | self.size += 2 | |
749 | record.write(self) |
|
749 | record.write(self) | |
750 | self.size -= 2 |
|
750 | self.size -= 2 | |
751 |
|
751 | |||
752 | length = len(''.join(self.data[index:])) |
|
752 | length = len(''.join(self.data[index:])) | |
753 | self.insertShort(index, length) # Here is the short we adjusted for |
|
753 | self.insertShort(index, length) # Here is the short we adjusted for | |
754 |
|
754 | |||
755 | def packet(self): |
|
755 | def packet(self): | |
756 | """Returns a string containing the packet's bytes |
|
756 | """Returns a string containing the packet's bytes | |
757 |
|
757 | |||
758 | No further parts should be added to the packet once this |
|
758 | No further parts should be added to the packet once this | |
759 | is done.""" |
|
759 | is done.""" | |
760 | if not self.finished: |
|
760 | if not self.finished: | |
761 | self.finished = 1 |
|
761 | self.finished = 1 | |
762 | for question in self.questions: |
|
762 | for question in self.questions: | |
763 | self.writeQuestion(question) |
|
763 | self.writeQuestion(question) | |
764 | for answer, time in self.answers: |
|
764 | for answer, time in self.answers: | |
765 | self.writeRecord(answer, time) |
|
765 | self.writeRecord(answer, time) | |
766 | for authority in self.authorities: |
|
766 | for authority in self.authorities: | |
767 | self.writeRecord(authority, 0) |
|
767 | self.writeRecord(authority, 0) | |
768 | for additional in self.additionals: |
|
768 | for additional in self.additionals: | |
769 | self.writeRecord(additional, 0) |
|
769 | self.writeRecord(additional, 0) | |
770 |
|
770 | |||
771 | self.insertShort(0, len(self.additionals)) |
|
771 | self.insertShort(0, len(self.additionals)) | |
772 | self.insertShort(0, len(self.authorities)) |
|
772 | self.insertShort(0, len(self.authorities)) | |
773 | self.insertShort(0, len(self.answers)) |
|
773 | self.insertShort(0, len(self.answers)) | |
774 | self.insertShort(0, len(self.questions)) |
|
774 | self.insertShort(0, len(self.questions)) | |
775 | self.insertShort(0, self.flags) |
|
775 | self.insertShort(0, self.flags) | |
776 | if self.multicast: |
|
776 | if self.multicast: | |
777 | self.insertShort(0, 0) |
|
777 | self.insertShort(0, 0) | |
778 | else: |
|
778 | else: | |
779 | self.insertShort(0, self.id) |
|
779 | self.insertShort(0, self.id) | |
780 | return ''.join(self.data) |
|
780 | return ''.join(self.data) | |
781 |
|
781 | |||
782 |
|
782 | |||
783 | class DNSCache(object): |
|
783 | class DNSCache(object): | |
784 | """A cache of DNS entries""" |
|
784 | """A cache of DNS entries""" | |
785 |
|
785 | |||
786 | def __init__(self): |
|
786 | def __init__(self): | |
787 | self.cache = {} |
|
787 | self.cache = {} | |
788 |
|
788 | |||
789 | def add(self, entry): |
|
789 | def add(self, entry): | |
790 | """Adds an entry""" |
|
790 | """Adds an entry""" | |
791 | try: |
|
791 | try: | |
792 | list = self.cache[entry.key] |
|
792 | list = self.cache[entry.key] | |
793 | except KeyError: |
|
793 | except KeyError: | |
794 | list = self.cache[entry.key] = [] |
|
794 | list = self.cache[entry.key] = [] | |
795 | list.append(entry) |
|
795 | list.append(entry) | |
796 |
|
796 | |||
797 | def remove(self, entry): |
|
797 | def remove(self, entry): | |
798 | """Removes an entry""" |
|
798 | """Removes an entry""" | |
799 | try: |
|
799 | try: | |
800 | list = self.cache[entry.key] |
|
800 | list = self.cache[entry.key] | |
801 | list.remove(entry) |
|
801 | list.remove(entry) | |
802 | except KeyError: |
|
802 | except KeyError: | |
803 | pass |
|
803 | pass | |
804 |
|
804 | |||
805 | def get(self, entry): |
|
805 | def get(self, entry): | |
806 | """Gets an entry by key. Will return None if there is no |
|
806 | """Gets an entry by key. Will return None if there is no | |
807 | matching entry.""" |
|
807 | matching entry.""" | |
808 | try: |
|
808 | try: | |
809 | list = self.cache[entry.key] |
|
809 | list = self.cache[entry.key] | |
810 | return list[list.index(entry)] |
|
810 | return list[list.index(entry)] | |
811 | except (KeyError, ValueError): |
|
811 | except (KeyError, ValueError): | |
812 | return None |
|
812 | return None | |
813 |
|
813 | |||
814 | def getByDetails(self, name, type, clazz): |
|
814 | def getByDetails(self, name, type, clazz): | |
815 | """Gets an entry by details. Will return None if there is |
|
815 | """Gets an entry by details. Will return None if there is | |
816 | no matching entry.""" |
|
816 | no matching entry.""" | |
817 | entry = DNSEntry(name, type, clazz) |
|
817 | entry = DNSEntry(name, type, clazz) | |
818 | return self.get(entry) |
|
818 | return self.get(entry) | |
819 |
|
819 | |||
820 | def entriesWithName(self, name): |
|
820 | def entriesWithName(self, name): | |
821 | """Returns a list of entries whose key matches the name.""" |
|
821 | """Returns a list of entries whose key matches the name.""" | |
822 | try: |
|
822 | try: | |
823 | return self.cache[name] |
|
823 | return self.cache[name] | |
824 | except KeyError: |
|
824 | except KeyError: | |
825 | return [] |
|
825 | return [] | |
826 |
|
826 | |||
827 | def entries(self): |
|
827 | def entries(self): | |
828 | """Returns a list of all entries""" |
|
828 | """Returns a list of all entries""" | |
829 | def add(x, y): return x+y |
|
829 | def add(x, y): return x+y | |
830 | try: |
|
830 | try: | |
831 | return reduce(add, self.cache.values()) |
|
831 | return reduce(add, self.cache.values()) | |
832 | except Exception: |
|
832 | except Exception: | |
833 | return [] |
|
833 | return [] | |
834 |
|
834 | |||
835 |
|
835 | |||
836 | class Engine(threading.Thread): |
|
836 | class Engine(threading.Thread): | |
837 | """An engine wraps read access to sockets, allowing objects that |
|
837 | """An engine wraps read access to sockets, allowing objects that | |
838 | need to receive data from sockets to be called back when the |
|
838 | need to receive data from sockets to be called back when the | |
839 | sockets are ready. |
|
839 | sockets are ready. | |
840 |
|
840 | |||
841 | A reader needs a handle_read() method, which is called when the socket |
|
841 | A reader needs a handle_read() method, which is called when the socket | |
842 | it is interested in is ready for reading. |
|
842 | it is interested in is ready for reading. | |
843 |
|
843 | |||
844 | Writers are not implemented here, because we only send short |
|
844 | Writers are not implemented here, because we only send short | |
845 | packets. |
|
845 | packets. | |
846 | """ |
|
846 | """ | |
847 |
|
847 | |||
848 | def __init__(self, zeroconf): |
|
848 | def __init__(self, zeroconf): | |
849 | threading.Thread.__init__(self) |
|
849 | threading.Thread.__init__(self) | |
850 | self.zeroconf = zeroconf |
|
850 | self.zeroconf = zeroconf | |
851 | self.readers = {} # maps socket to reader |
|
851 | self.readers = {} # maps socket to reader | |
852 | self.timeout = 5 |
|
852 | self.timeout = 5 | |
853 | self.condition = threading.Condition() |
|
853 | self.condition = threading.Condition() | |
854 | self.start() |
|
854 | self.start() | |
855 |
|
855 | |||
856 | def run(self): |
|
856 | def run(self): | |
857 | while not globals()['_GLOBAL_DONE']: |
|
857 | while not globals()['_GLOBAL_DONE']: | |
858 | rs = self.getReaders() |
|
858 | rs = self.getReaders() | |
859 | if len(rs) == 0: |
|
859 | if len(rs) == 0: | |
860 | # No sockets to manage, but we wait for the timeout |
|
860 | # No sockets to manage, but we wait for the timeout | |
861 | # or addition of a socket |
|
861 | # or addition of a socket | |
862 | # |
|
862 | # | |
863 | self.condition.acquire() |
|
863 | self.condition.acquire() | |
864 | self.condition.wait(self.timeout) |
|
864 | self.condition.wait(self.timeout) | |
865 | self.condition.release() |
|
865 | self.condition.release() | |
866 | else: |
|
866 | else: | |
867 | try: |
|
867 | try: | |
868 | rr, wr, er = select.select(rs, [], [], self.timeout) |
|
868 | rr, wr, er = select.select(rs, [], [], self.timeout) | |
869 | for socket in rr: |
|
869 | for socket in rr: | |
870 | try: |
|
870 | try: | |
871 | self.readers[socket].handle_read() |
|
871 | self.readers[socket].handle_read() | |
872 | except Exception: |
|
872 | except Exception: | |
873 | if not globals()['_GLOBAL_DONE']: |
|
873 | if not globals()['_GLOBAL_DONE']: | |
874 | traceback.print_exc() |
|
874 | traceback.print_exc() | |
875 | except Exception: |
|
875 | except Exception: | |
876 | pass |
|
876 | pass | |
877 |
|
877 | |||
878 | def getReaders(self): |
|
878 | def getReaders(self): | |
879 | self.condition.acquire() |
|
879 | self.condition.acquire() | |
880 | result = self.readers.keys() |
|
880 | result = self.readers.keys() | |
881 | self.condition.release() |
|
881 | self.condition.release() | |
882 | return result |
|
882 | return result | |
883 |
|
883 | |||
884 | def addReader(self, reader, socket): |
|
884 | def addReader(self, reader, socket): | |
885 | self.condition.acquire() |
|
885 | self.condition.acquire() | |
886 | self.readers[socket] = reader |
|
886 | self.readers[socket] = reader | |
887 | self.condition.notify() |
|
887 | self.condition.notify() | |
888 | self.condition.release() |
|
888 | self.condition.release() | |
889 |
|
889 | |||
890 | def delReader(self, socket): |
|
890 | def delReader(self, socket): | |
891 | self.condition.acquire() |
|
891 | self.condition.acquire() | |
892 | del(self.readers[socket]) |
|
892 | del(self.readers[socket]) | |
893 | self.condition.notify() |
|
893 | self.condition.notify() | |
894 | self.condition.release() |
|
894 | self.condition.release() | |
895 |
|
895 | |||
896 | def notify(self): |
|
896 | def notify(self): | |
897 | self.condition.acquire() |
|
897 | self.condition.acquire() | |
898 | self.condition.notify() |
|
898 | self.condition.notify() | |
899 | self.condition.release() |
|
899 | self.condition.release() | |
900 |
|
900 | |||
901 | class Listener(object): |
|
901 | class Listener(object): | |
902 | """A Listener is used by this module to listen on the multicast |
|
902 | """A Listener is used by this module to listen on the multicast | |
903 | group to which DNS messages are sent, allowing the implementation |
|
903 | group to which DNS messages are sent, allowing the implementation | |
904 | to cache information as it arrives. |
|
904 | to cache information as it arrives. | |
905 |
|
905 | |||
906 | It requires registration with an Engine object in order to have |
|
906 | It requires registration with an Engine object in order to have | |
907 | the read() method called when a socket is available for reading.""" |
|
907 | the read() method called when a socket is available for reading.""" | |
908 |
|
908 | |||
909 | def __init__(self, zeroconf): |
|
909 | def __init__(self, zeroconf): | |
910 | self.zeroconf = zeroconf |
|
910 | self.zeroconf = zeroconf | |
911 | self.zeroconf.engine.addReader(self, self.zeroconf.socket) |
|
911 | self.zeroconf.engine.addReader(self, self.zeroconf.socket) | |
912 |
|
912 | |||
913 | def handle_read(self): |
|
913 | def handle_read(self): | |
914 | data, (addr, port) = self.zeroconf.socket.recvfrom(_MAX_MSG_ABSOLUTE) |
|
914 | data, (addr, port) = self.zeroconf.socket.recvfrom(_MAX_MSG_ABSOLUTE) | |
915 | self.data = data |
|
915 | self.data = data | |
916 | msg = DNSIncoming(data) |
|
916 | msg = DNSIncoming(data) | |
917 | if msg.isQuery(): |
|
917 | if msg.isQuery(): | |
918 | # Always multicast responses |
|
918 | # Always multicast responses | |
919 | # |
|
919 | # | |
920 | if port == _MDNS_PORT: |
|
920 | if port == _MDNS_PORT: | |
921 | self.zeroconf.handleQuery(msg, _MDNS_ADDR, _MDNS_PORT) |
|
921 | self.zeroconf.handleQuery(msg, _MDNS_ADDR, _MDNS_PORT) | |
922 | # If it's not a multicast query, reply via unicast |
|
922 | # If it's not a multicast query, reply via unicast | |
923 | # and multicast |
|
923 | # and multicast | |
924 | # |
|
924 | # | |
925 | elif port == _DNS_PORT: |
|
925 | elif port == _DNS_PORT: | |
926 | self.zeroconf.handleQuery(msg, addr, port) |
|
926 | self.zeroconf.handleQuery(msg, addr, port) | |
927 | self.zeroconf.handleQuery(msg, _MDNS_ADDR, _MDNS_PORT) |
|
927 | self.zeroconf.handleQuery(msg, _MDNS_ADDR, _MDNS_PORT) | |
928 | else: |
|
928 | else: | |
929 | self.zeroconf.handleResponse(msg) |
|
929 | self.zeroconf.handleResponse(msg) | |
930 |
|
930 | |||
931 |
|
931 | |||
932 | class Reaper(threading.Thread): |
|
932 | class Reaper(threading.Thread): | |
933 | """A Reaper is used by this module to remove cache entries that |
|
933 | """A Reaper is used by this module to remove cache entries that | |
934 | have expired.""" |
|
934 | have expired.""" | |
935 |
|
935 | |||
936 | def __init__(self, zeroconf): |
|
936 | def __init__(self, zeroconf): | |
937 | threading.Thread.__init__(self) |
|
937 | threading.Thread.__init__(self) | |
938 | self.zeroconf = zeroconf |
|
938 | self.zeroconf = zeroconf | |
939 | self.start() |
|
939 | self.start() | |
940 |
|
940 | |||
941 | def run(self): |
|
941 | def run(self): | |
942 | while True: |
|
942 | while True: | |
943 | self.zeroconf.wait(10 * 1000) |
|
943 | self.zeroconf.wait(10 * 1000) | |
944 | if globals()['_GLOBAL_DONE']: |
|
944 | if globals()['_GLOBAL_DONE']: | |
945 | return |
|
945 | return | |
946 | now = currentTimeMillis() |
|
946 | now = currentTimeMillis() | |
947 | for record in self.zeroconf.cache.entries(): |
|
947 | for record in self.zeroconf.cache.entries(): | |
948 | if record.isExpired(now): |
|
948 | if record.isExpired(now): | |
949 | self.zeroconf.updateRecord(now, record) |
|
949 | self.zeroconf.updateRecord(now, record) | |
950 | self.zeroconf.cache.remove(record) |
|
950 | self.zeroconf.cache.remove(record) | |
951 |
|
951 | |||
952 |
|
952 | |||
953 | class ServiceBrowser(threading.Thread): |
|
953 | class ServiceBrowser(threading.Thread): | |
954 | """Used to browse for a service of a specific type. |
|
954 | """Used to browse for a service of a specific type. | |
955 |
|
955 | |||
956 | The listener object will have its addService() and |
|
956 | The listener object will have its addService() and | |
957 | removeService() methods called when this browser |
|
957 | removeService() methods called when this browser | |
958 | discovers changes in the services availability.""" |
|
958 | discovers changes in the services availability.""" | |
959 |
|
959 | |||
960 | def __init__(self, zeroconf, type, listener): |
|
960 | def __init__(self, zeroconf, type, listener): | |
961 | """Creates a browser for a specific type""" |
|
961 | """Creates a browser for a specific type""" | |
962 | threading.Thread.__init__(self) |
|
962 | threading.Thread.__init__(self) | |
963 | self.zeroconf = zeroconf |
|
963 | self.zeroconf = zeroconf | |
964 | self.type = type |
|
964 | self.type = type | |
965 | self.listener = listener |
|
965 | self.listener = listener | |
966 | self.services = {} |
|
966 | self.services = {} | |
967 | self.nextTime = currentTimeMillis() |
|
967 | self.nextTime = currentTimeMillis() | |
968 | self.delay = _BROWSER_TIME |
|
968 | self.delay = _BROWSER_TIME | |
969 | self.list = [] |
|
969 | self.list = [] | |
970 |
|
970 | |||
971 | self.done = 0 |
|
971 | self.done = 0 | |
972 |
|
972 | |||
973 | self.zeroconf.addListener(self, DNSQuestion(self.type, _TYPE_PTR, _CLASS_IN)) |
|
973 | self.zeroconf.addListener(self, DNSQuestion(self.type, _TYPE_PTR, _CLASS_IN)) | |
974 | self.start() |
|
974 | self.start() | |
975 |
|
975 | |||
976 | def updateRecord(self, zeroconf, now, record): |
|
976 | def updateRecord(self, zeroconf, now, record): | |
977 | """Callback invoked by Zeroconf when new information arrives. |
|
977 | """Callback invoked by Zeroconf when new information arrives. | |
978 |
|
978 | |||
979 | Updates information required by browser in the Zeroconf cache.""" |
|
979 | Updates information required by browser in the Zeroconf cache.""" | |
980 | if record.type == _TYPE_PTR and record.name == self.type: |
|
980 | if record.type == _TYPE_PTR and record.name == self.type: | |
981 | expired = record.isExpired(now) |
|
981 | expired = record.isExpired(now) | |
982 | try: |
|
982 | try: | |
983 | oldrecord = self.services[record.alias.lower()] |
|
983 | oldrecord = self.services[record.alias.lower()] | |
984 | if not expired: |
|
984 | if not expired: | |
985 | oldrecord.resetTTL(record) |
|
985 | oldrecord.resetTTL(record) | |
986 | else: |
|
986 | else: | |
987 | del(self.services[record.alias.lower()]) |
|
987 | del(self.services[record.alias.lower()]) | |
988 | callback = lambda x: self.listener.removeService(x, self.type, record.alias) |
|
988 | callback = lambda x: self.listener.removeService(x, self.type, record.alias) | |
989 | self.list.append(callback) |
|
989 | self.list.append(callback) | |
990 | return |
|
990 | return | |
991 | except Exception: |
|
991 | except Exception: | |
992 | if not expired: |
|
992 | if not expired: | |
993 | self.services[record.alias.lower()] = record |
|
993 | self.services[record.alias.lower()] = record | |
994 | callback = lambda x: self.listener.addService(x, self.type, record.alias) |
|
994 | callback = lambda x: self.listener.addService(x, self.type, record.alias) | |
995 | self.list.append(callback) |
|
995 | self.list.append(callback) | |
996 |
|
996 | |||
997 | expires = record.getExpirationTime(75) |
|
997 | expires = record.getExpirationTime(75) | |
998 | if expires < self.nextTime: |
|
998 | if expires < self.nextTime: | |
999 | self.nextTime = expires |
|
999 | self.nextTime = expires | |
1000 |
|
1000 | |||
1001 | def cancel(self): |
|
1001 | def cancel(self): | |
1002 | self.done = 1 |
|
1002 | self.done = 1 | |
1003 | self.zeroconf.notifyAll() |
|
1003 | self.zeroconf.notifyAll() | |
1004 |
|
1004 | |||
1005 | def run(self): |
|
1005 | def run(self): | |
1006 | while True: |
|
1006 | while True: | |
1007 | event = None |
|
1007 | event = None | |
1008 | now = currentTimeMillis() |
|
1008 | now = currentTimeMillis() | |
1009 | if len(self.list) == 0 and self.nextTime > now: |
|
1009 | if len(self.list) == 0 and self.nextTime > now: | |
1010 | self.zeroconf.wait(self.nextTime - now) |
|
1010 | self.zeroconf.wait(self.nextTime - now) | |
1011 | if globals()['_GLOBAL_DONE'] or self.done: |
|
1011 | if globals()['_GLOBAL_DONE'] or self.done: | |
1012 | return |
|
1012 | return | |
1013 | now = currentTimeMillis() |
|
1013 | now = currentTimeMillis() | |
1014 |
|
1014 | |||
1015 | if self.nextTime <= now: |
|
1015 | if self.nextTime <= now: | |
1016 | out = DNSOutgoing(_FLAGS_QR_QUERY) |
|
1016 | out = DNSOutgoing(_FLAGS_QR_QUERY) | |
1017 | out.addQuestion(DNSQuestion(self.type, _TYPE_PTR, _CLASS_IN)) |
|
1017 | out.addQuestion(DNSQuestion(self.type, _TYPE_PTR, _CLASS_IN)) | |
1018 | for record in self.services.values(): |
|
1018 | for record in self.services.values(): | |
1019 | if not record.isExpired(now): |
|
1019 | if not record.isExpired(now): | |
1020 | out.addAnswerAtTime(record, now) |
|
1020 | out.addAnswerAtTime(record, now) | |
1021 | self.zeroconf.send(out) |
|
1021 | self.zeroconf.send(out) | |
1022 | self.nextTime = now + self.delay |
|
1022 | self.nextTime = now + self.delay | |
1023 | self.delay = min(20 * 1000, self.delay * 2) |
|
1023 | self.delay = min(20 * 1000, self.delay * 2) | |
1024 |
|
1024 | |||
1025 | if len(self.list) > 0: |
|
1025 | if len(self.list) > 0: | |
1026 | event = self.list.pop(0) |
|
1026 | event = self.list.pop(0) | |
1027 |
|
1027 | |||
1028 | if event is not None: |
|
1028 | if event is not None: | |
1029 | event(self.zeroconf) |
|
1029 | event(self.zeroconf) | |
1030 |
|
1030 | |||
1031 |
|
1031 | |||
1032 | class ServiceInfo(object): |
|
1032 | class ServiceInfo(object): | |
1033 | """Service information""" |
|
1033 | """Service information""" | |
1034 |
|
1034 | |||
1035 | def __init__(self, type, name, address=None, port=None, weight=0, priority=0, properties=None, server=None): |
|
1035 | def __init__(self, type, name, address=None, port=None, weight=0, priority=0, properties=None, server=None): | |
1036 | """Create a service description. |
|
1036 | """Create a service description. | |
1037 |
|
1037 | |||
1038 | type: fully qualified service type name |
|
1038 | type: fully qualified service type name | |
1039 | name: fully qualified service name |
|
1039 | name: fully qualified service name | |
1040 | address: IP address as unsigned short, network byte order |
|
1040 | address: IP address as unsigned short, network byte order | |
1041 | port: port that the service runs on |
|
1041 | port: port that the service runs on | |
1042 | weight: weight of the service |
|
1042 | weight: weight of the service | |
1043 | priority: priority of the service |
|
1043 | priority: priority of the service | |
1044 | properties: dictionary of properties (or a string holding the bytes for the text field) |
|
1044 | properties: dictionary of properties (or a string holding the bytes for the text field) | |
1045 | server: fully qualified name for service host (defaults to name)""" |
|
1045 | server: fully qualified name for service host (defaults to name)""" | |
1046 |
|
1046 | |||
1047 | if not name.endswith(type): |
|
1047 | if not name.endswith(type): | |
1048 | raise BadTypeInNameException |
|
1048 | raise BadTypeInNameException | |
1049 | self.type = type |
|
1049 | self.type = type | |
1050 | self.name = name |
|
1050 | self.name = name | |
1051 | self.address = address |
|
1051 | self.address = address | |
1052 | self.port = port |
|
1052 | self.port = port | |
1053 | self.weight = weight |
|
1053 | self.weight = weight | |
1054 | self.priority = priority |
|
1054 | self.priority = priority | |
1055 | if server: |
|
1055 | if server: | |
1056 | self.server = server |
|
1056 | self.server = server | |
1057 | else: |
|
1057 | else: | |
1058 | self.server = name |
|
1058 | self.server = name | |
1059 | self.setProperties(properties) |
|
1059 | self.setProperties(properties) | |
1060 |
|
1060 | |||
1061 | def setProperties(self, properties): |
|
1061 | def setProperties(self, properties): | |
1062 | """Sets properties and text of this info from a dictionary""" |
|
1062 | """Sets properties and text of this info from a dictionary""" | |
1063 | if isinstance(properties, dict): |
|
1063 | if isinstance(properties, dict): | |
1064 | self.properties = properties |
|
1064 | self.properties = properties | |
1065 | list = [] |
|
1065 | list = [] | |
1066 | result = '' |
|
1066 | result = '' | |
1067 | for key in properties: |
|
1067 | for key in properties: | |
1068 | value = properties[key] |
|
1068 | value = properties[key] | |
1069 | if value is None: |
|
1069 | if value is None: | |
1070 | suffix = '' |
|
1070 | suffix = '' | |
1071 | elif isinstance(value, str): |
|
1071 | elif isinstance(value, str): | |
1072 | suffix = value |
|
1072 | suffix = value | |
1073 | elif isinstance(value, int): |
|
1073 | elif isinstance(value, int): | |
1074 | if value: |
|
1074 | if value: | |
1075 | suffix = 'true' |
|
1075 | suffix = 'true' | |
1076 | else: |
|
1076 | else: | |
1077 | suffix = 'false' |
|
1077 | suffix = 'false' | |
1078 | else: |
|
1078 | else: | |
1079 | suffix = '' |
|
1079 | suffix = '' | |
1080 | list.append('='.join((key, suffix))) |
|
1080 | list.append('='.join((key, suffix))) | |
1081 | for item in list: |
|
1081 | for item in list: | |
1082 | result = ''.join((result, struct.pack('!c', chr(len(item))), item)) |
|
1082 | result = ''.join((result, struct.pack('!c', chr(len(item))), item)) | |
1083 | self.text = result |
|
1083 | self.text = result | |
1084 | else: |
|
1084 | else: | |
1085 | self.text = properties |
|
1085 | self.text = properties | |
1086 |
|
1086 | |||
1087 | def setText(self, text): |
|
1087 | def setText(self, text): | |
1088 | """Sets properties and text given a text field""" |
|
1088 | """Sets properties and text given a text field""" | |
1089 | self.text = text |
|
1089 | self.text = text | |
1090 | try: |
|
1090 | try: | |
1091 | result = {} |
|
1091 | result = {} | |
1092 | end = len(text) |
|
1092 | end = len(text) | |
1093 | index = 0 |
|
1093 | index = 0 | |
1094 | strs = [] |
|
1094 | strs = [] | |
1095 | while index < end: |
|
1095 | while index < end: | |
1096 | length = ord(text[index]) |
|
1096 | length = ord(text[index]) | |
1097 | index += 1 |
|
1097 | index += 1 | |
1098 | strs.append(text[index:index+length]) |
|
1098 | strs.append(text[index:index+length]) | |
1099 | index += length |
|
1099 | index += length | |
1100 |
|
1100 | |||
1101 | for s in strs: |
|
1101 | for s in strs: | |
1102 | eindex = s.find('=') |
|
1102 | eindex = s.find('=') | |
1103 | if eindex == -1: |
|
1103 | if eindex == -1: | |
1104 | # No equals sign at all |
|
1104 | # No equals sign at all | |
1105 | key = s |
|
1105 | key = s | |
1106 | value = 0 |
|
1106 | value = 0 | |
1107 | else: |
|
1107 | else: | |
1108 | key = s[:eindex] |
|
1108 | key = s[:eindex] | |
1109 | value = s[eindex+1:] |
|
1109 | value = s[eindex+1:] | |
1110 | if value == 'true': |
|
1110 | if value == 'true': | |
1111 | value = 1 |
|
1111 | value = 1 | |
1112 | elif value == 'false' or not value: |
|
1112 | elif value == 'false' or not value: | |
1113 | value = 0 |
|
1113 | value = 0 | |
1114 |
|
1114 | |||
1115 | # Only update non-existent properties |
|
1115 | # Only update non-existent properties | |
1116 | if key and result.get(key) == None: |
|
1116 | if key and result.get(key) == None: | |
1117 | result[key] = value |
|
1117 | result[key] = value | |
1118 |
|
1118 | |||
1119 | self.properties = result |
|
1119 | self.properties = result | |
1120 | except Exception: |
|
1120 | except Exception: | |
1121 | traceback.print_exc() |
|
1121 | traceback.print_exc() | |
1122 | self.properties = None |
|
1122 | self.properties = None | |
1123 |
|
1123 | |||
1124 | def getType(self): |
|
1124 | def getType(self): | |
1125 | """Type accessor""" |
|
1125 | """Type accessor""" | |
1126 | return self.type |
|
1126 | return self.type | |
1127 |
|
1127 | |||
1128 | def getName(self): |
|
1128 | def getName(self): | |
1129 | """Name accessor""" |
|
1129 | """Name accessor""" | |
1130 | if self.type is not None and self.name.endswith("." + self.type): |
|
1130 | if self.type is not None and self.name.endswith("." + self.type): | |
1131 | return self.name[:len(self.name) - len(self.type) - 1] |
|
1131 | return self.name[:len(self.name) - len(self.type) - 1] | |
1132 | return self.name |
|
1132 | return self.name | |
1133 |
|
1133 | |||
1134 | def getAddress(self): |
|
1134 | def getAddress(self): | |
1135 | """Address accessor""" |
|
1135 | """Address accessor""" | |
1136 | return self.address |
|
1136 | return self.address | |
1137 |
|
1137 | |||
1138 | def getPort(self): |
|
1138 | def getPort(self): | |
1139 | """Port accessor""" |
|
1139 | """Port accessor""" | |
1140 | return self.port |
|
1140 | return self.port | |
1141 |
|
1141 | |||
1142 | def getPriority(self): |
|
1142 | def getPriority(self): | |
1143 | """Priority accessor""" |
|
1143 | """Priority accessor""" | |
1144 | return self.priority |
|
1144 | return self.priority | |
1145 |
|
1145 | |||
1146 | def getWeight(self): |
|
1146 | def getWeight(self): | |
1147 | """Weight accessor""" |
|
1147 | """Weight accessor""" | |
1148 | return self.weight |
|
1148 | return self.weight | |
1149 |
|
1149 | |||
1150 | def getProperties(self): |
|
1150 | def getProperties(self): | |
1151 | """Properties accessor""" |
|
1151 | """Properties accessor""" | |
1152 | return self.properties |
|
1152 | return self.properties | |
1153 |
|
1153 | |||
1154 | def getText(self): |
|
1154 | def getText(self): | |
1155 | """Text accessor""" |
|
1155 | """Text accessor""" | |
1156 | return self.text |
|
1156 | return self.text | |
1157 |
|
1157 | |||
1158 | def getServer(self): |
|
1158 | def getServer(self): | |
1159 | """Server accessor""" |
|
1159 | """Server accessor""" | |
1160 | return self.server |
|
1160 | return self.server | |
1161 |
|
1161 | |||
1162 | def updateRecord(self, zeroconf, now, record): |
|
1162 | def updateRecord(self, zeroconf, now, record): | |
1163 | """Updates service information from a DNS record""" |
|
1163 | """Updates service information from a DNS record""" | |
1164 | if record is not None and not record.isExpired(now): |
|
1164 | if record is not None and not record.isExpired(now): | |
1165 | if record.type == _TYPE_A: |
|
1165 | if record.type == _TYPE_A: | |
1166 | #if record.name == self.name: |
|
1166 | #if record.name == self.name: | |
1167 | if record.name == self.server: |
|
1167 | if record.name == self.server: | |
1168 | self.address = record.address |
|
1168 | self.address = record.address | |
1169 | elif record.type == _TYPE_SRV: |
|
1169 | elif record.type == _TYPE_SRV: | |
1170 | if record.name == self.name: |
|
1170 | if record.name == self.name: | |
1171 | self.server = record.server |
|
1171 | self.server = record.server | |
1172 | self.port = record.port |
|
1172 | self.port = record.port | |
1173 | self.weight = record.weight |
|
1173 | self.weight = record.weight | |
1174 | self.priority = record.priority |
|
1174 | self.priority = record.priority | |
1175 | #self.address = None |
|
1175 | #self.address = None | |
1176 | self.updateRecord(zeroconf, now, zeroconf.cache.getByDetails(self.server, _TYPE_A, _CLASS_IN)) |
|
1176 | self.updateRecord(zeroconf, now, zeroconf.cache.getByDetails(self.server, _TYPE_A, _CLASS_IN)) | |
1177 | elif record.type == _TYPE_TXT: |
|
1177 | elif record.type == _TYPE_TXT: | |
1178 | if record.name == self.name: |
|
1178 | if record.name == self.name: | |
1179 | self.setText(record.text) |
|
1179 | self.setText(record.text) | |
1180 |
|
1180 | |||
1181 | def request(self, zeroconf, timeout): |
|
1181 | def request(self, zeroconf, timeout): | |
1182 | """Returns true if the service could be discovered on the |
|
1182 | """Returns true if the service could be discovered on the | |
1183 | network, and updates this object with details discovered. |
|
1183 | network, and updates this object with details discovered. | |
1184 | """ |
|
1184 | """ | |
1185 | now = currentTimeMillis() |
|
1185 | now = currentTimeMillis() | |
1186 | delay = _LISTENER_TIME |
|
1186 | delay = _LISTENER_TIME | |
1187 | next = now + delay |
|
1187 | next = now + delay | |
1188 | last = now + timeout |
|
1188 | last = now + timeout | |
1189 | result = 0 |
|
1189 | result = 0 | |
1190 | try: |
|
1190 | try: | |
1191 | zeroconf.addListener(self, DNSQuestion(self.name, _TYPE_ANY, _CLASS_IN)) |
|
1191 | zeroconf.addListener(self, DNSQuestion(self.name, _TYPE_ANY, _CLASS_IN)) | |
1192 | while self.server is None or self.address is None or self.text is None: |
|
1192 | while self.server is None or self.address is None or self.text is None: | |
1193 | if last <= now: |
|
1193 | if last <= now: | |
1194 | return 0 |
|
1194 | return 0 | |
1195 | if next <= now: |
|
1195 | if next <= now: | |
1196 | out = DNSOutgoing(_FLAGS_QR_QUERY) |
|
1196 | out = DNSOutgoing(_FLAGS_QR_QUERY) | |
1197 | out.addQuestion(DNSQuestion(self.name, _TYPE_SRV, _CLASS_IN)) |
|
1197 | out.addQuestion(DNSQuestion(self.name, _TYPE_SRV, _CLASS_IN)) | |
1198 | out.addAnswerAtTime(zeroconf.cache.getByDetails(self.name, _TYPE_SRV, _CLASS_IN), now) |
|
1198 | out.addAnswerAtTime(zeroconf.cache.getByDetails(self.name, _TYPE_SRV, _CLASS_IN), now) | |
1199 | out.addQuestion(DNSQuestion(self.name, _TYPE_TXT, _CLASS_IN)) |
|
1199 | out.addQuestion(DNSQuestion(self.name, _TYPE_TXT, _CLASS_IN)) | |
1200 | out.addAnswerAtTime(zeroconf.cache.getByDetails(self.name, _TYPE_TXT, _CLASS_IN), now) |
|
1200 | out.addAnswerAtTime(zeroconf.cache.getByDetails(self.name, _TYPE_TXT, _CLASS_IN), now) | |
1201 | if self.server is not None: |
|
1201 | if self.server is not None: | |
1202 | out.addQuestion(DNSQuestion(self.server, _TYPE_A, _CLASS_IN)) |
|
1202 | out.addQuestion(DNSQuestion(self.server, _TYPE_A, _CLASS_IN)) | |
1203 | out.addAnswerAtTime(zeroconf.cache.getByDetails(self.server, _TYPE_A, _CLASS_IN), now) |
|
1203 | out.addAnswerAtTime(zeroconf.cache.getByDetails(self.server, _TYPE_A, _CLASS_IN), now) | |
1204 | zeroconf.send(out) |
|
1204 | zeroconf.send(out) | |
1205 | next = now + delay |
|
1205 | next = now + delay | |
1206 | delay = delay * 2 |
|
1206 | delay = delay * 2 | |
1207 |
|
1207 | |||
1208 | zeroconf.wait(min(next, last) - now) |
|
1208 | zeroconf.wait(min(next, last) - now) | |
1209 | now = currentTimeMillis() |
|
1209 | now = currentTimeMillis() | |
1210 | result = 1 |
|
1210 | result = 1 | |
1211 | finally: |
|
1211 | finally: | |
1212 | zeroconf.removeListener(self) |
|
1212 | zeroconf.removeListener(self) | |
1213 |
|
1213 | |||
1214 | return result |
|
1214 | return result | |
1215 |
|
1215 | |||
1216 | def __eq__(self, other): |
|
1216 | def __eq__(self, other): | |
1217 | """Tests equality of service name""" |
|
1217 | """Tests equality of service name""" | |
1218 | if isinstance(other, ServiceInfo): |
|
1218 | if isinstance(other, ServiceInfo): | |
1219 | return other.name == self.name |
|
1219 | return other.name == self.name | |
1220 | return 0 |
|
1220 | return 0 | |
1221 |
|
1221 | |||
1222 | def __ne__(self, other): |
|
1222 | def __ne__(self, other): | |
1223 | """Non-equality test""" |
|
1223 | """Non-equality test""" | |
1224 | return not self.__eq__(other) |
|
1224 | return not self.__eq__(other) | |
1225 |
|
1225 | |||
1226 | def __repr__(self): |
|
1226 | def __repr__(self): | |
1227 | """String representation""" |
|
1227 | """String representation""" | |
1228 | result = "service[%s,%s:%s," % (self.name, socket.inet_ntoa(self.getAddress()), self.port) |
|
1228 | result = "service[%s,%s:%s," % (self.name, socket.inet_ntoa(self.getAddress()), self.port) | |
1229 | if self.text is None: |
|
1229 | if self.text is None: | |
1230 | result += "None" |
|
1230 | result += "None" | |
1231 | else: |
|
1231 | else: | |
1232 | if len(self.text) < 20: |
|
1232 | if len(self.text) < 20: | |
1233 | result += self.text |
|
1233 | result += self.text | |
1234 | else: |
|
1234 | else: | |
1235 | result += self.text[:17] + "..." |
|
1235 | result += self.text[:17] + "..." | |
1236 | result += "]" |
|
1236 | result += "]" | |
1237 | return result |
|
1237 | return result | |
1238 |
|
1238 | |||
1239 |
|
1239 | |||
1240 | class Zeroconf(object): |
|
1240 | class Zeroconf(object): | |
1241 | """Implementation of Zeroconf Multicast DNS Service Discovery |
|
1241 | """Implementation of Zeroconf Multicast DNS Service Discovery | |
1242 |
|
1242 | |||
1243 | Supports registration, unregistration, queries and browsing. |
|
1243 | Supports registration, unregistration, queries and browsing. | |
1244 | """ |
|
1244 | """ | |
1245 | def __init__(self, bindaddress=None): |
|
1245 | def __init__(self, bindaddress=None): | |
1246 | """Creates an instance of the Zeroconf class, establishing |
|
1246 | """Creates an instance of the Zeroconf class, establishing | |
1247 | multicast communications, listening and reaping threads.""" |
|
1247 | multicast communications, listening and reaping threads.""" | |
1248 | globals()['_GLOBAL_DONE'] = 0 |
|
1248 | globals()['_GLOBAL_DONE'] = 0 | |
1249 | if bindaddress is None: |
|
1249 | if bindaddress is None: | |
1250 | self.intf = socket.gethostbyname(socket.gethostname()) |
|
1250 | self.intf = socket.gethostbyname(socket.gethostname()) | |
1251 | else: |
|
1251 | else: | |
1252 | self.intf = bindaddress |
|
1252 | self.intf = bindaddress | |
1253 | self.group = ('', _MDNS_PORT) |
|
1253 | self.group = ('', _MDNS_PORT) | |
1254 | self.socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) |
|
1254 | self.socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) | |
1255 | try: |
|
1255 | try: | |
1256 | self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) |
|
1256 | self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) | |
1257 | self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1) |
|
1257 | self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1) | |
1258 | except Exception: |
|
1258 | except Exception: | |
1259 | # SO_REUSEADDR should be equivalent to SO_REUSEPORT for |
|
1259 | # SO_REUSEADDR should be equivalent to SO_REUSEPORT for | |
1260 | # multicast UDP sockets (p 731, "TCP/IP Illustrated, |
|
1260 | # multicast UDP sockets (p 731, "TCP/IP Illustrated, | |
1261 | # Volume 2"), but some BSD-derived systems require |
|
1261 | # Volume 2"), but some BSD-derived systems require | |
1262 | # SO_REUSEPORT to be specified explicitly. Also, not all |
|
1262 | # SO_REUSEPORT to be specified explicitly. Also, not all | |
1263 | # versions of Python have SO_REUSEPORT available. So |
|
1263 | # versions of Python have SO_REUSEPORT available. So | |
1264 | # if you're on a BSD-based system, and haven't upgraded |
|
1264 | # if you're on a BSD-based system, and haven't upgraded | |
1265 | # to Python 2.3 yet, you may find this library doesn't |
|
1265 | # to Python 2.3 yet, you may find this library doesn't | |
1266 | # work as expected. |
|
1266 | # work as expected. | |
1267 | # |
|
1267 | # | |
1268 | pass |
|
1268 | pass | |
1269 | self.socket.setsockopt(socket.SOL_IP, socket.IP_MULTICAST_TTL, 255) |
|
1269 | self.socket.setsockopt(socket.SOL_IP, socket.IP_MULTICAST_TTL, 255) | |
1270 | self.socket.setsockopt(socket.SOL_IP, socket.IP_MULTICAST_LOOP, 1) |
|
1270 | self.socket.setsockopt(socket.SOL_IP, socket.IP_MULTICAST_LOOP, 1) | |
1271 | try: |
|
1271 | try: | |
1272 | self.socket.bind(self.group) |
|
1272 | self.socket.bind(self.group) | |
1273 | except Exception: |
|
1273 | except Exception: | |
1274 | # Some versions of linux raise an exception even though |
|
1274 | # Some versions of linux raise an exception even though | |
1275 |
# |
|
1275 | # SO_REUSEADDR and SO_REUSEPORT have been set, so ignore it | |
1276 | # |
|
|||
1277 | pass |
|
1276 | pass | |
1278 | self.socket.setsockopt(socket.SOL_IP, socket.IP_ADD_MEMBERSHIP, socket.inet_aton(_MDNS_ADDR) + socket.inet_aton('0.0.0.0')) |
|
1277 | self.socket.setsockopt(socket.SOL_IP, socket.IP_ADD_MEMBERSHIP, socket.inet_aton(_MDNS_ADDR) + socket.inet_aton('0.0.0.0')) | |
1279 |
|
1278 | |||
1280 | self.listeners = [] |
|
1279 | self.listeners = [] | |
1281 | self.browsers = [] |
|
1280 | self.browsers = [] | |
1282 | self.services = {} |
|
1281 | self.services = {} | |
1283 | self.servicetypes = {} |
|
1282 | self.servicetypes = {} | |
1284 |
|
1283 | |||
1285 | self.cache = DNSCache() |
|
1284 | self.cache = DNSCache() | |
1286 |
|
1285 | |||
1287 | self.condition = threading.Condition() |
|
1286 | self.condition = threading.Condition() | |
1288 |
|
1287 | |||
1289 | self.engine = Engine(self) |
|
1288 | self.engine = Engine(self) | |
1290 | self.listener = Listener(self) |
|
1289 | self.listener = Listener(self) | |
1291 | self.reaper = Reaper(self) |
|
1290 | self.reaper = Reaper(self) | |
1292 |
|
1291 | |||
1293 | def isLoopback(self): |
|
1292 | def isLoopback(self): | |
1294 | return self.intf.startswith("127.0.0.1") |
|
1293 | return self.intf.startswith("127.0.0.1") | |
1295 |
|
1294 | |||
1296 | def isLinklocal(self): |
|
1295 | def isLinklocal(self): | |
1297 | return self.intf.startswith("169.254.") |
|
1296 | return self.intf.startswith("169.254.") | |
1298 |
|
1297 | |||
1299 | def wait(self, timeout): |
|
1298 | def wait(self, timeout): | |
1300 | """Calling thread waits for a given number of milliseconds or |
|
1299 | """Calling thread waits for a given number of milliseconds or | |
1301 | until notified.""" |
|
1300 | until notified.""" | |
1302 | self.condition.acquire() |
|
1301 | self.condition.acquire() | |
1303 | self.condition.wait(timeout/1000) |
|
1302 | self.condition.wait(timeout/1000) | |
1304 | self.condition.release() |
|
1303 | self.condition.release() | |
1305 |
|
1304 | |||
1306 | def notifyAll(self): |
|
1305 | def notifyAll(self): | |
1307 | """Notifies all waiting threads""" |
|
1306 | """Notifies all waiting threads""" | |
1308 | self.condition.acquire() |
|
1307 | self.condition.acquire() | |
1309 | self.condition.notifyAll() |
|
1308 | self.condition.notifyAll() | |
1310 | self.condition.release() |
|
1309 | self.condition.release() | |
1311 |
|
1310 | |||
1312 | def getServiceInfo(self, type, name, timeout=3000): |
|
1311 | def getServiceInfo(self, type, name, timeout=3000): | |
1313 | """Returns network's service information for a particular |
|
1312 | """Returns network's service information for a particular | |
1314 | name and type, or None if no service matches by the timeout, |
|
1313 | name and type, or None if no service matches by the timeout, | |
1315 | which defaults to 3 seconds.""" |
|
1314 | which defaults to 3 seconds.""" | |
1316 | info = ServiceInfo(type, name) |
|
1315 | info = ServiceInfo(type, name) | |
1317 | if info.request(self, timeout): |
|
1316 | if info.request(self, timeout): | |
1318 | return info |
|
1317 | return info | |
1319 | return None |
|
1318 | return None | |
1320 |
|
1319 | |||
1321 | def addServiceListener(self, type, listener): |
|
1320 | def addServiceListener(self, type, listener): | |
1322 | """Adds a listener for a particular service type. This object |
|
1321 | """Adds a listener for a particular service type. This object | |
1323 | will then have its updateRecord method called when information |
|
1322 | will then have its updateRecord method called when information | |
1324 | arrives for that type.""" |
|
1323 | arrives for that type.""" | |
1325 | self.removeServiceListener(listener) |
|
1324 | self.removeServiceListener(listener) | |
1326 | self.browsers.append(ServiceBrowser(self, type, listener)) |
|
1325 | self.browsers.append(ServiceBrowser(self, type, listener)) | |
1327 |
|
1326 | |||
1328 | def removeServiceListener(self, listener): |
|
1327 | def removeServiceListener(self, listener): | |
1329 | """Removes a listener from the set that is currently listening.""" |
|
1328 | """Removes a listener from the set that is currently listening.""" | |
1330 | for browser in self.browsers: |
|
1329 | for browser in self.browsers: | |
1331 | if browser.listener == listener: |
|
1330 | if browser.listener == listener: | |
1332 | browser.cancel() |
|
1331 | browser.cancel() | |
1333 | del(browser) |
|
1332 | del(browser) | |
1334 |
|
1333 | |||
1335 | def registerService(self, info, ttl=_DNS_TTL): |
|
1334 | def registerService(self, info, ttl=_DNS_TTL): | |
1336 | """Registers service information to the network with a default TTL |
|
1335 | """Registers service information to the network with a default TTL | |
1337 | of 60 seconds. Zeroconf will then respond to requests for |
|
1336 | of 60 seconds. Zeroconf will then respond to requests for | |
1338 | information for that service. The name of the service may be |
|
1337 | information for that service. The name of the service may be | |
1339 | changed if needed to make it unique on the network.""" |
|
1338 | changed if needed to make it unique on the network.""" | |
1340 | self.checkService(info) |
|
1339 | self.checkService(info) | |
1341 | self.services[info.name.lower()] = info |
|
1340 | self.services[info.name.lower()] = info | |
1342 | if self.servicetypes.has_key(info.type): |
|
1341 | if self.servicetypes.has_key(info.type): | |
1343 | self.servicetypes[info.type]+=1 |
|
1342 | self.servicetypes[info.type]+=1 | |
1344 | else: |
|
1343 | else: | |
1345 | self.servicetypes[info.type]=1 |
|
1344 | self.servicetypes[info.type]=1 | |
1346 | now = currentTimeMillis() |
|
1345 | now = currentTimeMillis() | |
1347 | nextTime = now |
|
1346 | nextTime = now | |
1348 | i = 0 |
|
1347 | i = 0 | |
1349 | while i < 3: |
|
1348 | while i < 3: | |
1350 | if now < nextTime: |
|
1349 | if now < nextTime: | |
1351 | self.wait(nextTime - now) |
|
1350 | self.wait(nextTime - now) | |
1352 | now = currentTimeMillis() |
|
1351 | now = currentTimeMillis() | |
1353 | continue |
|
1352 | continue | |
1354 | out = DNSOutgoing(_FLAGS_QR_RESPONSE | _FLAGS_AA) |
|
1353 | out = DNSOutgoing(_FLAGS_QR_RESPONSE | _FLAGS_AA) | |
1355 | out.addAnswerAtTime(DNSPointer(info.type, _TYPE_PTR, _CLASS_IN, ttl, info.name), 0) |
|
1354 | out.addAnswerAtTime(DNSPointer(info.type, _TYPE_PTR, _CLASS_IN, ttl, info.name), 0) | |
1356 | out.addAnswerAtTime(DNSService(info.name, _TYPE_SRV, _CLASS_IN, ttl, info.priority, info.weight, info.port, info.server), 0) |
|
1355 | out.addAnswerAtTime(DNSService(info.name, _TYPE_SRV, _CLASS_IN, ttl, info.priority, info.weight, info.port, info.server), 0) | |
1357 | out.addAnswerAtTime(DNSText(info.name, _TYPE_TXT, _CLASS_IN, ttl, info.text), 0) |
|
1356 | out.addAnswerAtTime(DNSText(info.name, _TYPE_TXT, _CLASS_IN, ttl, info.text), 0) | |
1358 | if info.address: |
|
1357 | if info.address: | |
1359 | out.addAnswerAtTime(DNSAddress(info.server, _TYPE_A, _CLASS_IN, ttl, info.address), 0) |
|
1358 | out.addAnswerAtTime(DNSAddress(info.server, _TYPE_A, _CLASS_IN, ttl, info.address), 0) | |
1360 | self.send(out) |
|
1359 | self.send(out) | |
1361 | i += 1 |
|
1360 | i += 1 | |
1362 | nextTime += _REGISTER_TIME |
|
1361 | nextTime += _REGISTER_TIME | |
1363 |
|
1362 | |||
1364 | def unregisterService(self, info): |
|
1363 | def unregisterService(self, info): | |
1365 | """Unregister a service.""" |
|
1364 | """Unregister a service.""" | |
1366 | try: |
|
1365 | try: | |
1367 | del(self.services[info.name.lower()]) |
|
1366 | del(self.services[info.name.lower()]) | |
1368 | if self.servicetypes[info.type]>1: |
|
1367 | if self.servicetypes[info.type]>1: | |
1369 | self.servicetypes[info.type]-=1 |
|
1368 | self.servicetypes[info.type]-=1 | |
1370 | else: |
|
1369 | else: | |
1371 | del self.servicetypes[info.type] |
|
1370 | del self.servicetypes[info.type] | |
1372 | except KeyError: |
|
1371 | except KeyError: | |
1373 | pass |
|
1372 | pass | |
1374 | now = currentTimeMillis() |
|
1373 | now = currentTimeMillis() | |
1375 | nextTime = now |
|
1374 | nextTime = now | |
1376 | i = 0 |
|
1375 | i = 0 | |
1377 | while i < 3: |
|
1376 | while i < 3: | |
1378 | if now < nextTime: |
|
1377 | if now < nextTime: | |
1379 | self.wait(nextTime - now) |
|
1378 | self.wait(nextTime - now) | |
1380 | now = currentTimeMillis() |
|
1379 | now = currentTimeMillis() | |
1381 | continue |
|
1380 | continue | |
1382 | out = DNSOutgoing(_FLAGS_QR_RESPONSE | _FLAGS_AA) |
|
1381 | out = DNSOutgoing(_FLAGS_QR_RESPONSE | _FLAGS_AA) | |
1383 | out.addAnswerAtTime(DNSPointer(info.type, _TYPE_PTR, _CLASS_IN, 0, info.name), 0) |
|
1382 | out.addAnswerAtTime(DNSPointer(info.type, _TYPE_PTR, _CLASS_IN, 0, info.name), 0) | |
1384 | out.addAnswerAtTime(DNSService(info.name, _TYPE_SRV, _CLASS_IN, 0, info.priority, info.weight, info.port, info.name), 0) |
|
1383 | out.addAnswerAtTime(DNSService(info.name, _TYPE_SRV, _CLASS_IN, 0, info.priority, info.weight, info.port, info.name), 0) | |
1385 | out.addAnswerAtTime(DNSText(info.name, _TYPE_TXT, _CLASS_IN, 0, info.text), 0) |
|
1384 | out.addAnswerAtTime(DNSText(info.name, _TYPE_TXT, _CLASS_IN, 0, info.text), 0) | |
1386 | if info.address: |
|
1385 | if info.address: | |
1387 | out.addAnswerAtTime(DNSAddress(info.server, _TYPE_A, _CLASS_IN, 0, info.address), 0) |
|
1386 | out.addAnswerAtTime(DNSAddress(info.server, _TYPE_A, _CLASS_IN, 0, info.address), 0) | |
1388 | self.send(out) |
|
1387 | self.send(out) | |
1389 | i += 1 |
|
1388 | i += 1 | |
1390 | nextTime += _UNREGISTER_TIME |
|
1389 | nextTime += _UNREGISTER_TIME | |
1391 |
|
1390 | |||
1392 | def unregisterAllServices(self): |
|
1391 | def unregisterAllServices(self): | |
1393 | """Unregister all registered services.""" |
|
1392 | """Unregister all registered services.""" | |
1394 | if len(self.services) > 0: |
|
1393 | if len(self.services) > 0: | |
1395 | now = currentTimeMillis() |
|
1394 | now = currentTimeMillis() | |
1396 | nextTime = now |
|
1395 | nextTime = now | |
1397 | i = 0 |
|
1396 | i = 0 | |
1398 | while i < 3: |
|
1397 | while i < 3: | |
1399 | if now < nextTime: |
|
1398 | if now < nextTime: | |
1400 | self.wait(nextTime - now) |
|
1399 | self.wait(nextTime - now) | |
1401 | now = currentTimeMillis() |
|
1400 | now = currentTimeMillis() | |
1402 | continue |
|
1401 | continue | |
1403 | out = DNSOutgoing(_FLAGS_QR_RESPONSE | _FLAGS_AA) |
|
1402 | out = DNSOutgoing(_FLAGS_QR_RESPONSE | _FLAGS_AA) | |
1404 | for info in self.services.values(): |
|
1403 | for info in self.services.values(): | |
1405 | out.addAnswerAtTime(DNSPointer(info.type, _TYPE_PTR, _CLASS_IN, 0, info.name), 0) |
|
1404 | out.addAnswerAtTime(DNSPointer(info.type, _TYPE_PTR, _CLASS_IN, 0, info.name), 0) | |
1406 | out.addAnswerAtTime(DNSService(info.name, _TYPE_SRV, _CLASS_IN, 0, info.priority, info.weight, info.port, info.server), 0) |
|
1405 | out.addAnswerAtTime(DNSService(info.name, _TYPE_SRV, _CLASS_IN, 0, info.priority, info.weight, info.port, info.server), 0) | |
1407 | out.addAnswerAtTime(DNSText(info.name, _TYPE_TXT, _CLASS_IN, 0, info.text), 0) |
|
1406 | out.addAnswerAtTime(DNSText(info.name, _TYPE_TXT, _CLASS_IN, 0, info.text), 0) | |
1408 | if info.address: |
|
1407 | if info.address: | |
1409 | out.addAnswerAtTime(DNSAddress(info.server, _TYPE_A, _CLASS_IN, 0, info.address), 0) |
|
1408 | out.addAnswerAtTime(DNSAddress(info.server, _TYPE_A, _CLASS_IN, 0, info.address), 0) | |
1410 | self.send(out) |
|
1409 | self.send(out) | |
1411 | i += 1 |
|
1410 | i += 1 | |
1412 | nextTime += _UNREGISTER_TIME |
|
1411 | nextTime += _UNREGISTER_TIME | |
1413 |
|
1412 | |||
1414 | def checkService(self, info): |
|
1413 | def checkService(self, info): | |
1415 | """Checks the network for a unique service name, modifying the |
|
1414 | """Checks the network for a unique service name, modifying the | |
1416 | ServiceInfo passed in if it is not unique.""" |
|
1415 | ServiceInfo passed in if it is not unique.""" | |
1417 | now = currentTimeMillis() |
|
1416 | now = currentTimeMillis() | |
1418 | nextTime = now |
|
1417 | nextTime = now | |
1419 | i = 0 |
|
1418 | i = 0 | |
1420 | while i < 3: |
|
1419 | while i < 3: | |
1421 | for record in self.cache.entriesWithName(info.type): |
|
1420 | for record in self.cache.entriesWithName(info.type): | |
1422 | if record.type == _TYPE_PTR and not record.isExpired(now) and record.alias == info.name: |
|
1421 | if record.type == _TYPE_PTR and not record.isExpired(now) and record.alias == info.name: | |
1423 | if (info.name.find('.') < 0): |
|
1422 | if (info.name.find('.') < 0): | |
1424 | info.name = info.name + ".[" + info.address + ":" + info.port + "]." + info.type |
|
1423 | info.name = info.name + ".[" + info.address + ":" + info.port + "]." + info.type | |
1425 | self.checkService(info) |
|
1424 | self.checkService(info) | |
1426 | return |
|
1425 | return | |
1427 | raise NonUniqueNameException |
|
1426 | raise NonUniqueNameException | |
1428 | if now < nextTime: |
|
1427 | if now < nextTime: | |
1429 | self.wait(nextTime - now) |
|
1428 | self.wait(nextTime - now) | |
1430 | now = currentTimeMillis() |
|
1429 | now = currentTimeMillis() | |
1431 | continue |
|
1430 | continue | |
1432 | out = DNSOutgoing(_FLAGS_QR_QUERY | _FLAGS_AA) |
|
1431 | out = DNSOutgoing(_FLAGS_QR_QUERY | _FLAGS_AA) | |
1433 | self.debug = out |
|
1432 | self.debug = out | |
1434 | out.addQuestion(DNSQuestion(info.type, _TYPE_PTR, _CLASS_IN)) |
|
1433 | out.addQuestion(DNSQuestion(info.type, _TYPE_PTR, _CLASS_IN)) | |
1435 | out.addAuthoritativeAnswer(DNSPointer(info.type, _TYPE_PTR, _CLASS_IN, _DNS_TTL, info.name)) |
|
1434 | out.addAuthoritativeAnswer(DNSPointer(info.type, _TYPE_PTR, _CLASS_IN, _DNS_TTL, info.name)) | |
1436 | self.send(out) |
|
1435 | self.send(out) | |
1437 | i += 1 |
|
1436 | i += 1 | |
1438 | nextTime += _CHECK_TIME |
|
1437 | nextTime += _CHECK_TIME | |
1439 |
|
1438 | |||
1440 | def addListener(self, listener, question): |
|
1439 | def addListener(self, listener, question): | |
1441 | """Adds a listener for a given question. The listener will have |
|
1440 | """Adds a listener for a given question. The listener will have | |
1442 | its updateRecord method called when information is available to |
|
1441 | its updateRecord method called when information is available to | |
1443 | answer the question.""" |
|
1442 | answer the question.""" | |
1444 | now = currentTimeMillis() |
|
1443 | now = currentTimeMillis() | |
1445 | self.listeners.append(listener) |
|
1444 | self.listeners.append(listener) | |
1446 | if question is not None: |
|
1445 | if question is not None: | |
1447 | for record in self.cache.entriesWithName(question.name): |
|
1446 | for record in self.cache.entriesWithName(question.name): | |
1448 | if question.answeredBy(record) and not record.isExpired(now): |
|
1447 | if question.answeredBy(record) and not record.isExpired(now): | |
1449 | listener.updateRecord(self, now, record) |
|
1448 | listener.updateRecord(self, now, record) | |
1450 | self.notifyAll() |
|
1449 | self.notifyAll() | |
1451 |
|
1450 | |||
1452 | def removeListener(self, listener): |
|
1451 | def removeListener(self, listener): | |
1453 | """Removes a listener.""" |
|
1452 | """Removes a listener.""" | |
1454 | try: |
|
1453 | try: | |
1455 | self.listeners.remove(listener) |
|
1454 | self.listeners.remove(listener) | |
1456 | self.notifyAll() |
|
1455 | self.notifyAll() | |
1457 | except Exception: |
|
1456 | except Exception: | |
1458 | pass |
|
1457 | pass | |
1459 |
|
1458 | |||
1460 | def updateRecord(self, now, rec): |
|
1459 | def updateRecord(self, now, rec): | |
1461 | """Used to notify listeners of new information that has updated |
|
1460 | """Used to notify listeners of new information that has updated | |
1462 | a record.""" |
|
1461 | a record.""" | |
1463 | for listener in self.listeners: |
|
1462 | for listener in self.listeners: | |
1464 | listener.updateRecord(self, now, rec) |
|
1463 | listener.updateRecord(self, now, rec) | |
1465 | self.notifyAll() |
|
1464 | self.notifyAll() | |
1466 |
|
1465 | |||
1467 | def handleResponse(self, msg): |
|
1466 | def handleResponse(self, msg): | |
1468 | """Deal with incoming response packets. All answers |
|
1467 | """Deal with incoming response packets. All answers | |
1469 | are held in the cache, and listeners are notified.""" |
|
1468 | are held in the cache, and listeners are notified.""" | |
1470 | now = currentTimeMillis() |
|
1469 | now = currentTimeMillis() | |
1471 | for record in msg.answers: |
|
1470 | for record in msg.answers: | |
1472 | expired = record.isExpired(now) |
|
1471 | expired = record.isExpired(now) | |
1473 | if record in self.cache.entries(): |
|
1472 | if record in self.cache.entries(): | |
1474 | if expired: |
|
1473 | if expired: | |
1475 | self.cache.remove(record) |
|
1474 | self.cache.remove(record) | |
1476 | else: |
|
1475 | else: | |
1477 | entry = self.cache.get(record) |
|
1476 | entry = self.cache.get(record) | |
1478 | if entry is not None: |
|
1477 | if entry is not None: | |
1479 | entry.resetTTL(record) |
|
1478 | entry.resetTTL(record) | |
1480 | record = entry |
|
1479 | record = entry | |
1481 | else: |
|
1480 | else: | |
1482 | self.cache.add(record) |
|
1481 | self.cache.add(record) | |
1483 |
|
1482 | |||
1484 | self.updateRecord(now, record) |
|
1483 | self.updateRecord(now, record) | |
1485 |
|
1484 | |||
1486 | def handleQuery(self, msg, addr, port): |
|
1485 | def handleQuery(self, msg, addr, port): | |
1487 | """Deal with incoming query packets. Provides a response if |
|
1486 | """Deal with incoming query packets. Provides a response if | |
1488 | possible.""" |
|
1487 | possible.""" | |
1489 | out = None |
|
1488 | out = None | |
1490 |
|
1489 | |||
1491 | # Support unicast client responses |
|
1490 | # Support unicast client responses | |
1492 | # |
|
1491 | # | |
1493 | if port != _MDNS_PORT: |
|
1492 | if port != _MDNS_PORT: | |
1494 | out = DNSOutgoing(_FLAGS_QR_RESPONSE | _FLAGS_AA, 0) |
|
1493 | out = DNSOutgoing(_FLAGS_QR_RESPONSE | _FLAGS_AA, 0) | |
1495 | for question in msg.questions: |
|
1494 | for question in msg.questions: | |
1496 | out.addQuestion(question) |
|
1495 | out.addQuestion(question) | |
1497 |
|
1496 | |||
1498 | for question in msg.questions: |
|
1497 | for question in msg.questions: | |
1499 | if question.type == _TYPE_PTR: |
|
1498 | if question.type == _TYPE_PTR: | |
1500 | if question.name == "_services._dns-sd._udp.local.": |
|
1499 | if question.name == "_services._dns-sd._udp.local.": | |
1501 | for stype in self.servicetypes.keys(): |
|
1500 | for stype in self.servicetypes.keys(): | |
1502 | if out is None: |
|
1501 | if out is None: | |
1503 | out = DNSOutgoing(_FLAGS_QR_RESPONSE | _FLAGS_AA) |
|
1502 | out = DNSOutgoing(_FLAGS_QR_RESPONSE | _FLAGS_AA) | |
1504 | out.addAnswer(msg, DNSPointer("_services._dns-sd._udp.local.", _TYPE_PTR, _CLASS_IN, _DNS_TTL, stype)) |
|
1503 | out.addAnswer(msg, DNSPointer("_services._dns-sd._udp.local.", _TYPE_PTR, _CLASS_IN, _DNS_TTL, stype)) | |
1505 | for service in self.services.values(): |
|
1504 | for service in self.services.values(): | |
1506 | if question.name == service.type: |
|
1505 | if question.name == service.type: | |
1507 | if out is None: |
|
1506 | if out is None: | |
1508 | out = DNSOutgoing(_FLAGS_QR_RESPONSE | _FLAGS_AA) |
|
1507 | out = DNSOutgoing(_FLAGS_QR_RESPONSE | _FLAGS_AA) | |
1509 | out.addAnswer(msg, DNSPointer(service.type, _TYPE_PTR, _CLASS_IN, _DNS_TTL, service.name)) |
|
1508 | out.addAnswer(msg, DNSPointer(service.type, _TYPE_PTR, _CLASS_IN, _DNS_TTL, service.name)) | |
1510 | else: |
|
1509 | else: | |
1511 | try: |
|
1510 | try: | |
1512 | if out is None: |
|
1511 | if out is None: | |
1513 | out = DNSOutgoing(_FLAGS_QR_RESPONSE | _FLAGS_AA) |
|
1512 | out = DNSOutgoing(_FLAGS_QR_RESPONSE | _FLAGS_AA) | |
1514 |
|
1513 | |||
1515 | # Answer A record queries for any service addresses we know |
|
1514 | # Answer A record queries for any service addresses we know | |
1516 | if question.type == _TYPE_A or question.type == _TYPE_ANY: |
|
1515 | if question.type == _TYPE_A or question.type == _TYPE_ANY: | |
1517 | for service in self.services.values(): |
|
1516 | for service in self.services.values(): | |
1518 | if service.server == question.name.lower(): |
|
1517 | if service.server == question.name.lower(): | |
1519 | out.addAnswer(msg, DNSAddress(question.name, _TYPE_A, _CLASS_IN | _CLASS_UNIQUE, _DNS_TTL, service.address)) |
|
1518 | out.addAnswer(msg, DNSAddress(question.name, _TYPE_A, _CLASS_IN | _CLASS_UNIQUE, _DNS_TTL, service.address)) | |
1520 |
|
1519 | |||
1521 | service = self.services.get(question.name.lower(), None) |
|
1520 | service = self.services.get(question.name.lower(), None) | |
1522 | if not service: continue |
|
1521 | if not service: continue | |
1523 |
|
1522 | |||
1524 | if question.type == _TYPE_SRV or question.type == _TYPE_ANY: |
|
1523 | if question.type == _TYPE_SRV or question.type == _TYPE_ANY: | |
1525 | out.addAnswer(msg, DNSService(question.name, _TYPE_SRV, _CLASS_IN | _CLASS_UNIQUE, _DNS_TTL, service.priority, service.weight, service.port, service.server)) |
|
1524 | out.addAnswer(msg, DNSService(question.name, _TYPE_SRV, _CLASS_IN | _CLASS_UNIQUE, _DNS_TTL, service.priority, service.weight, service.port, service.server)) | |
1526 | if question.type == _TYPE_TXT or question.type == _TYPE_ANY: |
|
1525 | if question.type == _TYPE_TXT or question.type == _TYPE_ANY: | |
1527 | out.addAnswer(msg, DNSText(question.name, _TYPE_TXT, _CLASS_IN | _CLASS_UNIQUE, _DNS_TTL, service.text)) |
|
1526 | out.addAnswer(msg, DNSText(question.name, _TYPE_TXT, _CLASS_IN | _CLASS_UNIQUE, _DNS_TTL, service.text)) | |
1528 | if question.type == _TYPE_SRV: |
|
1527 | if question.type == _TYPE_SRV: | |
1529 | out.addAdditionalAnswer(DNSAddress(service.server, _TYPE_A, _CLASS_IN | _CLASS_UNIQUE, _DNS_TTL, service.address)) |
|
1528 | out.addAdditionalAnswer(DNSAddress(service.server, _TYPE_A, _CLASS_IN | _CLASS_UNIQUE, _DNS_TTL, service.address)) | |
1530 | except Exception: |
|
1529 | except Exception: | |
1531 | traceback.print_exc() |
|
1530 | traceback.print_exc() | |
1532 |
|
1531 | |||
1533 | if out is not None and out.answers: |
|
1532 | if out is not None and out.answers: | |
1534 | out.id = msg.id |
|
1533 | out.id = msg.id | |
1535 | self.send(out, addr, port) |
|
1534 | self.send(out, addr, port) | |
1536 |
|
1535 | |||
1537 | def send(self, out, addr = _MDNS_ADDR, port = _MDNS_PORT): |
|
1536 | def send(self, out, addr = _MDNS_ADDR, port = _MDNS_PORT): | |
1538 | """Sends an outgoing packet.""" |
|
1537 | """Sends an outgoing packet.""" | |
1539 | # This is a quick test to see if we can parse the packets we generate |
|
1538 | # This is a quick test to see if we can parse the packets we generate | |
1540 | #temp = DNSIncoming(out.packet()) |
|
1539 | #temp = DNSIncoming(out.packet()) | |
1541 | try: |
|
1540 | try: | |
1542 | self.socket.sendto(out.packet(), 0, (addr, port)) |
|
1541 | self.socket.sendto(out.packet(), 0, (addr, port)) | |
1543 | except Exception: |
|
1542 | except Exception: | |
1544 | # Ignore this, it may be a temporary loss of network connection |
|
1543 | # Ignore this, it may be a temporary loss of network connection | |
1545 | pass |
|
1544 | pass | |
1546 |
|
1545 | |||
1547 | def close(self): |
|
1546 | def close(self): | |
1548 | """Ends the background threads, and prevent this instance from |
|
1547 | """Ends the background threads, and prevent this instance from | |
1549 | servicing further queries.""" |
|
1548 | servicing further queries.""" | |
1550 | if globals()['_GLOBAL_DONE'] == 0: |
|
1549 | if globals()['_GLOBAL_DONE'] == 0: | |
1551 | globals()['_GLOBAL_DONE'] = 1 |
|
1550 | globals()['_GLOBAL_DONE'] = 1 | |
1552 | self.notifyAll() |
|
1551 | self.notifyAll() | |
1553 | self.engine.notify() |
|
1552 | self.engine.notify() | |
1554 | self.unregisterAllServices() |
|
1553 | self.unregisterAllServices() | |
1555 | self.socket.setsockopt(socket.SOL_IP, socket.IP_DROP_MEMBERSHIP, socket.inet_aton(_MDNS_ADDR) + socket.inet_aton('0.0.0.0')) |
|
1554 | self.socket.setsockopt(socket.SOL_IP, socket.IP_DROP_MEMBERSHIP, socket.inet_aton(_MDNS_ADDR) + socket.inet_aton('0.0.0.0')) | |
1556 | self.socket.close() |
|
1555 | self.socket.close() | |
1557 |
|
1556 | |||
1558 | # Test a few module features, including service registration, service |
|
1557 | # Test a few module features, including service registration, service | |
1559 | # query (for Zoe), and service unregistration. |
|
1558 | # query (for Zoe), and service unregistration. | |
1560 |
|
1559 | |||
1561 | if __name__ == '__main__': |
|
1560 | if __name__ == '__main__': | |
1562 | print "Multicast DNS Service Discovery for Python, version", __version__ |
|
1561 | print "Multicast DNS Service Discovery for Python, version", __version__ | |
1563 | r = Zeroconf() |
|
1562 | r = Zeroconf() | |
1564 | print "1. Testing registration of a service..." |
|
1563 | print "1. Testing registration of a service..." | |
1565 | desc = {'version':'0.10','a':'test value', 'b':'another value'} |
|
1564 | desc = {'version':'0.10','a':'test value', 'b':'another value'} | |
1566 | info = ServiceInfo("_http._tcp.local.", "My Service Name._http._tcp.local.", socket.inet_aton("127.0.0.1"), 1234, 0, 0, desc) |
|
1565 | info = ServiceInfo("_http._tcp.local.", "My Service Name._http._tcp.local.", socket.inet_aton("127.0.0.1"), 1234, 0, 0, desc) | |
1567 | print " Registering service..." |
|
1566 | print " Registering service..." | |
1568 | r.registerService(info) |
|
1567 | r.registerService(info) | |
1569 | print " Registration done." |
|
1568 | print " Registration done." | |
1570 | print "2. Testing query of service information..." |
|
1569 | print "2. Testing query of service information..." | |
1571 | print " Getting ZOE service:", str(r.getServiceInfo("_http._tcp.local.", "ZOE._http._tcp.local.")) |
|
1570 | print " Getting ZOE service:", str(r.getServiceInfo("_http._tcp.local.", "ZOE._http._tcp.local.")) | |
1572 | print " Query done." |
|
1571 | print " Query done." | |
1573 | print "3. Testing query of own service..." |
|
1572 | print "3. Testing query of own service..." | |
1574 | print " Getting self:", str(r.getServiceInfo("_http._tcp.local.", "My Service Name._http._tcp.local.")) |
|
1573 | print " Getting self:", str(r.getServiceInfo("_http._tcp.local.", "My Service Name._http._tcp.local.")) | |
1575 | print " Query done." |
|
1574 | print " Query done." | |
1576 | print "4. Testing unregister of service information..." |
|
1575 | print "4. Testing unregister of service information..." | |
1577 | r.unregisterService(info) |
|
1576 | r.unregisterService(info) | |
1578 | print " Unregister done." |
|
1577 | print " Unregister done." | |
1579 | r.close() |
|
1578 | r.close() | |
1580 |
|
1579 | |||
1581 | # no-check-code |
|
1580 | # no-check-code |
@@ -1,188 +1,188 | |||||
1 | # hook.py - hook support for mercurial |
|
1 | # hook.py - hook support for mercurial | |
2 | # |
|
2 | # | |
3 | # Copyright 2007 Matt Mackall <mpm@selenic.com> |
|
3 | # Copyright 2007 Matt Mackall <mpm@selenic.com> | |
4 | # |
|
4 | # | |
5 | # This software may be used and distributed according to the terms of the |
|
5 | # This software may be used and distributed according to the terms of the | |
6 | # GNU General Public License version 2 or any later version. |
|
6 | # GNU General Public License version 2 or any later version. | |
7 |
|
7 | |||
8 | from i18n import _ |
|
8 | from i18n import _ | |
9 | import os, sys |
|
9 | import os, sys | |
10 | import extensions, util |
|
10 | import extensions, util | |
11 |
|
11 | |||
12 | def _pythonhook(ui, repo, name, hname, funcname, args, throw): |
|
12 | def _pythonhook(ui, repo, name, hname, funcname, args, throw): | |
13 | '''call python hook. hook is callable object, looked up as |
|
13 | '''call python hook. hook is callable object, looked up as | |
14 | name in python module. if callable returns "true", hook |
|
14 | name in python module. if callable returns "true", hook | |
15 | fails, else passes. if hook raises exception, treated as |
|
15 | fails, else passes. if hook raises exception, treated as | |
16 | hook failure. exception propagates if throw is "true". |
|
16 | hook failure. exception propagates if throw is "true". | |
17 |
|
17 | |||
18 | reason for "true" meaning "hook failed" is so that |
|
18 | reason for "true" meaning "hook failed" is so that | |
19 | unmodified commands (e.g. mercurial.commands.update) can |
|
19 | unmodified commands (e.g. mercurial.commands.update) can | |
20 | be run as hooks without wrappers to convert return values.''' |
|
20 | be run as hooks without wrappers to convert return values.''' | |
21 |
|
21 | |||
22 | ui.note(_("calling hook %s: %s\n") % (hname, funcname)) |
|
22 | ui.note(_("calling hook %s: %s\n") % (hname, funcname)) | |
23 | obj = funcname |
|
23 | obj = funcname | |
24 | if not util.safehasattr(obj, '__call__'): |
|
24 | if not util.safehasattr(obj, '__call__'): | |
25 | d = funcname.rfind('.') |
|
25 | d = funcname.rfind('.') | |
26 | if d == -1: |
|
26 | if d == -1: | |
27 | raise util.Abort(_('%s hook is invalid ("%s" not in ' |
|
27 | raise util.Abort(_('%s hook is invalid ("%s" not in ' | |
28 | 'a module)') % (hname, funcname)) |
|
28 | 'a module)') % (hname, funcname)) | |
29 | modname = funcname[:d] |
|
29 | modname = funcname[:d] | |
30 | oldpaths = sys.path |
|
30 | oldpaths = sys.path | |
31 | if util.mainfrozen(): |
|
31 | if util.mainfrozen(): | |
32 | # binary installs require sys.path manipulation |
|
32 | # binary installs require sys.path manipulation | |
33 | modpath, modfile = os.path.split(modname) |
|
33 | modpath, modfile = os.path.split(modname) | |
34 | if modpath and modfile: |
|
34 | if modpath and modfile: | |
35 | sys.path = sys.path[:] + [modpath] |
|
35 | sys.path = sys.path[:] + [modpath] | |
36 | modname = modfile |
|
36 | modname = modfile | |
37 | try: |
|
37 | try: | |
38 | obj = __import__(modname) |
|
38 | obj = __import__(modname) | |
39 | except ImportError: |
|
39 | except ImportError: | |
40 | e1 = sys.exc_type, sys.exc_value, sys.exc_traceback |
|
40 | e1 = sys.exc_type, sys.exc_value, sys.exc_traceback | |
41 | try: |
|
41 | try: | |
42 | # extensions are loaded with hgext_ prefix |
|
42 | # extensions are loaded with hgext_ prefix | |
43 | obj = __import__("hgext_%s" % modname) |
|
43 | obj = __import__("hgext_%s" % modname) | |
44 | except ImportError: |
|
44 | except ImportError: | |
45 | e2 = sys.exc_type, sys.exc_value, sys.exc_traceback |
|
45 | e2 = sys.exc_type, sys.exc_value, sys.exc_traceback | |
46 | if ui.tracebackflag: |
|
46 | if ui.tracebackflag: | |
47 | ui.warn(_('exception from first failed import attempt:\n')) |
|
47 | ui.warn(_('exception from first failed import attempt:\n')) | |
48 | ui.traceback(e1) |
|
48 | ui.traceback(e1) | |
49 | if ui.tracebackflag: |
|
49 | if ui.tracebackflag: | |
50 | ui.warn(_('exception from second failed import attempt:\n')) |
|
50 | ui.warn(_('exception from second failed import attempt:\n')) | |
51 | ui.traceback(e2) |
|
51 | ui.traceback(e2) | |
52 | raise util.Abort(_('%s hook is invalid ' |
|
52 | raise util.Abort(_('%s hook is invalid ' | |
53 | '(import of "%s" failed)') % |
|
53 | '(import of "%s" failed)') % | |
54 | (hname, modname)) |
|
54 | (hname, modname)) | |
55 | sys.path = oldpaths |
|
55 | sys.path = oldpaths | |
56 | try: |
|
56 | try: | |
57 | for p in funcname.split('.')[1:]: |
|
57 | for p in funcname.split('.')[1:]: | |
58 | obj = getattr(obj, p) |
|
58 | obj = getattr(obj, p) | |
59 | except AttributeError: |
|
59 | except AttributeError: | |
60 | raise util.Abort(_('%s hook is invalid ' |
|
60 | raise util.Abort(_('%s hook is invalid ' | |
61 | '("%s" is not defined)') % |
|
61 | '("%s" is not defined)') % | |
62 | (hname, funcname)) |
|
62 | (hname, funcname)) | |
63 | if not util.safehasattr(obj, '__call__'): |
|
63 | if not util.safehasattr(obj, '__call__'): | |
64 | raise util.Abort(_('%s hook is invalid ' |
|
64 | raise util.Abort(_('%s hook is invalid ' | |
65 | '("%s" is not callable)') % |
|
65 | '("%s" is not callable)') % | |
66 | (hname, funcname)) |
|
66 | (hname, funcname)) | |
67 | try: |
|
67 | try: | |
68 | try: |
|
68 | try: | |
69 | # redirect IO descriptors to the ui descriptors so hooks |
|
69 | # redirect IO descriptors to the ui descriptors so hooks | |
70 | # that write directly to these don't mess up the command |
|
70 | # that write directly to these don't mess up the command | |
71 | # protocol when running through the command server |
|
71 | # protocol when running through the command server | |
72 | old = sys.stdout, sys.stderr, sys.stdin |
|
72 | old = sys.stdout, sys.stderr, sys.stdin | |
73 | sys.stdout, sys.stderr, sys.stdin = ui.fout, ui.ferr, ui.fin |
|
73 | sys.stdout, sys.stderr, sys.stdin = ui.fout, ui.ferr, ui.fin | |
74 |
|
74 | |||
75 | r = obj(ui=ui, repo=repo, hooktype=name, **args) |
|
75 | r = obj(ui=ui, repo=repo, hooktype=name, **args) | |
76 | except KeyboardInterrupt: |
|
76 | except KeyboardInterrupt: | |
77 | raise |
|
77 | raise | |
78 | except Exception, exc: |
|
78 | except Exception, exc: | |
79 | if isinstance(exc, util.Abort): |
|
79 | if isinstance(exc, util.Abort): | |
80 | ui.warn(_('error: %s hook failed: %s\n') % |
|
80 | ui.warn(_('error: %s hook failed: %s\n') % | |
81 | (hname, exc.args[0])) |
|
81 | (hname, exc.args[0])) | |
82 | else: |
|
82 | else: | |
83 | ui.warn(_('error: %s hook raised an exception: ' |
|
83 | ui.warn(_('error: %s hook raised an exception: ' | |
84 | '%s\n') % (hname, exc)) |
|
84 | '%s\n') % (hname, exc)) | |
85 | if throw: |
|
85 | if throw: | |
86 | raise |
|
86 | raise | |
87 | ui.traceback() |
|
87 | ui.traceback() | |
88 | return True |
|
88 | return True | |
89 | finally: |
|
89 | finally: | |
90 | sys.stdout, sys.stderr, sys.stdin = old |
|
90 | sys.stdout, sys.stderr, sys.stdin = old | |
91 | if r: |
|
91 | if r: | |
92 | if throw: |
|
92 | if throw: | |
93 | raise util.Abort(_('%s hook failed') % hname) |
|
93 | raise util.Abort(_('%s hook failed') % hname) | |
94 | ui.warn(_('warning: %s hook failed\n') % hname) |
|
94 | ui.warn(_('warning: %s hook failed\n') % hname) | |
95 | return r |
|
95 | return r | |
96 |
|
96 | |||
97 | def _exthook(ui, repo, name, cmd, args, throw): |
|
97 | def _exthook(ui, repo, name, cmd, args, throw): | |
98 | ui.note(_("running hook %s: %s\n") % (name, cmd)) |
|
98 | ui.note(_("running hook %s: %s\n") % (name, cmd)) | |
99 |
|
99 | |||
100 | env = {} |
|
100 | env = {} | |
101 | for k, v in args.iteritems(): |
|
101 | for k, v in args.iteritems(): | |
102 | if util.safehasattr(v, '__call__'): |
|
102 | if util.safehasattr(v, '__call__'): | |
103 | v = v() |
|
103 | v = v() | |
104 | if isinstance(v, dict): |
|
104 | if isinstance(v, dict): | |
105 | # make the dictionary element order stable across Python |
|
105 | # make the dictionary element order stable across Python | |
106 | # implementations |
|
106 | # implementations | |
107 | v = ('{' + |
|
107 | v = ('{' + | |
108 | ', '.join('%r: %r' % i for i in sorted(v.iteritems())) + |
|
108 | ', '.join('%r: %r' % i for i in sorted(v.iteritems())) + | |
109 | '}') |
|
109 | '}') | |
110 | env['HG_' + k.upper()] = v |
|
110 | env['HG_' + k.upper()] = v | |
111 |
|
111 | |||
112 | if repo: |
|
112 | if repo: | |
113 | cwd = repo.root |
|
113 | cwd = repo.root | |
114 | else: |
|
114 | else: | |
115 | cwd = os.getcwd() |
|
115 | cwd = os.getcwd() | |
116 | if 'HG_URL' in env and env['HG_URL'].startswith('remote:http'): |
|
116 | if 'HG_URL' in env and env['HG_URL'].startswith('remote:http'): | |
117 | r = util.system(cmd, environ=env, cwd=cwd, out=ui) |
|
117 | r = util.system(cmd, environ=env, cwd=cwd, out=ui) | |
118 | else: |
|
118 | else: | |
119 | r = util.system(cmd, environ=env, cwd=cwd, out=ui.fout) |
|
119 | r = util.system(cmd, environ=env, cwd=cwd, out=ui.fout) | |
120 | if r: |
|
120 | if r: | |
121 | desc, r = util.explainexit(r) |
|
121 | desc, r = util.explainexit(r) | |
122 | if throw: |
|
122 | if throw: | |
123 | raise util.Abort(_('%s hook %s') % (name, desc)) |
|
123 | raise util.Abort(_('%s hook %s') % (name, desc)) | |
124 | ui.warn(_('warning: %s hook %s\n') % (name, desc)) |
|
124 | ui.warn(_('warning: %s hook %s\n') % (name, desc)) | |
125 | return r |
|
125 | return r | |
126 |
|
126 | |||
127 | def _allhooks(ui): |
|
127 | def _allhooks(ui): | |
128 | hooks = [] |
|
128 | hooks = [] | |
129 | for name, cmd in ui.configitems('hooks'): |
|
129 | for name, cmd in ui.configitems('hooks'): | |
130 | if not name.startswith('priority'): |
|
130 | if not name.startswith('priority'): | |
131 | priority = ui.configint('hooks', 'priority.%s' % name, 0) |
|
131 | priority = ui.configint('hooks', 'priority.%s' % name, 0) | |
132 | hooks.append((-priority, len(hooks), name, cmd)) |
|
132 | hooks.append((-priority, len(hooks), name, cmd)) | |
133 | return [(k, v) for p, o, k, v in sorted(hooks)] |
|
133 | return [(k, v) for p, o, k, v in sorted(hooks)] | |
134 |
|
134 | |||
135 | _redirect = False |
|
135 | _redirect = False | |
136 | def redirect(state): |
|
136 | def redirect(state): | |
137 | global _redirect |
|
137 | global _redirect | |
138 | _redirect = state |
|
138 | _redirect = state | |
139 |
|
139 | |||
140 | def hook(ui, repo, name, throw=False, **args): |
|
140 | def hook(ui, repo, name, throw=False, **args): | |
141 | if not ui.callhooks: |
|
141 | if not ui.callhooks: | |
142 | return False |
|
142 | return False | |
143 |
|
143 | |||
144 | r = False |
|
144 | r = False | |
145 |
|
145 | |||
146 | oldstdout = -1 |
|
146 | oldstdout = -1 | |
147 | if _redirect: |
|
147 | if _redirect: | |
148 | try: |
|
148 | try: | |
149 | stdoutno = sys.__stdout__.fileno() |
|
149 | stdoutno = sys.__stdout__.fileno() | |
150 | stderrno = sys.__stderr__.fileno() |
|
150 | stderrno = sys.__stderr__.fileno() | |
151 | # temporarily redirect stdout to stderr, if possible |
|
151 | # temporarily redirect stdout to stderr, if possible | |
152 | if stdoutno >= 0 and stderrno >= 0: |
|
152 | if stdoutno >= 0 and stderrno >= 0: | |
153 | sys.__stdout__.flush() |
|
153 | sys.__stdout__.flush() | |
154 | oldstdout = os.dup(stdoutno) |
|
154 | oldstdout = os.dup(stdoutno) | |
155 | os.dup2(stderrno, stdoutno) |
|
155 | os.dup2(stderrno, stdoutno) | |
156 | except AttributeError: |
|
156 | except AttributeError: | |
157 | # __stdout/err__ doesn't have fileno(), it's not a real file |
|
157 | # __stdout__/__stderr__ doesn't have fileno(), it's not a real file | |
158 | pass |
|
158 | pass | |
159 |
|
159 | |||
160 | try: |
|
160 | try: | |
161 | for hname, cmd in _allhooks(ui): |
|
161 | for hname, cmd in _allhooks(ui): | |
162 | if hname.split('.')[0] != name or not cmd: |
|
162 | if hname.split('.')[0] != name or not cmd: | |
163 | continue |
|
163 | continue | |
164 | if util.safehasattr(cmd, '__call__'): |
|
164 | if util.safehasattr(cmd, '__call__'): | |
165 | r = _pythonhook(ui, repo, name, hname, cmd, args, throw) or r |
|
165 | r = _pythonhook(ui, repo, name, hname, cmd, args, throw) or r | |
166 | elif cmd.startswith('python:'): |
|
166 | elif cmd.startswith('python:'): | |
167 | if cmd.count(':') >= 2: |
|
167 | if cmd.count(':') >= 2: | |
168 | path, cmd = cmd[7:].rsplit(':', 1) |
|
168 | path, cmd = cmd[7:].rsplit(':', 1) | |
169 | path = util.expandpath(path) |
|
169 | path = util.expandpath(path) | |
170 | if repo: |
|
170 | if repo: | |
171 | path = os.path.join(repo.root, path) |
|
171 | path = os.path.join(repo.root, path) | |
172 | try: |
|
172 | try: | |
173 | mod = extensions.loadpath(path, 'hghook.%s' % hname) |
|
173 | mod = extensions.loadpath(path, 'hghook.%s' % hname) | |
174 | except Exception: |
|
174 | except Exception: | |
175 | ui.write(_("loading %s hook failed:\n") % hname) |
|
175 | ui.write(_("loading %s hook failed:\n") % hname) | |
176 | raise |
|
176 | raise | |
177 | hookfn = getattr(mod, cmd) |
|
177 | hookfn = getattr(mod, cmd) | |
178 | else: |
|
178 | else: | |
179 | hookfn = cmd[7:].strip() |
|
179 | hookfn = cmd[7:].strip() | |
180 | r = _pythonhook(ui, repo, name, hname, hookfn, args, throw) or r |
|
180 | r = _pythonhook(ui, repo, name, hname, hookfn, args, throw) or r | |
181 | else: |
|
181 | else: | |
182 | r = _exthook(ui, repo, hname, cmd, args, throw) or r |
|
182 | r = _exthook(ui, repo, hname, cmd, args, throw) or r | |
183 | finally: |
|
183 | finally: | |
184 | if _redirect and oldstdout >= 0: |
|
184 | if _redirect and oldstdout >= 0: | |
185 | os.dup2(oldstdout, stdoutno) |
|
185 | os.dup2(oldstdout, stdoutno) | |
186 | os.close(oldstdout) |
|
186 | os.close(oldstdout) | |
187 |
|
187 | |||
188 | return r |
|
188 | return r |
@@ -1,674 +1,674 | |||||
1 | # Copyright 2010, Google Inc. |
|
1 | # Copyright 2010, Google Inc. | |
2 | # All rights reserved. |
|
2 | # All rights reserved. | |
3 | # |
|
3 | # | |
4 | # Redistribution and use in source and binary forms, with or without |
|
4 | # Redistribution and use in source and binary forms, with or without | |
5 | # modification, are permitted provided that the following conditions are |
|
5 | # modification, are permitted provided that the following conditions are | |
6 | # met: |
|
6 | # met: | |
7 | # |
|
7 | # | |
8 | # * Redistributions of source code must retain the above copyright |
|
8 | # * Redistributions of source code must retain the above copyright | |
9 | # notice, this list of conditions and the following disclaimer. |
|
9 | # notice, this list of conditions and the following disclaimer. | |
10 | # * Redistributions in binary form must reproduce the above |
|
10 | # * Redistributions in binary form must reproduce the above | |
11 | # copyright notice, this list of conditions and the following disclaimer |
|
11 | # copyright notice, this list of conditions and the following disclaimer | |
12 | # in the documentation and/or other materials provided with the |
|
12 | # in the documentation and/or other materials provided with the | |
13 | # distribution. |
|
13 | # distribution. | |
14 | # * Neither the name of Google Inc. nor the names of its |
|
14 | # * Neither the name of Google Inc. nor the names of its | |
15 | # contributors may be used to endorse or promote products derived from |
|
15 | # contributors may be used to endorse or promote products derived from | |
16 | # this software without specific prior written permission. |
|
16 | # this software without specific prior written permission. | |
17 |
|
17 | |||
18 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
|
18 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
19 | # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
|
19 | # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
20 | # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
|
20 | # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | |
21 | # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
|
21 | # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | |
22 | # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
|
22 | # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |
23 | # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
|
23 | # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | |
24 | # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
|
24 | # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |
25 | # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
|
25 | # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | |
26 | # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
|
26 | # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
27 | # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
|
27 | # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
28 | # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
|
28 | # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
29 | """Improved HTTP/1.1 client library |
|
29 | """Improved HTTP/1.1 client library | |
30 |
|
30 | |||
31 | This library contains an HTTPConnection which is similar to the one in |
|
31 | This library contains an HTTPConnection which is similar to the one in | |
32 | httplib, but has several additional features: |
|
32 | httplib, but has several additional features: | |
33 |
|
33 | |||
34 | * supports keepalives natively |
|
34 | * supports keepalives natively | |
35 | * uses select() to block for incoming data |
|
35 | * uses select() to block for incoming data | |
36 | * notices when the server responds early to a request |
|
36 | * notices when the server responds early to a request | |
37 | * implements ssl inline instead of in a different class |
|
37 | * implements ssl inline instead of in a different class | |
38 | """ |
|
38 | """ | |
39 |
|
39 | |||
40 | import cStringIO |
|
40 | import cStringIO | |
41 | import errno |
|
41 | import errno | |
42 | import httplib |
|
42 | import httplib | |
43 | import logging |
|
43 | import logging | |
44 | import rfc822 |
|
44 | import rfc822 | |
45 | import select |
|
45 | import select | |
46 | import socket |
|
46 | import socket | |
47 |
|
47 | |||
48 | import _readers |
|
48 | import _readers | |
49 | import socketutil |
|
49 | import socketutil | |
50 |
|
50 | |||
51 | logger = logging.getLogger(__name__) |
|
51 | logger = logging.getLogger(__name__) | |
52 |
|
52 | |||
53 | __all__ = ['HTTPConnection', 'HTTPResponse'] |
|
53 | __all__ = ['HTTPConnection', 'HTTPResponse'] | |
54 |
|
54 | |||
55 | HTTP_VER_1_0 = 'HTTP/1.0' |
|
55 | HTTP_VER_1_0 = 'HTTP/1.0' | |
56 | HTTP_VER_1_1 = 'HTTP/1.1' |
|
56 | HTTP_VER_1_1 = 'HTTP/1.1' | |
57 |
|
57 | |||
58 | OUTGOING_BUFFER_SIZE = 1 << 15 |
|
58 | OUTGOING_BUFFER_SIZE = 1 << 15 | |
59 | INCOMING_BUFFER_SIZE = 1 << 20 |
|
59 | INCOMING_BUFFER_SIZE = 1 << 20 | |
60 |
|
60 | |||
61 | HDR_ACCEPT_ENCODING = 'accept-encoding' |
|
61 | HDR_ACCEPT_ENCODING = 'accept-encoding' | |
62 | HDR_CONNECTION_CTRL = 'connection' |
|
62 | HDR_CONNECTION_CTRL = 'connection' | |
63 | HDR_CONTENT_LENGTH = 'content-length' |
|
63 | HDR_CONTENT_LENGTH = 'content-length' | |
64 | HDR_XFER_ENCODING = 'transfer-encoding' |
|
64 | HDR_XFER_ENCODING = 'transfer-encoding' | |
65 |
|
65 | |||
66 | XFER_ENCODING_CHUNKED = 'chunked' |
|
66 | XFER_ENCODING_CHUNKED = 'chunked' | |
67 |
|
67 | |||
68 | CONNECTION_CLOSE = 'close' |
|
68 | CONNECTION_CLOSE = 'close' | |
69 |
|
69 | |||
70 | EOL = '\r\n' |
|
70 | EOL = '\r\n' | |
71 | _END_HEADERS = EOL * 2 |
|
71 | _END_HEADERS = EOL * 2 | |
72 |
|
72 | |||
73 | # Based on some searching around, 1 second seems like a reasonable |
|
73 | # Based on some searching around, 1 second seems like a reasonable | |
74 | # default here. |
|
74 | # default here. | |
75 | TIMEOUT_ASSUME_CONTINUE = 1 |
|
75 | TIMEOUT_ASSUME_CONTINUE = 1 | |
76 | TIMEOUT_DEFAULT = None |
|
76 | TIMEOUT_DEFAULT = None | |
77 |
|
77 | |||
78 |
|
78 | |||
79 | class HTTPResponse(object): |
|
79 | class HTTPResponse(object): | |
80 | """Response from an HTTP server. |
|
80 | """Response from an HTTP server. | |
81 |
|
81 | |||
82 | The response will continue to load as available. If you need the |
|
82 | The response will continue to load as available. If you need the | |
83 | complete response before continuing, check the .complete() method. |
|
83 | complete response before continuing, check the .complete() method. | |
84 | """ |
|
84 | """ | |
85 | def __init__(self, sock, timeout, method): |
|
85 | def __init__(self, sock, timeout, method): | |
86 | self.sock = sock |
|
86 | self.sock = sock | |
87 | self.method = method |
|
87 | self.method = method | |
88 | self.raw_response = '' |
|
88 | self.raw_response = '' | |
89 | self._headers_len = 0 |
|
89 | self._headers_len = 0 | |
90 | self.headers = None |
|
90 | self.headers = None | |
91 | self.will_close = False |
|
91 | self.will_close = False | |
92 | self.status_line = '' |
|
92 | self.status_line = '' | |
93 | self.status = None |
|
93 | self.status = None | |
94 | self.continued = False |
|
94 | self.continued = False | |
95 | self.http_version = None |
|
95 | self.http_version = None | |
96 | self.reason = None |
|
96 | self.reason = None | |
97 | self._reader = None |
|
97 | self._reader = None | |
98 |
|
98 | |||
99 | self._read_location = 0 |
|
99 | self._read_location = 0 | |
100 | self._eol = EOL |
|
100 | self._eol = EOL | |
101 |
|
101 | |||
102 | self._timeout = timeout |
|
102 | self._timeout = timeout | |
103 |
|
103 | |||
104 | @property |
|
104 | @property | |
105 | def _end_headers(self): |
|
105 | def _end_headers(self): | |
106 | return self._eol * 2 |
|
106 | return self._eol * 2 | |
107 |
|
107 | |||
108 | def complete(self): |
|
108 | def complete(self): | |
109 | """Returns true if this response is completely loaded. |
|
109 | """Returns true if this response is completely loaded. | |
110 |
|
110 | |||
111 | Note that if this is a connection where complete means the |
|
111 | Note that if this is a connection where complete means the | |
112 | socket is closed, this will nearly always return False, even |
|
112 | socket is closed, this will nearly always return False, even | |
113 | in cases where all the data has actually been loaded. |
|
113 | in cases where all the data has actually been loaded. | |
114 | """ |
|
114 | """ | |
115 | if self._reader: |
|
115 | if self._reader: | |
116 | return self._reader.done() |
|
116 | return self._reader.done() | |
117 |
|
117 | |||
118 | def _close(self): |
|
118 | def _close(self): | |
119 | if self._reader is not None: |
|
119 | if self._reader is not None: | |
120 | self._reader._close() |
|
120 | self._reader._close() | |
121 |
|
121 | |||
122 | def readline(self): |
|
122 | def readline(self): | |
123 | """Read a single line from the response body. |
|
123 | """Read a single line from the response body. | |
124 |
|
124 | |||
125 | This may block until either a line ending is found or the |
|
125 | This may block until either a line ending is found or the | |
126 | response is complete. |
|
126 | response is complete. | |
127 | """ |
|
127 | """ | |
128 | # TODO: move this into the reader interface where it can be |
|
128 | # TODO: move this into the reader interface where it can be | |
129 | # smarter (and probably avoid copies) |
|
129 | # smarter (and probably avoid copies) | |
130 | bytes = [] |
|
130 | bytes = [] | |
131 | while not bytes: |
|
131 | while not bytes: | |
132 | try: |
|
132 | try: | |
133 | bytes = [self._reader.read(1)] |
|
133 | bytes = [self._reader.read(1)] | |
134 | except _readers.ReadNotReady: |
|
134 | except _readers.ReadNotReady: | |
135 | self._select() |
|
135 | self._select() | |
136 | while bytes[-1] != '\n' and not self.complete(): |
|
136 | while bytes[-1] != '\n' and not self.complete(): | |
137 | self._select() |
|
137 | self._select() | |
138 | bytes.append(self._reader.read(1)) |
|
138 | bytes.append(self._reader.read(1)) | |
139 | if bytes[-1] != '\n': |
|
139 | if bytes[-1] != '\n': | |
140 | next = self._reader.read(1) |
|
140 | next = self._reader.read(1) | |
141 | while next and next != '\n': |
|
141 | while next and next != '\n': | |
142 | bytes.append(next) |
|
142 | bytes.append(next) | |
143 | next = self._reader.read(1) |
|
143 | next = self._reader.read(1) | |
144 | bytes.append(next) |
|
144 | bytes.append(next) | |
145 | return ''.join(bytes) |
|
145 | return ''.join(bytes) | |
146 |
|
146 | |||
147 | def read(self, length=None): |
|
147 | def read(self, length=None): | |
148 | # if length is None, unbounded read |
|
148 | # if length is None, unbounded read | |
149 | while (not self.complete() # never select on a finished read |
|
149 | while (not self.complete() # never select on a finished read | |
150 | and (not length # unbounded, so we wait for complete() |
|
150 | and (not length # unbounded, so we wait for complete() | |
151 | or length > self._reader.available_data)): |
|
151 | or length > self._reader.available_data)): | |
152 | self._select() |
|
152 | self._select() | |
153 | if not length: |
|
153 | if not length: | |
154 | length = self._reader.available_data |
|
154 | length = self._reader.available_data | |
155 | r = self._reader.read(length) |
|
155 | r = self._reader.read(length) | |
156 | if self.complete() and self.will_close: |
|
156 | if self.complete() and self.will_close: | |
157 | self.sock.close() |
|
157 | self.sock.close() | |
158 | return r |
|
158 | return r | |
159 |
|
159 | |||
160 | def _select(self): |
|
160 | def _select(self): | |
161 | r, _, _ = select.select([self.sock], [], [], self._timeout) |
|
161 | r, _, _ = select.select([self.sock], [], [], self._timeout) | |
162 | if not r: |
|
162 | if not r: | |
163 | # socket was not readable. If the response is not |
|
163 | # socket was not readable. If the response is not | |
164 | # complete, raise a timeout. |
|
164 | # complete, raise a timeout. | |
165 | if not self.complete(): |
|
165 | if not self.complete(): | |
166 | logger.info('timed out with timeout of %s', self._timeout) |
|
166 | logger.info('timed out with timeout of %s', self._timeout) | |
167 | raise HTTPTimeoutException('timeout reading data') |
|
167 | raise HTTPTimeoutException('timeout reading data') | |
168 | try: |
|
168 | try: | |
169 | data = self.sock.recv(INCOMING_BUFFER_SIZE) |
|
169 | data = self.sock.recv(INCOMING_BUFFER_SIZE) | |
170 | except socket.sslerror, e: |
|
170 | except socket.sslerror, e: | |
171 | if e.args[0] != socket.SSL_ERROR_WANT_READ: |
|
171 | if e.args[0] != socket.SSL_ERROR_WANT_READ: | |
172 | raise |
|
172 | raise | |
173 | logger.debug('SSL_WANT_READ in _select, should retry later') |
|
173 | logger.debug('SSL_ERROR_WANT_READ in _select, should retry later') | |
174 | return True |
|
174 | return True | |
175 | logger.debug('response read %d data during _select', len(data)) |
|
175 | logger.debug('response read %d data during _select', len(data)) | |
176 | # If the socket was readable and no data was read, that means |
|
176 | # If the socket was readable and no data was read, that means | |
177 | # the socket was closed. Inform the reader (if any) so it can |
|
177 | # the socket was closed. Inform the reader (if any) so it can | |
178 | # raise an exception if this is an invalid situation. |
|
178 | # raise an exception if this is an invalid situation. | |
179 | if not data: |
|
179 | if not data: | |
180 | if self._reader: |
|
180 | if self._reader: | |
181 | self._reader._close() |
|
181 | self._reader._close() | |
182 | return False |
|
182 | return False | |
183 | else: |
|
183 | else: | |
184 | self._load_response(data) |
|
184 | self._load_response(data) | |
185 | return True |
|
185 | return True | |
186 |
|
186 | |||
187 | def _load_response(self, data): |
|
187 | def _load_response(self, data): | |
188 | # Being here implies we're not at the end of the headers yet, |
|
188 | # Being here implies we're not at the end of the headers yet, | |
189 | # since at the end of this method if headers were completely |
|
189 | # since at the end of this method if headers were completely | |
190 | # loaded we replace this method with the load() method of the |
|
190 | # loaded we replace this method with the load() method of the | |
191 | # reader we created. |
|
191 | # reader we created. | |
192 | self.raw_response += data |
|
192 | self.raw_response += data | |
193 | # This is a bogus server with bad line endings |
|
193 | # This is a bogus server with bad line endings | |
194 | if self._eol not in self.raw_response: |
|
194 | if self._eol not in self.raw_response: | |
195 | for bad_eol in ('\n', '\r'): |
|
195 | for bad_eol in ('\n', '\r'): | |
196 | if (bad_eol in self.raw_response |
|
196 | if (bad_eol in self.raw_response | |
197 | # verify that bad_eol is not the end of the incoming data |
|
197 | # verify that bad_eol is not the end of the incoming data | |
198 | # as this could be a response line that just got |
|
198 | # as this could be a response line that just got | |
199 | # split between \r and \n. |
|
199 | # split between \r and \n. | |
200 | and (self.raw_response.index(bad_eol) < |
|
200 | and (self.raw_response.index(bad_eol) < | |
201 | (len(self.raw_response) - 1))): |
|
201 | (len(self.raw_response) - 1))): | |
202 | logger.info('bogus line endings detected, ' |
|
202 | logger.info('bogus line endings detected, ' | |
203 | 'using %r for EOL', bad_eol) |
|
203 | 'using %r for EOL', bad_eol) | |
204 | self._eol = bad_eol |
|
204 | self._eol = bad_eol | |
205 | break |
|
205 | break | |
206 | # exit early if not at end of headers |
|
206 | # exit early if not at end of headers | |
207 | if self._end_headers not in self.raw_response or self.headers: |
|
207 | if self._end_headers not in self.raw_response or self.headers: | |
208 | return |
|
208 | return | |
209 |
|
209 | |||
210 | # handle 100-continue response |
|
210 | # handle 100-continue response | |
211 | hdrs, body = self.raw_response.split(self._end_headers, 1) |
|
211 | hdrs, body = self.raw_response.split(self._end_headers, 1) | |
212 | http_ver, status = hdrs.split(' ', 1) |
|
212 | http_ver, status = hdrs.split(' ', 1) | |
213 | if status.startswith('100'): |
|
213 | if status.startswith('100'): | |
214 | self.raw_response = body |
|
214 | self.raw_response = body | |
215 | self.continued = True |
|
215 | self.continued = True | |
216 | logger.debug('continue seen, setting body to %r', body) |
|
216 | logger.debug('continue seen, setting body to %r', body) | |
217 | return |
|
217 | return | |
218 |
|
218 | |||
219 | # arriving here means we should parse response headers |
|
219 | # arriving here means we should parse response headers | |
220 | # as all headers have arrived completely |
|
220 | # as all headers have arrived completely | |
221 | hdrs, body = self.raw_response.split(self._end_headers, 1) |
|
221 | hdrs, body = self.raw_response.split(self._end_headers, 1) | |
222 | del self.raw_response |
|
222 | del self.raw_response | |
223 | if self._eol in hdrs: |
|
223 | if self._eol in hdrs: | |
224 | self.status_line, hdrs = hdrs.split(self._eol, 1) |
|
224 | self.status_line, hdrs = hdrs.split(self._eol, 1) | |
225 | else: |
|
225 | else: | |
226 | self.status_line = hdrs |
|
226 | self.status_line = hdrs | |
227 | hdrs = '' |
|
227 | hdrs = '' | |
228 | # TODO HTTP < 1.0 support |
|
228 | # TODO HTTP < 1.0 support | |
229 | (self.http_version, self.status, |
|
229 | (self.http_version, self.status, | |
230 | self.reason) = self.status_line.split(' ', 2) |
|
230 | self.reason) = self.status_line.split(' ', 2) | |
231 | self.status = int(self.status) |
|
231 | self.status = int(self.status) | |
232 | if self._eol != EOL: |
|
232 | if self._eol != EOL: | |
233 | hdrs = hdrs.replace(self._eol, '\r\n') |
|
233 | hdrs = hdrs.replace(self._eol, '\r\n') | |
234 | headers = rfc822.Message(cStringIO.StringIO(hdrs)) |
|
234 | headers = rfc822.Message(cStringIO.StringIO(hdrs)) | |
235 | content_len = None |
|
235 | content_len = None | |
236 | if HDR_CONTENT_LENGTH in headers: |
|
236 | if HDR_CONTENT_LENGTH in headers: | |
237 | content_len = int(headers[HDR_CONTENT_LENGTH]) |
|
237 | content_len = int(headers[HDR_CONTENT_LENGTH]) | |
238 | if self.http_version == HTTP_VER_1_0: |
|
238 | if self.http_version == HTTP_VER_1_0: | |
239 | self.will_close = True |
|
239 | self.will_close = True | |
240 | elif HDR_CONNECTION_CTRL in headers: |
|
240 | elif HDR_CONNECTION_CTRL in headers: | |
241 | self.will_close = ( |
|
241 | self.will_close = ( | |
242 | headers[HDR_CONNECTION_CTRL].lower() == CONNECTION_CLOSE) |
|
242 | headers[HDR_CONNECTION_CTRL].lower() == CONNECTION_CLOSE) | |
243 | if (HDR_XFER_ENCODING in headers |
|
243 | if (HDR_XFER_ENCODING in headers | |
244 | and headers[HDR_XFER_ENCODING].lower() == XFER_ENCODING_CHUNKED): |
|
244 | and headers[HDR_XFER_ENCODING].lower() == XFER_ENCODING_CHUNKED): | |
245 | self._reader = _readers.ChunkedReader(self._eol) |
|
245 | self._reader = _readers.ChunkedReader(self._eol) | |
246 | logger.debug('using a chunked reader') |
|
246 | logger.debug('using a chunked reader') | |
247 | else: |
|
247 | else: | |
248 | # HEAD responses are forbidden from returning a body, and |
|
248 | # HEAD responses are forbidden from returning a body, and | |
249 | # it's implausible for a CONNECT response to use |
|
249 | # it's implausible for a CONNECT response to use | |
250 | # close-is-end logic for an OK response. |
|
250 | # close-is-end logic for an OK response. | |
251 | if (self.method == 'HEAD' or |
|
251 | if (self.method == 'HEAD' or | |
252 | (self.method == 'CONNECT' and content_len is None)): |
|
252 | (self.method == 'CONNECT' and content_len is None)): | |
253 | content_len = 0 |
|
253 | content_len = 0 | |
254 | if content_len is not None: |
|
254 | if content_len is not None: | |
255 | logger.debug('using a content-length reader with length %d', |
|
255 | logger.debug('using a content-length reader with length %d', | |
256 | content_len) |
|
256 | content_len) | |
257 | self._reader = _readers.ContentLengthReader(content_len) |
|
257 | self._reader = _readers.ContentLengthReader(content_len) | |
258 | else: |
|
258 | else: | |
259 | # Response body had no length specified and is not |
|
259 | # Response body had no length specified and is not | |
260 | # chunked, so the end of the body will only be |
|
260 | # chunked, so the end of the body will only be | |
261 | # identifiable by the termination of the socket by the |
|
261 | # identifiable by the termination of the socket by the | |
262 | # server. My interpretation of the spec means that we |
|
262 | # server. My interpretation of the spec means that we | |
263 | # are correct in hitting this case if |
|
263 | # are correct in hitting this case if | |
264 | # transfer-encoding, content-length, and |
|
264 | # transfer-encoding, content-length, and | |
265 | # connection-control were left unspecified. |
|
265 | # connection-control were left unspecified. | |
266 | self._reader = _readers.CloseIsEndReader() |
|
266 | self._reader = _readers.CloseIsEndReader() | |
267 | logger.debug('using a close-is-end reader') |
|
267 | logger.debug('using a close-is-end reader') | |
268 | self.will_close = True |
|
268 | self.will_close = True | |
269 |
|
269 | |||
270 | if body: |
|
270 | if body: | |
271 | self._reader._load(body) |
|
271 | self._reader._load(body) | |
272 | logger.debug('headers complete') |
|
272 | logger.debug('headers complete') | |
273 | self.headers = headers |
|
273 | self.headers = headers | |
274 | self._load_response = self._reader._load |
|
274 | self._load_response = self._reader._load | |
275 |
|
275 | |||
276 |
|
276 | |||
277 | class HTTPConnection(object): |
|
277 | class HTTPConnection(object): | |
278 | """Connection to a single http server. |
|
278 | """Connection to a single http server. | |
279 |
|
279 | |||
280 | Supports 100-continue and keepalives natively. Uses select() for |
|
280 | Supports 100-continue and keepalives natively. Uses select() for | |
281 | non-blocking socket operations. |
|
281 | non-blocking socket operations. | |
282 | """ |
|
282 | """ | |
283 | http_version = HTTP_VER_1_1 |
|
283 | http_version = HTTP_VER_1_1 | |
284 | response_class = HTTPResponse |
|
284 | response_class = HTTPResponse | |
285 |
|
285 | |||
286 | def __init__(self, host, port=None, use_ssl=None, ssl_validator=None, |
|
286 | def __init__(self, host, port=None, use_ssl=None, ssl_validator=None, | |
287 | timeout=TIMEOUT_DEFAULT, |
|
287 | timeout=TIMEOUT_DEFAULT, | |
288 | continue_timeout=TIMEOUT_ASSUME_CONTINUE, |
|
288 | continue_timeout=TIMEOUT_ASSUME_CONTINUE, | |
289 | proxy_hostport=None, **ssl_opts): |
|
289 | proxy_hostport=None, **ssl_opts): | |
290 | """Create a new HTTPConnection. |
|
290 | """Create a new HTTPConnection. | |
291 |
|
291 | |||
292 | Args: |
|
292 | Args: | |
293 | host: The host to which we'll connect. |
|
293 | host: The host to which we'll connect. | |
294 | port: Optional. The port over which we'll connect. Default 80 for |
|
294 | port: Optional. The port over which we'll connect. Default 80 for | |
295 | non-ssl, 443 for ssl. |
|
295 | non-ssl, 443 for ssl. | |
296 | use_ssl: Optional. Whether to use ssl. Defaults to False if port is |
|
296 | use_ssl: Optional. Whether to use ssl. Defaults to False if port is | |
297 | not 443, true if port is 443. |
|
297 | not 443, true if port is 443. | |
298 | ssl_validator: a function(socket) to validate the ssl cert |
|
298 | ssl_validator: a function(socket) to validate the ssl cert | |
299 | timeout: Optional. Connection timeout, default is TIMEOUT_DEFAULT. |
|
299 | timeout: Optional. Connection timeout, default is TIMEOUT_DEFAULT. | |
300 | continue_timeout: Optional. Timeout for waiting on an expected |
|
300 | continue_timeout: Optional. Timeout for waiting on an expected | |
301 | "100 Continue" response. Default is TIMEOUT_ASSUME_CONTINUE. |
|
301 | "100 Continue" response. Default is TIMEOUT_ASSUME_CONTINUE. | |
302 | proxy_hostport: Optional. Tuple of (host, port) to use as an http |
|
302 | proxy_hostport: Optional. Tuple of (host, port) to use as an http | |
303 | proxy for the connection. Default is to not use a proxy. |
|
303 | proxy for the connection. Default is to not use a proxy. | |
304 | """ |
|
304 | """ | |
305 | if port is None and host.count(':') == 1 or ']:' in host: |
|
305 | if port is None and host.count(':') == 1 or ']:' in host: | |
306 | host, port = host.rsplit(':', 1) |
|
306 | host, port = host.rsplit(':', 1) | |
307 | port = int(port) |
|
307 | port = int(port) | |
308 | if '[' in host: |
|
308 | if '[' in host: | |
309 | host = host[1:-1] |
|
309 | host = host[1:-1] | |
310 | if use_ssl is None and port is None: |
|
310 | if use_ssl is None and port is None: | |
311 | use_ssl = False |
|
311 | use_ssl = False | |
312 | port = 80 |
|
312 | port = 80 | |
313 | elif use_ssl is None: |
|
313 | elif use_ssl is None: | |
314 | use_ssl = (port == 443) |
|
314 | use_ssl = (port == 443) | |
315 | elif port is None: |
|
315 | elif port is None: | |
316 | port = (use_ssl and 443 or 80) |
|
316 | port = (use_ssl and 443 or 80) | |
317 | self.port = port |
|
317 | self.port = port | |
318 | if use_ssl and not socketutil.have_ssl: |
|
318 | if use_ssl and not socketutil.have_ssl: | |
319 | raise Exception('ssl requested but unavailable on this Python') |
|
319 | raise Exception('ssl requested but unavailable on this Python') | |
320 | self.ssl = use_ssl |
|
320 | self.ssl = use_ssl | |
321 | self.ssl_opts = ssl_opts |
|
321 | self.ssl_opts = ssl_opts | |
322 | self._ssl_validator = ssl_validator |
|
322 | self._ssl_validator = ssl_validator | |
323 | self.host = host |
|
323 | self.host = host | |
324 | self.sock = None |
|
324 | self.sock = None | |
325 | self._current_response = None |
|
325 | self._current_response = None | |
326 | self._current_response_taken = False |
|
326 | self._current_response_taken = False | |
327 | if proxy_hostport is None: |
|
327 | if proxy_hostport is None: | |
328 | self._proxy_host = self._proxy_port = None |
|
328 | self._proxy_host = self._proxy_port = None | |
329 | else: |
|
329 | else: | |
330 | self._proxy_host, self._proxy_port = proxy_hostport |
|
330 | self._proxy_host, self._proxy_port = proxy_hostport | |
331 |
|
331 | |||
332 | self.timeout = timeout |
|
332 | self.timeout = timeout | |
333 | self.continue_timeout = continue_timeout |
|
333 | self.continue_timeout = continue_timeout | |
334 |
|
334 | |||
335 | def _connect(self): |
|
335 | def _connect(self): | |
336 | """Connect to the host and port specified in __init__.""" |
|
336 | """Connect to the host and port specified in __init__.""" | |
337 | if self.sock: |
|
337 | if self.sock: | |
338 | return |
|
338 | return | |
339 | if self._proxy_host is not None: |
|
339 | if self._proxy_host is not None: | |
340 | logger.info('Connecting to http proxy %s:%s', |
|
340 | logger.info('Connecting to http proxy %s:%s', | |
341 | self._proxy_host, self._proxy_port) |
|
341 | self._proxy_host, self._proxy_port) | |
342 | sock = socketutil.create_connection((self._proxy_host, |
|
342 | sock = socketutil.create_connection((self._proxy_host, | |
343 | self._proxy_port)) |
|
343 | self._proxy_port)) | |
344 | if self.ssl: |
|
344 | if self.ssl: | |
345 | # TODO proxy header support |
|
345 | # TODO proxy header support | |
346 | data = self.buildheaders('CONNECT', '%s:%d' % (self.host, |
|
346 | data = self.buildheaders('CONNECT', '%s:%d' % (self.host, | |
347 | self.port), |
|
347 | self.port), | |
348 | {}, HTTP_VER_1_0) |
|
348 | {}, HTTP_VER_1_0) | |
349 | sock.send(data) |
|
349 | sock.send(data) | |
350 | sock.setblocking(0) |
|
350 | sock.setblocking(0) | |
351 | r = self.response_class(sock, self.timeout, 'CONNECT') |
|
351 | r = self.response_class(sock, self.timeout, 'CONNECT') | |
352 | timeout_exc = HTTPTimeoutException( |
|
352 | timeout_exc = HTTPTimeoutException( | |
353 | 'Timed out waiting for CONNECT response from proxy') |
|
353 | 'Timed out waiting for CONNECT response from proxy') | |
354 | while not r.complete(): |
|
354 | while not r.complete(): | |
355 | try: |
|
355 | try: | |
356 | if not r._select(): |
|
356 | if not r._select(): | |
357 | if not r.complete(): |
|
357 | if not r.complete(): | |
358 | raise timeout_exc |
|
358 | raise timeout_exc | |
359 | except HTTPTimeoutException: |
|
359 | except HTTPTimeoutException: | |
360 | # This raise/except pattern looks goofy, but |
|
360 | # This raise/except pattern looks goofy, but | |
361 | # _select can raise the timeout as well as the |
|
361 | # _select can raise the timeout as well as the | |
362 | # loop body. I wish it wasn't this convoluted, |
|
362 | # loop body. I wish it wasn't this convoluted, | |
363 | # but I don't have a better solution |
|
363 | # but I don't have a better solution | |
364 | # immediately handy. |
|
364 | # immediately handy. | |
365 | raise timeout_exc |
|
365 | raise timeout_exc | |
366 | if r.status != 200: |
|
366 | if r.status != 200: | |
367 | raise HTTPProxyConnectFailedException( |
|
367 | raise HTTPProxyConnectFailedException( | |
368 | 'Proxy connection failed: %d %s' % (r.status, |
|
368 | 'Proxy connection failed: %d %s' % (r.status, | |
369 | r.read())) |
|
369 | r.read())) | |
370 | logger.info('CONNECT (for SSL) to %s:%s via proxy succeeded.', |
|
370 | logger.info('CONNECT (for SSL) to %s:%s via proxy succeeded.', | |
371 | self.host, self.port) |
|
371 | self.host, self.port) | |
372 | else: |
|
372 | else: | |
373 | sock = socketutil.create_connection((self.host, self.port)) |
|
373 | sock = socketutil.create_connection((self.host, self.port)) | |
374 | if self.ssl: |
|
374 | if self.ssl: | |
375 | # This is the default, but in the case of proxied SSL |
|
375 | # This is the default, but in the case of proxied SSL | |
376 | # requests the proxy logic above will have cleared |
|
376 | # requests the proxy logic above will have cleared | |
377 | # blocking mode, so re-enable it just to be safe. |
|
377 | # blocking mode, so re-enable it just to be safe. | |
378 | sock.setblocking(1) |
|
378 | sock.setblocking(1) | |
379 | logger.debug('wrapping socket for ssl with options %r', |
|
379 | logger.debug('wrapping socket for ssl with options %r', | |
380 | self.ssl_opts) |
|
380 | self.ssl_opts) | |
381 | sock = socketutil.wrap_socket(sock, **self.ssl_opts) |
|
381 | sock = socketutil.wrap_socket(sock, **self.ssl_opts) | |
382 | if self._ssl_validator: |
|
382 | if self._ssl_validator: | |
383 | self._ssl_validator(sock) |
|
383 | self._ssl_validator(sock) | |
384 | sock.setblocking(0) |
|
384 | sock.setblocking(0) | |
385 | self.sock = sock |
|
385 | self.sock = sock | |
386 |
|
386 | |||
387 | def buildheaders(self, method, path, headers, http_ver): |
|
387 | def buildheaders(self, method, path, headers, http_ver): | |
388 | if self.ssl and self.port == 443 or self.port == 80: |
|
388 | if self.ssl and self.port == 443 or self.port == 80: | |
389 | # default port for protocol, so leave it out |
|
389 | # default port for protocol, so leave it out | |
390 | hdrhost = self.host |
|
390 | hdrhost = self.host | |
391 | else: |
|
391 | else: | |
392 | # include nonstandard port in header |
|
392 | # include nonstandard port in header | |
393 | if ':' in self.host: # must be IPv6 |
|
393 | if ':' in self.host: # must be IPv6 | |
394 | hdrhost = '[%s]:%d' % (self.host, self.port) |
|
394 | hdrhost = '[%s]:%d' % (self.host, self.port) | |
395 | else: |
|
395 | else: | |
396 | hdrhost = '%s:%d' % (self.host, self.port) |
|
396 | hdrhost = '%s:%d' % (self.host, self.port) | |
397 | if self._proxy_host and not self.ssl: |
|
397 | if self._proxy_host and not self.ssl: | |
398 | # When talking to a regular http proxy we must send the |
|
398 | # When talking to a regular http proxy we must send the | |
399 | # full URI, but in all other cases we must not (although |
|
399 | # full URI, but in all other cases we must not (although | |
400 | # technically RFC 2616 says servers must accept our |
|
400 | # technically RFC 2616 says servers must accept our | |
401 | # request if we screw up, experimentally few do that |
|
401 | # request if we screw up, experimentally few do that | |
402 | # correctly.) |
|
402 | # correctly.) | |
403 | assert path[0] == '/', 'path must start with a /' |
|
403 | assert path[0] == '/', 'path must start with a /' | |
404 | path = 'http://%s%s' % (hdrhost, path) |
|
404 | path = 'http://%s%s' % (hdrhost, path) | |
405 | outgoing = ['%s %s %s%s' % (method, path, http_ver, EOL)] |
|
405 | outgoing = ['%s %s %s%s' % (method, path, http_ver, EOL)] | |
406 | headers['host'] = ('Host', hdrhost) |
|
406 | headers['host'] = ('Host', hdrhost) | |
407 | headers[HDR_ACCEPT_ENCODING] = (HDR_ACCEPT_ENCODING, 'identity') |
|
407 | headers[HDR_ACCEPT_ENCODING] = (HDR_ACCEPT_ENCODING, 'identity') | |
408 | for hdr, val in headers.itervalues(): |
|
408 | for hdr, val in headers.itervalues(): | |
409 | outgoing.append('%s: %s%s' % (hdr, val, EOL)) |
|
409 | outgoing.append('%s: %s%s' % (hdr, val, EOL)) | |
410 | outgoing.append(EOL) |
|
410 | outgoing.append(EOL) | |
411 | return ''.join(outgoing) |
|
411 | return ''.join(outgoing) | |
412 |
|
412 | |||
413 | def close(self): |
|
413 | def close(self): | |
414 | """Close the connection to the server. |
|
414 | """Close the connection to the server. | |
415 |
|
415 | |||
416 | This is a no-op if the connection is already closed. The |
|
416 | This is a no-op if the connection is already closed. The | |
417 | connection may automatically close if requested by the server |
|
417 | connection may automatically close if requested by the server | |
418 | or required by the nature of a response. |
|
418 | or required by the nature of a response. | |
419 | """ |
|
419 | """ | |
420 | if self.sock is None: |
|
420 | if self.sock is None: | |
421 | return |
|
421 | return | |
422 | self.sock.close() |
|
422 | self.sock.close() | |
423 | self.sock = None |
|
423 | self.sock = None | |
424 | logger.info('closed connection to %s on %s', self.host, self.port) |
|
424 | logger.info('closed connection to %s on %s', self.host, self.port) | |
425 |
|
425 | |||
426 | def busy(self): |
|
426 | def busy(self): | |
427 | """Returns True if this connection object is currently in use. |
|
427 | """Returns True if this connection object is currently in use. | |
428 |
|
428 | |||
429 | If a response is still pending, this will return True, even if |
|
429 | If a response is still pending, this will return True, even if | |
430 | the request has finished sending. In the future, |
|
430 | the request has finished sending. In the future, | |
431 | HTTPConnection may transparently juggle multiple connections |
|
431 | HTTPConnection may transparently juggle multiple connections | |
432 | to the server, in which case this will be useful to detect if |
|
432 | to the server, in which case this will be useful to detect if | |
433 | any of those connections is ready for use. |
|
433 | any of those connections is ready for use. | |
434 | """ |
|
434 | """ | |
435 | cr = self._current_response |
|
435 | cr = self._current_response | |
436 | if cr is not None: |
|
436 | if cr is not None: | |
437 | if self._current_response_taken: |
|
437 | if self._current_response_taken: | |
438 | if cr.will_close: |
|
438 | if cr.will_close: | |
439 | self.sock = None |
|
439 | self.sock = None | |
440 | self._current_response = None |
|
440 | self._current_response = None | |
441 | return False |
|
441 | return False | |
442 | elif cr.complete(): |
|
442 | elif cr.complete(): | |
443 | self._current_response = None |
|
443 | self._current_response = None | |
444 | return False |
|
444 | return False | |
445 | return True |
|
445 | return True | |
446 | return False |
|
446 | return False | |
447 |
|
447 | |||
448 | def request(self, method, path, body=None, headers={}, |
|
448 | def request(self, method, path, body=None, headers={}, | |
449 | expect_continue=False): |
|
449 | expect_continue=False): | |
450 | """Send a request to the server. |
|
450 | """Send a request to the server. | |
451 |
|
451 | |||
452 | For increased flexibility, this does not return the response |
|
452 | For increased flexibility, this does not return the response | |
453 | object. Future versions of HTTPConnection that juggle multiple |
|
453 | object. Future versions of HTTPConnection that juggle multiple | |
454 | sockets will be able to send (for example) 5 requests all at |
|
454 | sockets will be able to send (for example) 5 requests all at | |
455 | once, and then let the requests arrive as data is |
|
455 | once, and then let the requests arrive as data is | |
456 | available. Use the `getresponse()` method to retrieve the |
|
456 | available. Use the `getresponse()` method to retrieve the | |
457 | response. |
|
457 | response. | |
458 | """ |
|
458 | """ | |
459 | if self.busy(): |
|
459 | if self.busy(): | |
460 | raise httplib.CannotSendRequest( |
|
460 | raise httplib.CannotSendRequest( | |
461 | 'Can not send another request before ' |
|
461 | 'Can not send another request before ' | |
462 | 'current response is read!') |
|
462 | 'current response is read!') | |
463 | self._current_response_taken = False |
|
463 | self._current_response_taken = False | |
464 |
|
464 | |||
465 | logger.info('sending %s request for %s to %s on port %s', |
|
465 | logger.info('sending %s request for %s to %s on port %s', | |
466 | method, path, self.host, self.port) |
|
466 | method, path, self.host, self.port) | |
467 | hdrs = dict((k.lower(), (k, v)) for k, v in headers.iteritems()) |
|
467 | hdrs = dict((k.lower(), (k, v)) for k, v in headers.iteritems()) | |
468 | if hdrs.get('expect', ('', ''))[1].lower() == '100-continue': |
|
468 | if hdrs.get('expect', ('', ''))[1].lower() == '100-continue': | |
469 | expect_continue = True |
|
469 | expect_continue = True | |
470 | elif expect_continue: |
|
470 | elif expect_continue: | |
471 | hdrs['expect'] = ('Expect', '100-Continue') |
|
471 | hdrs['expect'] = ('Expect', '100-Continue') | |
472 |
|
472 | |||
473 | chunked = False |
|
473 | chunked = False | |
474 | if body and HDR_CONTENT_LENGTH not in hdrs: |
|
474 | if body and HDR_CONTENT_LENGTH not in hdrs: | |
475 | if getattr(body, '__len__', False): |
|
475 | if getattr(body, '__len__', False): | |
476 | hdrs[HDR_CONTENT_LENGTH] = (HDR_CONTENT_LENGTH, len(body)) |
|
476 | hdrs[HDR_CONTENT_LENGTH] = (HDR_CONTENT_LENGTH, len(body)) | |
477 | elif getattr(body, 'read', False): |
|
477 | elif getattr(body, 'read', False): | |
478 | hdrs[HDR_XFER_ENCODING] = (HDR_XFER_ENCODING, |
|
478 | hdrs[HDR_XFER_ENCODING] = (HDR_XFER_ENCODING, | |
479 | XFER_ENCODING_CHUNKED) |
|
479 | XFER_ENCODING_CHUNKED) | |
480 | chunked = True |
|
480 | chunked = True | |
481 | else: |
|
481 | else: | |
482 | raise BadRequestData('body has no __len__() nor read()') |
|
482 | raise BadRequestData('body has no __len__() nor read()') | |
483 |
|
483 | |||
484 | self._connect() |
|
484 | self._connect() | |
485 | outgoing_headers = self.buildheaders( |
|
485 | outgoing_headers = self.buildheaders( | |
486 | method, path, hdrs, self.http_version) |
|
486 | method, path, hdrs, self.http_version) | |
487 | response = None |
|
487 | response = None | |
488 | first = True |
|
488 | first = True | |
489 |
|
489 | |||
490 | def reconnect(where): |
|
490 | def reconnect(where): | |
491 | logger.info('reconnecting during %s', where) |
|
491 | logger.info('reconnecting during %s', where) | |
492 | self.close() |
|
492 | self.close() | |
493 | self._connect() |
|
493 | self._connect() | |
494 |
|
494 | |||
495 | while ((outgoing_headers or body) |
|
495 | while ((outgoing_headers or body) | |
496 | and not (response and response.complete())): |
|
496 | and not (response and response.complete())): | |
497 | select_timeout = self.timeout |
|
497 | select_timeout = self.timeout | |
498 | out = outgoing_headers or body |
|
498 | out = outgoing_headers or body | |
499 | blocking_on_continue = False |
|
499 | blocking_on_continue = False | |
500 | if expect_continue and not outgoing_headers and not ( |
|
500 | if expect_continue and not outgoing_headers and not ( | |
501 | response and (response.headers or response.continued)): |
|
501 | response and (response.headers or response.continued)): | |
502 | logger.info( |
|
502 | logger.info( | |
503 | 'waiting up to %s seconds for' |
|
503 | 'waiting up to %s seconds for' | |
504 | ' continue response from server', |
|
504 | ' continue response from server', | |
505 | self.continue_timeout) |
|
505 | self.continue_timeout) | |
506 | select_timeout = self.continue_timeout |
|
506 | select_timeout = self.continue_timeout | |
507 | blocking_on_continue = True |
|
507 | blocking_on_continue = True | |
508 | out = False |
|
508 | out = False | |
509 | if out: |
|
509 | if out: | |
510 | w = [self.sock] |
|
510 | w = [self.sock] | |
511 | else: |
|
511 | else: | |
512 | w = [] |
|
512 | w = [] | |
513 | r, w, x = select.select([self.sock], w, [], select_timeout) |
|
513 | r, w, x = select.select([self.sock], w, [], select_timeout) | |
514 | # if we were expecting a 100 continue and it's been long |
|
514 | # if we were expecting a 100 continue and it's been long | |
515 | # enough, just go ahead and assume it's ok. This is the |
|
515 | # enough, just go ahead and assume it's ok. This is the | |
516 | # recommended behavior from the RFC. |
|
516 | # recommended behavior from the RFC. | |
517 | if r == w == x == []: |
|
517 | if r == w == x == []: | |
518 | if blocking_on_continue: |
|
518 | if blocking_on_continue: | |
519 | expect_continue = False |
|
519 | expect_continue = False | |
520 | logger.info('no response to continue expectation from ' |
|
520 | logger.info('no response to continue expectation from ' | |
521 | 'server, optimistically sending request body') |
|
521 | 'server, optimistically sending request body') | |
522 | else: |
|
522 | else: | |
523 | raise HTTPTimeoutException('timeout sending data') |
|
523 | raise HTTPTimeoutException('timeout sending data') | |
524 | was_first = first |
|
524 | was_first = first | |
525 |
|
525 | |||
526 | # incoming data |
|
526 | # incoming data | |
527 | if r: |
|
527 | if r: | |
528 | try: |
|
528 | try: | |
529 | try: |
|
529 | try: | |
530 | data = r[0].recv(INCOMING_BUFFER_SIZE) |
|
530 | data = r[0].recv(INCOMING_BUFFER_SIZE) | |
531 | except socket.sslerror, e: |
|
531 | except socket.sslerror, e: | |
532 | if e.args[0] != socket.SSL_ERROR_WANT_READ: |
|
532 | if e.args[0] != socket.SSL_ERROR_WANT_READ: | |
533 | raise |
|
533 | raise | |
534 | logger.debug( |
|
534 | logger.debug( | |
535 | 'SSL_WANT_READ while sending data, retrying...') |
|
535 | 'SSL_ERROR_WANT_READ while sending data, retrying...') | |
536 | continue |
|
536 | continue | |
537 | if not data: |
|
537 | if not data: | |
538 | logger.info('socket appears closed in read') |
|
538 | logger.info('socket appears closed in read') | |
539 | self.sock = None |
|
539 | self.sock = None | |
540 | self._current_response = None |
|
540 | self._current_response = None | |
541 | if response is not None: |
|
541 | if response is not None: | |
542 | response._close() |
|
542 | response._close() | |
543 | # This if/elif ladder is a bit subtle, |
|
543 | # This if/elif ladder is a bit subtle, | |
544 | # comments in each branch should help. |
|
544 | # comments in each branch should help. | |
545 | if response is not None and response.complete(): |
|
545 | if response is not None and response.complete(): | |
546 | # Server responded completely and then |
|
546 | # Server responded completely and then | |
547 | # closed the socket. We should just shut |
|
547 | # closed the socket. We should just shut | |
548 | # things down and let the caller get their |
|
548 | # things down and let the caller get their | |
549 | # response. |
|
549 | # response. | |
550 | logger.info('Got an early response, ' |
|
550 | logger.info('Got an early response, ' | |
551 | 'aborting remaining request.') |
|
551 | 'aborting remaining request.') | |
552 | break |
|
552 | break | |
553 | elif was_first and response is None: |
|
553 | elif was_first and response is None: | |
554 | # Most likely a keepalive that got killed |
|
554 | # Most likely a keepalive that got killed | |
555 | # on the server's end. Commonly happens |
|
555 | # on the server's end. Commonly happens | |
556 | # after getting a really large response |
|
556 | # after getting a really large response | |
557 | # from the server. |
|
557 | # from the server. | |
558 | logger.info( |
|
558 | logger.info( | |
559 | 'Connection appeared closed in read on first' |
|
559 | 'Connection appeared closed in read on first' | |
560 | ' request loop iteration, will retry.') |
|
560 | ' request loop iteration, will retry.') | |
561 | reconnect('read') |
|
561 | reconnect('read') | |
562 | continue |
|
562 | continue | |
563 | else: |
|
563 | else: | |
564 | # We didn't just send the first data hunk, |
|
564 | # We didn't just send the first data hunk, | |
565 | # and either have a partial response or no |
|
565 | # and either have a partial response or no | |
566 | # response at all. There's really nothing |
|
566 | # response at all. There's really nothing | |
567 | # meaningful we can do here. |
|
567 | # meaningful we can do here. | |
568 | raise HTTPStateError( |
|
568 | raise HTTPStateError( | |
569 | 'Connection appears closed after ' |
|
569 | 'Connection appears closed after ' | |
570 | 'some request data was written, but the ' |
|
570 | 'some request data was written, but the ' | |
571 | 'response was missing or incomplete!') |
|
571 | 'response was missing or incomplete!') | |
572 | logger.debug('read %d bytes in request()', len(data)) |
|
572 | logger.debug('read %d bytes in request()', len(data)) | |
573 | if response is None: |
|
573 | if response is None: | |
574 | response = self.response_class(r[0], self.timeout, method) |
|
574 | response = self.response_class(r[0], self.timeout, method) | |
575 | response._load_response(data) |
|
575 | response._load_response(data) | |
576 | # Jump to the next select() call so we load more |
|
576 | # Jump to the next select() call so we load more | |
577 | # data if the server is still sending us content. |
|
577 | # data if the server is still sending us content. | |
578 | continue |
|
578 | continue | |
579 | except socket.error, e: |
|
579 | except socket.error, e: | |
580 | if e[0] != errno.EPIPE and not was_first: |
|
580 | if e[0] != errno.EPIPE and not was_first: | |
581 | raise |
|
581 | raise | |
582 |
|
582 | |||
583 | # outgoing data |
|
583 | # outgoing data | |
584 | if w and out: |
|
584 | if w and out: | |
585 | try: |
|
585 | try: | |
586 | if getattr(out, 'read', False): |
|
586 | if getattr(out, 'read', False): | |
587 | data = out.read(OUTGOING_BUFFER_SIZE) |
|
587 | data = out.read(OUTGOING_BUFFER_SIZE) | |
588 | if not data: |
|
588 | if not data: | |
589 | continue |
|
589 | continue | |
590 | if len(data) < OUTGOING_BUFFER_SIZE: |
|
590 | if len(data) < OUTGOING_BUFFER_SIZE: | |
591 | if chunked: |
|
591 | if chunked: | |
592 | body = '0' + EOL + EOL |
|
592 | body = '0' + EOL + EOL | |
593 | else: |
|
593 | else: | |
594 | body = None |
|
594 | body = None | |
595 | if chunked: |
|
595 | if chunked: | |
596 | out = hex(len(data))[2:] + EOL + data + EOL |
|
596 | out = hex(len(data))[2:] + EOL + data + EOL | |
597 | else: |
|
597 | else: | |
598 | out = data |
|
598 | out = data | |
599 | amt = w[0].send(out) |
|
599 | amt = w[0].send(out) | |
600 | except socket.error, e: |
|
600 | except socket.error, e: | |
601 | if e[0] == socket.SSL_ERROR_WANT_WRITE and self.ssl: |
|
601 | if e[0] == socket.SSL_ERROR_WANT_WRITE and self.ssl: | |
602 | # This means that SSL hasn't flushed its buffer into |
|
602 | # This means that SSL hasn't flushed its buffer into | |
603 | # the socket yet. |
|
603 | # the socket yet. | |
604 | # TODO: find a way to block on ssl flushing its buffer |
|
604 | # TODO: find a way to block on ssl flushing its buffer | |
605 | # similar to selecting on a raw socket. |
|
605 | # similar to selecting on a raw socket. | |
606 | continue |
|
606 | continue | |
607 | elif (e[0] not in (errno.ECONNRESET, errno.EPIPE) |
|
607 | elif (e[0] not in (errno.ECONNRESET, errno.EPIPE) | |
608 | and not first): |
|
608 | and not first): | |
609 | raise |
|
609 | raise | |
610 | reconnect('write') |
|
610 | reconnect('write') | |
611 | amt = self.sock.send(out) |
|
611 | amt = self.sock.send(out) | |
612 | logger.debug('sent %d', amt) |
|
612 | logger.debug('sent %d', amt) | |
613 | first = False |
|
613 | first = False | |
614 | # stash data we think we sent in case the socket breaks |
|
614 | # stash data we think we sent in case the socket breaks | |
615 | # when we read from it |
|
615 | # when we read from it | |
616 | if was_first: |
|
616 | if was_first: | |
617 | sent_data = out[:amt] |
|
617 | sent_data = out[:amt] | |
618 | if out is body: |
|
618 | if out is body: | |
619 | body = out[amt:] |
|
619 | body = out[amt:] | |
620 | else: |
|
620 | else: | |
621 | outgoing_headers = out[amt:] |
|
621 | outgoing_headers = out[amt:] | |
622 |
|
622 | |||
623 | # close if the server response said to or responded before eating |
|
623 | # close if the server response said to or responded before eating | |
624 | # the whole request |
|
624 | # the whole request | |
625 | if response is None: |
|
625 | if response is None: | |
626 | response = self.response_class(self.sock, self.timeout, method) |
|
626 | response = self.response_class(self.sock, self.timeout, method) | |
627 | complete = response.complete() |
|
627 | complete = response.complete() | |
628 | data_left = bool(outgoing_headers or body) |
|
628 | data_left = bool(outgoing_headers or body) | |
629 | if data_left: |
|
629 | if data_left: | |
630 | logger.info('stopped sending request early, ' |
|
630 | logger.info('stopped sending request early, ' | |
631 | 'will close the socket to be safe.') |
|
631 | 'will close the socket to be safe.') | |
632 | response.will_close = True |
|
632 | response.will_close = True | |
633 | if response.will_close: |
|
633 | if response.will_close: | |
634 | # The socket will be closed by the response, so we disown |
|
634 | # The socket will be closed by the response, so we disown | |
635 | # the socket |
|
635 | # the socket | |
636 | self.sock = None |
|
636 | self.sock = None | |
637 | self._current_response = response |
|
637 | self._current_response = response | |
638 |
|
638 | |||
639 | def getresponse(self): |
|
639 | def getresponse(self): | |
640 | if self._current_response is None: |
|
640 | if self._current_response is None: | |
641 | raise httplib.ResponseNotReady() |
|
641 | raise httplib.ResponseNotReady() | |
642 | r = self._current_response |
|
642 | r = self._current_response | |
643 | while r.headers is None: |
|
643 | while r.headers is None: | |
644 | if not r._select() and not r.complete(): |
|
644 | if not r._select() and not r.complete(): | |
645 | raise _readers.HTTPRemoteClosedError() |
|
645 | raise _readers.HTTPRemoteClosedError() | |
646 | if r.will_close: |
|
646 | if r.will_close: | |
647 | self.sock = None |
|
647 | self.sock = None | |
648 | self._current_response = None |
|
648 | self._current_response = None | |
649 | elif r.complete(): |
|
649 | elif r.complete(): | |
650 | self._current_response = None |
|
650 | self._current_response = None | |
651 | else: |
|
651 | else: | |
652 | self._current_response_taken = True |
|
652 | self._current_response_taken = True | |
653 | return r |
|
653 | return r | |
654 |
|
654 | |||
655 |
|
655 | |||
656 | class HTTPTimeoutException(httplib.HTTPException): |
|
656 | class HTTPTimeoutException(httplib.HTTPException): | |
657 | """A timeout occurred while waiting on the server.""" |
|
657 | """A timeout occurred while waiting on the server.""" | |
658 |
|
658 | |||
659 |
|
659 | |||
660 | class BadRequestData(httplib.HTTPException): |
|
660 | class BadRequestData(httplib.HTTPException): | |
661 | """Request body object has neither __len__ nor read.""" |
|
661 | """Request body object has neither __len__ nor read.""" | |
662 |
|
662 | |||
663 |
|
663 | |||
664 | class HTTPProxyConnectFailedException(httplib.HTTPException): |
|
664 | class HTTPProxyConnectFailedException(httplib.HTTPException): | |
665 | """Connecting to the HTTP proxy failed.""" |
|
665 | """Connecting to the HTTP proxy failed.""" | |
666 |
|
666 | |||
667 |
|
667 | |||
668 | class HTTPStateError(httplib.HTTPException): |
|
668 | class HTTPStateError(httplib.HTTPException): | |
669 | """Invalid internal state encountered.""" |
|
669 | """Invalid internal state encountered.""" | |
670 |
|
670 | |||
671 | # Forward this exception type from _readers since it needs to be part |
|
671 | # Forward this exception type from _readers since it needs to be part | |
672 | # of the public API. |
|
672 | # of the public API. | |
673 | HTTPRemoteClosedError = _readers.HTTPRemoteClosedError |
|
673 | HTTPRemoteClosedError = _readers.HTTPRemoteClosedError | |
674 | # no-check-code |
|
674 | # no-check-code |
@@ -1,284 +1,284 | |||||
1 | # httpconnection.py - urllib2 handler for new http support |
|
1 | # httpconnection.py - urllib2 handler for new http support | |
2 | # |
|
2 | # | |
3 | # Copyright 2005, 2006, 2007, 2008 Matt Mackall <mpm@selenic.com> |
|
3 | # Copyright 2005, 2006, 2007, 2008 Matt Mackall <mpm@selenic.com> | |
4 | # Copyright 2006, 2007 Alexis S. L. Carvalho <alexis@cecm.usp.br> |
|
4 | # Copyright 2006, 2007 Alexis S. L. Carvalho <alexis@cecm.usp.br> | |
5 | # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com> |
|
5 | # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com> | |
6 | # Copyright 2011 Google, Inc. |
|
6 | # Copyright 2011 Google, Inc. | |
7 | # |
|
7 | # | |
8 | # This software may be used and distributed according to the terms of the |
|
8 | # This software may be used and distributed according to the terms of the | |
9 | # GNU General Public License version 2 or any later version. |
|
9 | # GNU General Public License version 2 or any later version. | |
10 | import logging |
|
10 | import logging | |
11 | import socket |
|
11 | import socket | |
12 | import urllib |
|
12 | import urllib | |
13 | import urllib2 |
|
13 | import urllib2 | |
14 | import os |
|
14 | import os | |
15 |
|
15 | |||
16 | from mercurial import httpclient |
|
16 | from mercurial import httpclient | |
17 | from mercurial import sslutil |
|
17 | from mercurial import sslutil | |
18 | from mercurial import util |
|
18 | from mercurial import util | |
19 | from mercurial.i18n import _ |
|
19 | from mercurial.i18n import _ | |
20 |
|
20 | |||
21 | # moved here from url.py to avoid a cycle |
|
21 | # moved here from url.py to avoid a cycle | |
22 | class httpsendfile(object): |
|
22 | class httpsendfile(object): | |
23 | """This is a wrapper around the objects returned by python's "open". |
|
23 | """This is a wrapper around the objects returned by python's "open". | |
24 |
|
24 | |||
25 | Its purpose is to send file-like objects via HTTP. |
|
25 | Its purpose is to send file-like objects via HTTP. | |
26 | It do however not define a __len__ attribute because the length |
|
26 | It do however not define a __len__ attribute because the length | |
27 | might be more than Py_ssize_t can handle. |
|
27 | might be more than Py_ssize_t can handle. | |
28 | """ |
|
28 | """ | |
29 |
|
29 | |||
30 | def __init__(self, ui, *args, **kwargs): |
|
30 | def __init__(self, ui, *args, **kwargs): | |
31 | # We can't just "self._data = open(*args, **kwargs)" here because there |
|
31 | # We can't just "self._data = open(*args, **kwargs)" here because there | |
32 | # is an "open" function defined in this module that shadows the global |
|
32 | # is an "open" function defined in this module that shadows the global | |
33 | # one |
|
33 | # one | |
34 | self.ui = ui |
|
34 | self.ui = ui | |
35 | self._data = open(*args, **kwargs) |
|
35 | self._data = open(*args, **kwargs) | |
36 | self.seek = self._data.seek |
|
36 | self.seek = self._data.seek | |
37 | self.close = self._data.close |
|
37 | self.close = self._data.close | |
38 | self.write = self._data.write |
|
38 | self.write = self._data.write | |
39 | self.length = os.fstat(self._data.fileno()).st_size |
|
39 | self.length = os.fstat(self._data.fileno()).st_size | |
40 | self._pos = 0 |
|
40 | self._pos = 0 | |
41 | self._total = self.length // 1024 * 2 |
|
41 | self._total = self.length // 1024 * 2 | |
42 |
|
42 | |||
43 | def read(self, *args, **kwargs): |
|
43 | def read(self, *args, **kwargs): | |
44 | try: |
|
44 | try: | |
45 | ret = self._data.read(*args, **kwargs) |
|
45 | ret = self._data.read(*args, **kwargs) | |
46 | except EOFError: |
|
46 | except EOFError: | |
47 | self.ui.progress(_('sending'), None) |
|
47 | self.ui.progress(_('sending'), None) | |
48 | self._pos += len(ret) |
|
48 | self._pos += len(ret) | |
49 | # We pass double the max for total because we currently have |
|
49 | # We pass double the max for total because we currently have | |
50 | # to send the bundle twice in the case of a server that |
|
50 | # to send the bundle twice in the case of a server that | |
51 | # requires authentication. Since we can't know until we try |
|
51 | # requires authentication. Since we can't know until we try | |
52 | # once whether authentication will be required, just lie to |
|
52 | # once whether authentication will be required, just lie to | |
53 | # the user and maybe the push succeeds suddenly at 50%. |
|
53 | # the user and maybe the push succeeds suddenly at 50%. | |
54 | self.ui.progress(_('sending'), self._pos // 1024, |
|
54 | self.ui.progress(_('sending'), self._pos // 1024, | |
55 | unit=_('kb'), total=self._total) |
|
55 | unit=_('kb'), total=self._total) | |
56 | return ret |
|
56 | return ret | |
57 |
|
57 | |||
58 | # moved here from url.py to avoid a cycle |
|
58 | # moved here from url.py to avoid a cycle | |
59 | def readauthforuri(ui, uri, user): |
|
59 | def readauthforuri(ui, uri, user): | |
60 | # Read configuration |
|
60 | # Read configuration | |
61 | config = dict() |
|
61 | config = dict() | |
62 | for key, val in ui.configitems('auth'): |
|
62 | for key, val in ui.configitems('auth'): | |
63 | if '.' not in key: |
|
63 | if '.' not in key: | |
64 | ui.warn(_("ignoring invalid [auth] key '%s'\n") % key) |
|
64 | ui.warn(_("ignoring invalid [auth] key '%s'\n") % key) | |
65 | continue |
|
65 | continue | |
66 | group, setting = key.rsplit('.', 1) |
|
66 | group, setting = key.rsplit('.', 1) | |
67 | gdict = config.setdefault(group, dict()) |
|
67 | gdict = config.setdefault(group, dict()) | |
68 | if setting in ('username', 'cert', 'key'): |
|
68 | if setting in ('username', 'cert', 'key'): | |
69 | val = util.expandpath(val) |
|
69 | val = util.expandpath(val) | |
70 | gdict[setting] = val |
|
70 | gdict[setting] = val | |
71 |
|
71 | |||
72 | # Find the best match |
|
72 | # Find the best match | |
73 | if '://' in uri: |
|
73 | if '://' in uri: | |
74 | scheme, hostpath = uri.split('://', 1) |
|
74 | scheme, hostpath = uri.split('://', 1) | |
75 | else: |
|
75 | else: | |
76 |
# |
|
76 | # Python 2.4.1 doesn't provide the full URI | |
77 | scheme, hostpath = 'http', uri |
|
77 | scheme, hostpath = 'http', uri | |
78 | bestuser = None |
|
78 | bestuser = None | |
79 | bestlen = 0 |
|
79 | bestlen = 0 | |
80 | bestauth = None |
|
80 | bestauth = None | |
81 | for group, auth in config.iteritems(): |
|
81 | for group, auth in config.iteritems(): | |
82 | if user and user != auth.get('username', user): |
|
82 | if user and user != auth.get('username', user): | |
83 | # If a username was set in the URI, the entry username |
|
83 | # If a username was set in the URI, the entry username | |
84 | # must either match it or be unset |
|
84 | # must either match it or be unset | |
85 | continue |
|
85 | continue | |
86 | prefix = auth.get('prefix') |
|
86 | prefix = auth.get('prefix') | |
87 | if not prefix: |
|
87 | if not prefix: | |
88 | continue |
|
88 | continue | |
89 | p = prefix.split('://', 1) |
|
89 | p = prefix.split('://', 1) | |
90 | if len(p) > 1: |
|
90 | if len(p) > 1: | |
91 | schemes, prefix = [p[0]], p[1] |
|
91 | schemes, prefix = [p[0]], p[1] | |
92 | else: |
|
92 | else: | |
93 | schemes = (auth.get('schemes') or 'https').split() |
|
93 | schemes = (auth.get('schemes') or 'https').split() | |
94 | if (prefix == '*' or hostpath.startswith(prefix)) and \ |
|
94 | if (prefix == '*' or hostpath.startswith(prefix)) and \ | |
95 | (len(prefix) > bestlen or (len(prefix) == bestlen and \ |
|
95 | (len(prefix) > bestlen or (len(prefix) == bestlen and \ | |
96 | not bestuser and 'username' in auth)) \ |
|
96 | not bestuser and 'username' in auth)) \ | |
97 | and scheme in schemes: |
|
97 | and scheme in schemes: | |
98 | bestlen = len(prefix) |
|
98 | bestlen = len(prefix) | |
99 | bestauth = group, auth |
|
99 | bestauth = group, auth | |
100 | bestuser = auth.get('username') |
|
100 | bestuser = auth.get('username') | |
101 | if user and not bestuser: |
|
101 | if user and not bestuser: | |
102 | auth['username'] = user |
|
102 | auth['username'] = user | |
103 | return bestauth |
|
103 | return bestauth | |
104 |
|
104 | |||
105 | # Mercurial (at least until we can remove the old codepath) requires |
|
105 | # Mercurial (at least until we can remove the old codepath) requires | |
106 | # that the http response object be sufficiently file-like, so we |
|
106 | # that the http response object be sufficiently file-like, so we | |
107 | # provide a close() method here. |
|
107 | # provide a close() method here. | |
108 | class HTTPResponse(httpclient.HTTPResponse): |
|
108 | class HTTPResponse(httpclient.HTTPResponse): | |
109 | def close(self): |
|
109 | def close(self): | |
110 | pass |
|
110 | pass | |
111 |
|
111 | |||
112 | class HTTPConnection(httpclient.HTTPConnection): |
|
112 | class HTTPConnection(httpclient.HTTPConnection): | |
113 | response_class = HTTPResponse |
|
113 | response_class = HTTPResponse | |
114 | def request(self, method, uri, body=None, headers={}): |
|
114 | def request(self, method, uri, body=None, headers={}): | |
115 | if isinstance(body, httpsendfile): |
|
115 | if isinstance(body, httpsendfile): | |
116 | body.seek(0) |
|
116 | body.seek(0) | |
117 | httpclient.HTTPConnection.request(self, method, uri, body=body, |
|
117 | httpclient.HTTPConnection.request(self, method, uri, body=body, | |
118 | headers=headers) |
|
118 | headers=headers) | |
119 |
|
119 | |||
120 |
|
120 | |||
121 | _configuredlogging = False |
|
121 | _configuredlogging = False | |
122 | LOGFMT = '%(levelname)s:%(name)s:%(lineno)d:%(message)s' |
|
122 | LOGFMT = '%(levelname)s:%(name)s:%(lineno)d:%(message)s' | |
123 | # Subclass BOTH of these because otherwise urllib2 "helpfully" |
|
123 | # Subclass BOTH of these because otherwise urllib2 "helpfully" | |
124 | # reinserts them since it notices we don't include any subclasses of |
|
124 | # reinserts them since it notices we don't include any subclasses of | |
125 | # them. |
|
125 | # them. | |
126 | class http2handler(urllib2.HTTPHandler, urllib2.HTTPSHandler): |
|
126 | class http2handler(urllib2.HTTPHandler, urllib2.HTTPSHandler): | |
127 | def __init__(self, ui, pwmgr): |
|
127 | def __init__(self, ui, pwmgr): | |
128 | global _configuredlogging |
|
128 | global _configuredlogging | |
129 | urllib2.AbstractHTTPHandler.__init__(self) |
|
129 | urllib2.AbstractHTTPHandler.__init__(self) | |
130 | self.ui = ui |
|
130 | self.ui = ui | |
131 | self.pwmgr = pwmgr |
|
131 | self.pwmgr = pwmgr | |
132 | self._connections = {} |
|
132 | self._connections = {} | |
133 | loglevel = ui.config('ui', 'http2debuglevel', default=None) |
|
133 | loglevel = ui.config('ui', 'http2debuglevel', default=None) | |
134 | if loglevel and not _configuredlogging: |
|
134 | if loglevel and not _configuredlogging: | |
135 | _configuredlogging = True |
|
135 | _configuredlogging = True | |
136 | logger = logging.getLogger('mercurial.httpclient') |
|
136 | logger = logging.getLogger('mercurial.httpclient') | |
137 | logger.setLevel(getattr(logging, loglevel.upper())) |
|
137 | logger.setLevel(getattr(logging, loglevel.upper())) | |
138 | handler = logging.StreamHandler() |
|
138 | handler = logging.StreamHandler() | |
139 | handler.setFormatter(logging.Formatter(LOGFMT)) |
|
139 | handler.setFormatter(logging.Formatter(LOGFMT)) | |
140 | logger.addHandler(handler) |
|
140 | logger.addHandler(handler) | |
141 |
|
141 | |||
142 | def close_all(self): |
|
142 | def close_all(self): | |
143 | """Close and remove all connection objects being kept for reuse.""" |
|
143 | """Close and remove all connection objects being kept for reuse.""" | |
144 | for openconns in self._connections.values(): |
|
144 | for openconns in self._connections.values(): | |
145 | for conn in openconns: |
|
145 | for conn in openconns: | |
146 | conn.close() |
|
146 | conn.close() | |
147 | self._connections = {} |
|
147 | self._connections = {} | |
148 |
|
148 | |||
149 | # shamelessly borrowed from urllib2.AbstractHTTPHandler |
|
149 | # shamelessly borrowed from urllib2.AbstractHTTPHandler | |
150 | def do_open(self, http_class, req, use_ssl): |
|
150 | def do_open(self, http_class, req, use_ssl): | |
151 | """Return an addinfourl object for the request, using http_class. |
|
151 | """Return an addinfourl object for the request, using http_class. | |
152 |
|
152 | |||
153 | http_class must implement the HTTPConnection API from httplib. |
|
153 | http_class must implement the HTTPConnection API from httplib. | |
154 | The addinfourl return value is a file-like object. It also |
|
154 | The addinfourl return value is a file-like object. It also | |
155 | has methods and attributes including: |
|
155 | has methods and attributes including: | |
156 | - info(): return a mimetools.Message object for the headers |
|
156 | - info(): return a mimetools.Message object for the headers | |
157 | - geturl(): return the original request URL |
|
157 | - geturl(): return the original request URL | |
158 | - code: HTTP status code |
|
158 | - code: HTTP status code | |
159 | """ |
|
159 | """ | |
160 | # If using a proxy, the host returned by get_host() is |
|
160 | # If using a proxy, the host returned by get_host() is | |
161 | # actually the proxy. On Python 2.6.1, the real destination |
|
161 | # actually the proxy. On Python 2.6.1, the real destination | |
162 | # hostname is encoded in the URI in the urllib2 request |
|
162 | # hostname is encoded in the URI in the urllib2 request | |
163 | # object. On Python 2.6.5, it's stored in the _tunnel_host |
|
163 | # object. On Python 2.6.5, it's stored in the _tunnel_host | |
164 | # attribute which has no accessor. |
|
164 | # attribute which has no accessor. | |
165 | tunhost = getattr(req, '_tunnel_host', None) |
|
165 | tunhost = getattr(req, '_tunnel_host', None) | |
166 | host = req.get_host() |
|
166 | host = req.get_host() | |
167 | if tunhost: |
|
167 | if tunhost: | |
168 | proxyhost = host |
|
168 | proxyhost = host | |
169 | host = tunhost |
|
169 | host = tunhost | |
170 | elif req.has_proxy(): |
|
170 | elif req.has_proxy(): | |
171 | proxyhost = req.get_host() |
|
171 | proxyhost = req.get_host() | |
172 | host = req.get_selector().split('://', 1)[1].split('/', 1)[0] |
|
172 | host = req.get_selector().split('://', 1)[1].split('/', 1)[0] | |
173 | else: |
|
173 | else: | |
174 | proxyhost = None |
|
174 | proxyhost = None | |
175 |
|
175 | |||
176 | if proxyhost: |
|
176 | if proxyhost: | |
177 | if ':' in proxyhost: |
|
177 | if ':' in proxyhost: | |
178 | # Note: this means we'll explode if we try and use an |
|
178 | # Note: this means we'll explode if we try and use an | |
179 | # IPv6 http proxy. This isn't a regression, so we |
|
179 | # IPv6 http proxy. This isn't a regression, so we | |
180 | # won't worry about it for now. |
|
180 | # won't worry about it for now. | |
181 | proxyhost, proxyport = proxyhost.rsplit(':', 1) |
|
181 | proxyhost, proxyport = proxyhost.rsplit(':', 1) | |
182 | else: |
|
182 | else: | |
183 | proxyport = 3128 # squid default |
|
183 | proxyport = 3128 # squid default | |
184 | proxy = (proxyhost, proxyport) |
|
184 | proxy = (proxyhost, proxyport) | |
185 | else: |
|
185 | else: | |
186 | proxy = None |
|
186 | proxy = None | |
187 |
|
187 | |||
188 | if not host: |
|
188 | if not host: | |
189 | raise urllib2.URLError('no host given') |
|
189 | raise urllib2.URLError('no host given') | |
190 |
|
190 | |||
191 | connkey = use_ssl, host, proxy |
|
191 | connkey = use_ssl, host, proxy | |
192 | allconns = self._connections.get(connkey, []) |
|
192 | allconns = self._connections.get(connkey, []) | |
193 | conns = [c for c in allconns if not c.busy()] |
|
193 | conns = [c for c in allconns if not c.busy()] | |
194 | if conns: |
|
194 | if conns: | |
195 | h = conns[0] |
|
195 | h = conns[0] | |
196 | else: |
|
196 | else: | |
197 | if allconns: |
|
197 | if allconns: | |
198 | self.ui.debug('all connections for %s busy, making a new ' |
|
198 | self.ui.debug('all connections for %s busy, making a new ' | |
199 | 'one\n' % host) |
|
199 | 'one\n' % host) | |
200 | timeout = None |
|
200 | timeout = None | |
201 | if req.timeout is not socket._GLOBAL_DEFAULT_TIMEOUT: |
|
201 | if req.timeout is not socket._GLOBAL_DEFAULT_TIMEOUT: | |
202 | timeout = req.timeout |
|
202 | timeout = req.timeout | |
203 | h = http_class(host, timeout=timeout, proxy_hostport=proxy) |
|
203 | h = http_class(host, timeout=timeout, proxy_hostport=proxy) | |
204 | self._connections.setdefault(connkey, []).append(h) |
|
204 | self._connections.setdefault(connkey, []).append(h) | |
205 |
|
205 | |||
206 | headers = dict(req.headers) |
|
206 | headers = dict(req.headers) | |
207 | headers.update(req.unredirected_hdrs) |
|
207 | headers.update(req.unredirected_hdrs) | |
208 | headers = dict( |
|
208 | headers = dict( | |
209 | (name.title(), val) for name, val in headers.items()) |
|
209 | (name.title(), val) for name, val in headers.items()) | |
210 | try: |
|
210 | try: | |
211 | path = req.get_selector() |
|
211 | path = req.get_selector() | |
212 | if '://' in path: |
|
212 | if '://' in path: | |
213 | path = path.split('://', 1)[1].split('/', 1)[1] |
|
213 | path = path.split('://', 1)[1].split('/', 1)[1] | |
214 | if path[0] != '/': |
|
214 | if path[0] != '/': | |
215 | path = '/' + path |
|
215 | path = '/' + path | |
216 | h.request(req.get_method(), path, req.data, headers) |
|
216 | h.request(req.get_method(), path, req.data, headers) | |
217 | r = h.getresponse() |
|
217 | r = h.getresponse() | |
218 | except socket.error, err: # XXX what error? |
|
218 | except socket.error, err: # XXX what error? | |
219 | raise urllib2.URLError(err) |
|
219 | raise urllib2.URLError(err) | |
220 |
|
220 | |||
221 | # Pick apart the HTTPResponse object to get the addinfourl |
|
221 | # Pick apart the HTTPResponse object to get the addinfourl | |
222 | # object initialized properly. |
|
222 | # object initialized properly. | |
223 | r.recv = r.read |
|
223 | r.recv = r.read | |
224 |
|
224 | |||
225 | resp = urllib.addinfourl(r, r.headers, req.get_full_url()) |
|
225 | resp = urllib.addinfourl(r, r.headers, req.get_full_url()) | |
226 | resp.code = r.status |
|
226 | resp.code = r.status | |
227 | resp.msg = r.reason |
|
227 | resp.msg = r.reason | |
228 | return resp |
|
228 | return resp | |
229 |
|
229 | |||
230 | # httplib always uses the given host/port as the socket connect |
|
230 | # httplib always uses the given host/port as the socket connect | |
231 | # target, and then allows full URIs in the request path, which it |
|
231 | # target, and then allows full URIs in the request path, which it | |
232 | # then observes and treats as a signal to do proxying instead. |
|
232 | # then observes and treats as a signal to do proxying instead. | |
233 | def http_open(self, req): |
|
233 | def http_open(self, req): | |
234 | if req.get_full_url().startswith('https'): |
|
234 | if req.get_full_url().startswith('https'): | |
235 | return self.https_open(req) |
|
235 | return self.https_open(req) | |
236 | return self.do_open(HTTPConnection, req, False) |
|
236 | return self.do_open(HTTPConnection, req, False) | |
237 |
|
237 | |||
238 | def https_open(self, req): |
|
238 | def https_open(self, req): | |
239 | # req.get_full_url() does not contain credentials and we may |
|
239 | # req.get_full_url() does not contain credentials and we may | |
240 | # need them to match the certificates. |
|
240 | # need them to match the certificates. | |
241 | url = req.get_full_url() |
|
241 | url = req.get_full_url() | |
242 | user, password = self.pwmgr.find_stored_password(url) |
|
242 | user, password = self.pwmgr.find_stored_password(url) | |
243 | res = readauthforuri(self.ui, url, user) |
|
243 | res = readauthforuri(self.ui, url, user) | |
244 | if res: |
|
244 | if res: | |
245 | group, auth = res |
|
245 | group, auth = res | |
246 | self.auth = auth |
|
246 | self.auth = auth | |
247 | self.ui.debug("using auth.%s.* for authentication\n" % group) |
|
247 | self.ui.debug("using auth.%s.* for authentication\n" % group) | |
248 | else: |
|
248 | else: | |
249 | self.auth = None |
|
249 | self.auth = None | |
250 | return self.do_open(self._makesslconnection, req, True) |
|
250 | return self.do_open(self._makesslconnection, req, True) | |
251 |
|
251 | |||
252 | def _makesslconnection(self, host, port=443, *args, **kwargs): |
|
252 | def _makesslconnection(self, host, port=443, *args, **kwargs): | |
253 | keyfile = None |
|
253 | keyfile = None | |
254 | certfile = None |
|
254 | certfile = None | |
255 |
|
255 | |||
256 | if args: # key_file |
|
256 | if args: # key_file | |
257 | keyfile = args.pop(0) |
|
257 | keyfile = args.pop(0) | |
258 | if args: # cert_file |
|
258 | if args: # cert_file | |
259 | certfile = args.pop(0) |
|
259 | certfile = args.pop(0) | |
260 |
|
260 | |||
261 | # if the user has specified different key/cert files in |
|
261 | # if the user has specified different key/cert files in | |
262 | # hgrc, we prefer these |
|
262 | # hgrc, we prefer these | |
263 | if self.auth and 'key' in self.auth and 'cert' in self.auth: |
|
263 | if self.auth and 'key' in self.auth and 'cert' in self.auth: | |
264 | keyfile = self.auth['key'] |
|
264 | keyfile = self.auth['key'] | |
265 | certfile = self.auth['cert'] |
|
265 | certfile = self.auth['cert'] | |
266 |
|
266 | |||
267 | # let host port take precedence |
|
267 | # let host port take precedence | |
268 | if ':' in host and '[' not in host or ']:' in host: |
|
268 | if ':' in host and '[' not in host or ']:' in host: | |
269 | host, port = host.rsplit(':', 1) |
|
269 | host, port = host.rsplit(':', 1) | |
270 | port = int(port) |
|
270 | port = int(port) | |
271 | if '[' in host: |
|
271 | if '[' in host: | |
272 | host = host[1:-1] |
|
272 | host = host[1:-1] | |
273 |
|
273 | |||
274 | if keyfile: |
|
274 | if keyfile: | |
275 | kwargs['keyfile'] = keyfile |
|
275 | kwargs['keyfile'] = keyfile | |
276 | if certfile: |
|
276 | if certfile: | |
277 | kwargs['certfile'] = certfile |
|
277 | kwargs['certfile'] = certfile | |
278 |
|
278 | |||
279 | kwargs.update(sslutil.sslkwargs(self.ui, host)) |
|
279 | kwargs.update(sslutil.sslkwargs(self.ui, host)) | |
280 |
|
280 | |||
281 | con = HTTPConnection(host, port, use_ssl=True, |
|
281 | con = HTTPConnection(host, port, use_ssl=True, | |
282 | ssl_validator=sslutil.validator(self.ui, host), |
|
282 | ssl_validator=sslutil.validator(self.ui, host), | |
283 | **kwargs) |
|
283 | **kwargs) | |
284 | return con |
|
284 | return con |
@@ -1,255 +1,255 | |||||
1 | # mail.py - mail sending bits for mercurial |
|
1 | # mail.py - mail sending bits for mercurial | |
2 | # |
|
2 | # | |
3 | # Copyright 2006 Matt Mackall <mpm@selenic.com> |
|
3 | # Copyright 2006 Matt Mackall <mpm@selenic.com> | |
4 | # |
|
4 | # | |
5 | # This software may be used and distributed according to the terms of the |
|
5 | # This software may be used and distributed according to the terms of the | |
6 | # GNU General Public License version 2 or any later version. |
|
6 | # GNU General Public License version 2 or any later version. | |
7 |
|
7 | |||
8 | from i18n import _ |
|
8 | from i18n import _ | |
9 | import util, encoding |
|
9 | import util, encoding | |
10 | import os, smtplib, socket, quopri, time |
|
10 | import os, smtplib, socket, quopri, time | |
11 | import email.Header, email.MIMEText, email.Utils |
|
11 | import email.Header, email.MIMEText, email.Utils | |
12 |
|
12 | |||
13 | _oldheaderinit = email.Header.Header.__init__ |
|
13 | _oldheaderinit = email.Header.Header.__init__ | |
14 | def _unifiedheaderinit(self, *args, **kw): |
|
14 | def _unifiedheaderinit(self, *args, **kw): | |
15 | """ |
|
15 | """ | |
16 | Python2.7 introduces a backwards incompatible change |
|
16 | Python 2.7 introduces a backwards incompatible change | |
17 | (Python issue1974, r70772) in email.Generator.Generator code: |
|
17 | (Python issue1974, r70772) in email.Generator.Generator code: | |
18 | pre-2.7 code passed "continuation_ws='\t'" to the Header |
|
18 | pre-2.7 code passed "continuation_ws='\t'" to the Header | |
19 | constructor, and 2.7 removed this parameter. |
|
19 | constructor, and 2.7 removed this parameter. | |
20 |
|
20 | |||
21 | Default argument is continuation_ws=' ', which means that the |
|
21 | Default argument is continuation_ws=' ', which means that the | |
22 | behaviour is different in <2.7 and 2.7 |
|
22 | behaviour is different in <2.7 and 2.7 | |
23 |
|
23 | |||
24 | We consider the 2.7 behaviour to be preferable, but need |
|
24 | We consider the 2.7 behaviour to be preferable, but need | |
25 | to have an unified behaviour for versions 2.4 to 2.7 |
|
25 | to have an unified behaviour for versions 2.4 to 2.7 | |
26 | """ |
|
26 | """ | |
27 | # override continuation_ws |
|
27 | # override continuation_ws | |
28 | kw['continuation_ws'] = ' ' |
|
28 | kw['continuation_ws'] = ' ' | |
29 | _oldheaderinit(self, *args, **kw) |
|
29 | _oldheaderinit(self, *args, **kw) | |
30 |
|
30 | |||
31 | email.Header.Header.__dict__['__init__'] = _unifiedheaderinit |
|
31 | email.Header.Header.__dict__['__init__'] = _unifiedheaderinit | |
32 |
|
32 | |||
33 | def _smtp(ui): |
|
33 | def _smtp(ui): | |
34 | '''build an smtp connection and return a function to send mail''' |
|
34 | '''build an smtp connection and return a function to send mail''' | |
35 | local_hostname = ui.config('smtp', 'local_hostname') |
|
35 | local_hostname = ui.config('smtp', 'local_hostname') | |
36 | tls = ui.config('smtp', 'tls', 'none') |
|
36 | tls = ui.config('smtp', 'tls', 'none') | |
37 | # backward compatible: when tls = true, we use starttls. |
|
37 | # backward compatible: when tls = true, we use starttls. | |
38 | starttls = tls == 'starttls' or util.parsebool(tls) |
|
38 | starttls = tls == 'starttls' or util.parsebool(tls) | |
39 | smtps = tls == 'smtps' |
|
39 | smtps = tls == 'smtps' | |
40 | if (starttls or smtps) and not util.safehasattr(socket, 'ssl'): |
|
40 | if (starttls or smtps) and not util.safehasattr(socket, 'ssl'): | |
41 | raise util.Abort(_("can't use TLS: Python SSL support not installed")) |
|
41 | raise util.Abort(_("can't use TLS: Python SSL support not installed")) | |
42 | if smtps: |
|
42 | if smtps: | |
43 | ui.note(_('(using smtps)\n')) |
|
43 | ui.note(_('(using smtps)\n')) | |
44 | s = smtplib.SMTP_SSL(local_hostname=local_hostname) |
|
44 | s = smtplib.SMTP_SSL(local_hostname=local_hostname) | |
45 | else: |
|
45 | else: | |
46 | s = smtplib.SMTP(local_hostname=local_hostname) |
|
46 | s = smtplib.SMTP(local_hostname=local_hostname) | |
47 | mailhost = ui.config('smtp', 'host') |
|
47 | mailhost = ui.config('smtp', 'host') | |
48 | if not mailhost: |
|
48 | if not mailhost: | |
49 | raise util.Abort(_('smtp.host not configured - cannot send mail')) |
|
49 | raise util.Abort(_('smtp.host not configured - cannot send mail')) | |
50 | mailport = util.getport(ui.config('smtp', 'port', 25)) |
|
50 | mailport = util.getport(ui.config('smtp', 'port', 25)) | |
51 | ui.note(_('sending mail: smtp host %s, port %s\n') % |
|
51 | ui.note(_('sending mail: smtp host %s, port %s\n') % | |
52 | (mailhost, mailport)) |
|
52 | (mailhost, mailport)) | |
53 | s.connect(host=mailhost, port=mailport) |
|
53 | s.connect(host=mailhost, port=mailport) | |
54 | if starttls: |
|
54 | if starttls: | |
55 | ui.note(_('(using starttls)\n')) |
|
55 | ui.note(_('(using starttls)\n')) | |
56 | s.ehlo() |
|
56 | s.ehlo() | |
57 | s.starttls() |
|
57 | s.starttls() | |
58 | s.ehlo() |
|
58 | s.ehlo() | |
59 | username = ui.config('smtp', 'username') |
|
59 | username = ui.config('smtp', 'username') | |
60 | password = ui.config('smtp', 'password') |
|
60 | password = ui.config('smtp', 'password') | |
61 | if username and not password: |
|
61 | if username and not password: | |
62 | password = ui.getpass() |
|
62 | password = ui.getpass() | |
63 | if username and password: |
|
63 | if username and password: | |
64 | ui.note(_('(authenticating to mail server as %s)\n') % |
|
64 | ui.note(_('(authenticating to mail server as %s)\n') % | |
65 | (username)) |
|
65 | (username)) | |
66 | try: |
|
66 | try: | |
67 | s.login(username, password) |
|
67 | s.login(username, password) | |
68 | except smtplib.SMTPException, inst: |
|
68 | except smtplib.SMTPException, inst: | |
69 | raise util.Abort(inst) |
|
69 | raise util.Abort(inst) | |
70 |
|
70 | |||
71 | def send(sender, recipients, msg): |
|
71 | def send(sender, recipients, msg): | |
72 | try: |
|
72 | try: | |
73 | return s.sendmail(sender, recipients, msg) |
|
73 | return s.sendmail(sender, recipients, msg) | |
74 | except smtplib.SMTPRecipientsRefused, inst: |
|
74 | except smtplib.SMTPRecipientsRefused, inst: | |
75 | recipients = [r[1] for r in inst.recipients.values()] |
|
75 | recipients = [r[1] for r in inst.recipients.values()] | |
76 | raise util.Abort('\n' + '\n'.join(recipients)) |
|
76 | raise util.Abort('\n' + '\n'.join(recipients)) | |
77 | except smtplib.SMTPException, inst: |
|
77 | except smtplib.SMTPException, inst: | |
78 | raise util.Abort(inst) |
|
78 | raise util.Abort(inst) | |
79 |
|
79 | |||
80 | return send |
|
80 | return send | |
81 |
|
81 | |||
82 | def _sendmail(ui, sender, recipients, msg): |
|
82 | def _sendmail(ui, sender, recipients, msg): | |
83 | '''send mail using sendmail.''' |
|
83 | '''send mail using sendmail.''' | |
84 | program = ui.config('email', 'method') |
|
84 | program = ui.config('email', 'method') | |
85 | cmdline = '%s -f %s %s' % (program, util.email(sender), |
|
85 | cmdline = '%s -f %s %s' % (program, util.email(sender), | |
86 | ' '.join(map(util.email, recipients))) |
|
86 | ' '.join(map(util.email, recipients))) | |
87 | ui.note(_('sending mail: %s\n') % cmdline) |
|
87 | ui.note(_('sending mail: %s\n') % cmdline) | |
88 | fp = util.popen(cmdline, 'w') |
|
88 | fp = util.popen(cmdline, 'w') | |
89 | fp.write(msg) |
|
89 | fp.write(msg) | |
90 | ret = fp.close() |
|
90 | ret = fp.close() | |
91 | if ret: |
|
91 | if ret: | |
92 | raise util.Abort('%s %s' % ( |
|
92 | raise util.Abort('%s %s' % ( | |
93 | os.path.basename(program.split(None, 1)[0]), |
|
93 | os.path.basename(program.split(None, 1)[0]), | |
94 | util.explainexit(ret)[0])) |
|
94 | util.explainexit(ret)[0])) | |
95 |
|
95 | |||
96 | def _mbox(mbox, sender, recipients, msg): |
|
96 | def _mbox(mbox, sender, recipients, msg): | |
97 | '''write mails to mbox''' |
|
97 | '''write mails to mbox''' | |
98 | fp = open(mbox, 'ab+') |
|
98 | fp = open(mbox, 'ab+') | |
99 | # Should be time.asctime(), but Windows prints 2-characters day |
|
99 | # Should be time.asctime(), but Windows prints 2-characters day | |
100 | # of month instead of one. Make them print the same thing. |
|
100 | # of month instead of one. Make them print the same thing. | |
101 | date = time.strftime('%a %b %d %H:%M:%S %Y', time.localtime()) |
|
101 | date = time.strftime('%a %b %d %H:%M:%S %Y', time.localtime()) | |
102 | fp.write('From %s %s\n' % (sender, date)) |
|
102 | fp.write('From %s %s\n' % (sender, date)) | |
103 | fp.write(msg) |
|
103 | fp.write(msg) | |
104 | fp.write('\n\n') |
|
104 | fp.write('\n\n') | |
105 | fp.close() |
|
105 | fp.close() | |
106 |
|
106 | |||
107 | def connect(ui, mbox=None): |
|
107 | def connect(ui, mbox=None): | |
108 | '''make a mail connection. return a function to send mail. |
|
108 | '''make a mail connection. return a function to send mail. | |
109 | call as sendmail(sender, list-of-recipients, msg).''' |
|
109 | call as sendmail(sender, list-of-recipients, msg).''' | |
110 | if mbox: |
|
110 | if mbox: | |
111 | open(mbox, 'wb').close() |
|
111 | open(mbox, 'wb').close() | |
112 | return lambda s, r, m: _mbox(mbox, s, r, m) |
|
112 | return lambda s, r, m: _mbox(mbox, s, r, m) | |
113 | if ui.config('email', 'method', 'smtp') == 'smtp': |
|
113 | if ui.config('email', 'method', 'smtp') == 'smtp': | |
114 | return _smtp(ui) |
|
114 | return _smtp(ui) | |
115 | return lambda s, r, m: _sendmail(ui, s, r, m) |
|
115 | return lambda s, r, m: _sendmail(ui, s, r, m) | |
116 |
|
116 | |||
117 | def sendmail(ui, sender, recipients, msg, mbox=None): |
|
117 | def sendmail(ui, sender, recipients, msg, mbox=None): | |
118 | send = connect(ui, mbox=mbox) |
|
118 | send = connect(ui, mbox=mbox) | |
119 | return send(sender, recipients, msg) |
|
119 | return send(sender, recipients, msg) | |
120 |
|
120 | |||
121 | def validateconfig(ui): |
|
121 | def validateconfig(ui): | |
122 | '''determine if we have enough config data to try sending email.''' |
|
122 | '''determine if we have enough config data to try sending email.''' | |
123 | method = ui.config('email', 'method', 'smtp') |
|
123 | method = ui.config('email', 'method', 'smtp') | |
124 | if method == 'smtp': |
|
124 | if method == 'smtp': | |
125 | if not ui.config('smtp', 'host'): |
|
125 | if not ui.config('smtp', 'host'): | |
126 | raise util.Abort(_('smtp specified as email transport, ' |
|
126 | raise util.Abort(_('smtp specified as email transport, ' | |
127 | 'but no smtp host configured')) |
|
127 | 'but no smtp host configured')) | |
128 | else: |
|
128 | else: | |
129 | if not util.findexe(method): |
|
129 | if not util.findexe(method): | |
130 | raise util.Abort(_('%r specified as email transport, ' |
|
130 | raise util.Abort(_('%r specified as email transport, ' | |
131 | 'but not in PATH') % method) |
|
131 | 'but not in PATH') % method) | |
132 |
|
132 | |||
133 | def mimetextpatch(s, subtype='plain', display=False): |
|
133 | def mimetextpatch(s, subtype='plain', display=False): | |
134 | '''Return MIME message suitable for a patch. |
|
134 | '''Return MIME message suitable for a patch. | |
135 | Charset will be detected as utf-8 or (possibly fake) us-ascii. |
|
135 | Charset will be detected as utf-8 or (possibly fake) us-ascii. | |
136 | Transfer encodings will be used if necessary.''' |
|
136 | Transfer encodings will be used if necessary.''' | |
137 |
|
137 | |||
138 | cs = 'us-ascii' |
|
138 | cs = 'us-ascii' | |
139 | if not display: |
|
139 | if not display: | |
140 | try: |
|
140 | try: | |
141 | s.decode('us-ascii') |
|
141 | s.decode('us-ascii') | |
142 | except UnicodeDecodeError: |
|
142 | except UnicodeDecodeError: | |
143 | try: |
|
143 | try: | |
144 | s.decode('utf-8') |
|
144 | s.decode('utf-8') | |
145 | cs = 'utf-8' |
|
145 | cs = 'utf-8' | |
146 | except UnicodeDecodeError: |
|
146 | except UnicodeDecodeError: | |
147 | # We'll go with us-ascii as a fallback. |
|
147 | # We'll go with us-ascii as a fallback. | |
148 | pass |
|
148 | pass | |
149 |
|
149 | |||
150 | return mimetextqp(s, subtype, cs) |
|
150 | return mimetextqp(s, subtype, cs) | |
151 |
|
151 | |||
152 | def mimetextqp(body, subtype, charset): |
|
152 | def mimetextqp(body, subtype, charset): | |
153 | '''Return MIME message. |
|
153 | '''Return MIME message. | |
154 | Quoted-printable transfer encoding will be used if necessary. |
|
154 | Quoted-printable transfer encoding will be used if necessary. | |
155 | ''' |
|
155 | ''' | |
156 | enc = None |
|
156 | enc = None | |
157 | for line in body.splitlines(): |
|
157 | for line in body.splitlines(): | |
158 | if len(line) > 950: |
|
158 | if len(line) > 950: | |
159 | body = quopri.encodestring(body) |
|
159 | body = quopri.encodestring(body) | |
160 | enc = "quoted-printable" |
|
160 | enc = "quoted-printable" | |
161 | break |
|
161 | break | |
162 |
|
162 | |||
163 | msg = email.MIMEText.MIMEText(body, subtype, charset) |
|
163 | msg = email.MIMEText.MIMEText(body, subtype, charset) | |
164 | if enc: |
|
164 | if enc: | |
165 | del msg['Content-Transfer-Encoding'] |
|
165 | del msg['Content-Transfer-Encoding'] | |
166 | msg['Content-Transfer-Encoding'] = enc |
|
166 | msg['Content-Transfer-Encoding'] = enc | |
167 | return msg |
|
167 | return msg | |
168 |
|
168 | |||
169 | def _charsets(ui): |
|
169 | def _charsets(ui): | |
170 | '''Obtains charsets to send mail parts not containing patches.''' |
|
170 | '''Obtains charsets to send mail parts not containing patches.''' | |
171 | charsets = [cs.lower() for cs in ui.configlist('email', 'charsets')] |
|
171 | charsets = [cs.lower() for cs in ui.configlist('email', 'charsets')] | |
172 | fallbacks = [encoding.fallbackencoding.lower(), |
|
172 | fallbacks = [encoding.fallbackencoding.lower(), | |
173 | encoding.encoding.lower(), 'utf-8'] |
|
173 | encoding.encoding.lower(), 'utf-8'] | |
174 | for cs in fallbacks: # find unique charsets while keeping order |
|
174 | for cs in fallbacks: # find unique charsets while keeping order | |
175 | if cs not in charsets: |
|
175 | if cs not in charsets: | |
176 | charsets.append(cs) |
|
176 | charsets.append(cs) | |
177 | return [cs for cs in charsets if not cs.endswith('ascii')] |
|
177 | return [cs for cs in charsets if not cs.endswith('ascii')] | |
178 |
|
178 | |||
179 | def _encode(ui, s, charsets): |
|
179 | def _encode(ui, s, charsets): | |
180 | '''Returns (converted) string, charset tuple. |
|
180 | '''Returns (converted) string, charset tuple. | |
181 | Finds out best charset by cycling through sendcharsets in descending |
|
181 | Finds out best charset by cycling through sendcharsets in descending | |
182 | order. Tries both encoding and fallbackencoding for input. Only as |
|
182 | order. Tries both encoding and fallbackencoding for input. Only as | |
183 | last resort send as is in fake ascii. |
|
183 | last resort send as is in fake ascii. | |
184 | Caveat: Do not use for mail parts containing patches!''' |
|
184 | Caveat: Do not use for mail parts containing patches!''' | |
185 | try: |
|
185 | try: | |
186 | s.decode('ascii') |
|
186 | s.decode('ascii') | |
187 | except UnicodeDecodeError: |
|
187 | except UnicodeDecodeError: | |
188 | sendcharsets = charsets or _charsets(ui) |
|
188 | sendcharsets = charsets or _charsets(ui) | |
189 | for ics in (encoding.encoding, encoding.fallbackencoding): |
|
189 | for ics in (encoding.encoding, encoding.fallbackencoding): | |
190 | try: |
|
190 | try: | |
191 | u = s.decode(ics) |
|
191 | u = s.decode(ics) | |
192 | except UnicodeDecodeError: |
|
192 | except UnicodeDecodeError: | |
193 | continue |
|
193 | continue | |
194 | for ocs in sendcharsets: |
|
194 | for ocs in sendcharsets: | |
195 | try: |
|
195 | try: | |
196 | return u.encode(ocs), ocs |
|
196 | return u.encode(ocs), ocs | |
197 | except UnicodeEncodeError: |
|
197 | except UnicodeEncodeError: | |
198 | pass |
|
198 | pass | |
199 | except LookupError: |
|
199 | except LookupError: | |
200 | ui.warn(_('ignoring invalid sendcharset: %s\n') % ocs) |
|
200 | ui.warn(_('ignoring invalid sendcharset: %s\n') % ocs) | |
201 | # if ascii, or all conversion attempts fail, send (broken) ascii |
|
201 | # if ascii, or all conversion attempts fail, send (broken) ascii | |
202 | return s, 'us-ascii' |
|
202 | return s, 'us-ascii' | |
203 |
|
203 | |||
204 | def headencode(ui, s, charsets=None, display=False): |
|
204 | def headencode(ui, s, charsets=None, display=False): | |
205 | '''Returns RFC-2047 compliant header from given string.''' |
|
205 | '''Returns RFC-2047 compliant header from given string.''' | |
206 | if not display: |
|
206 | if not display: | |
207 | # split into words? |
|
207 | # split into words? | |
208 | s, cs = _encode(ui, s, charsets) |
|
208 | s, cs = _encode(ui, s, charsets) | |
209 | return str(email.Header.Header(s, cs)) |
|
209 | return str(email.Header.Header(s, cs)) | |
210 | return s |
|
210 | return s | |
211 |
|
211 | |||
212 | def _addressencode(ui, name, addr, charsets=None): |
|
212 | def _addressencode(ui, name, addr, charsets=None): | |
213 | name = headencode(ui, name, charsets) |
|
213 | name = headencode(ui, name, charsets) | |
214 | try: |
|
214 | try: | |
215 | acc, dom = addr.split('@') |
|
215 | acc, dom = addr.split('@') | |
216 | acc = acc.encode('ascii') |
|
216 | acc = acc.encode('ascii') | |
217 | dom = dom.decode(encoding.encoding).encode('idna') |
|
217 | dom = dom.decode(encoding.encoding).encode('idna') | |
218 | addr = '%s@%s' % (acc, dom) |
|
218 | addr = '%s@%s' % (acc, dom) | |
219 | except UnicodeDecodeError: |
|
219 | except UnicodeDecodeError: | |
220 | raise util.Abort(_('invalid email address: %s') % addr) |
|
220 | raise util.Abort(_('invalid email address: %s') % addr) | |
221 | except ValueError: |
|
221 | except ValueError: | |
222 | try: |
|
222 | try: | |
223 | # too strict? |
|
223 | # too strict? | |
224 | addr = addr.encode('ascii') |
|
224 | addr = addr.encode('ascii') | |
225 | except UnicodeDecodeError: |
|
225 | except UnicodeDecodeError: | |
226 | raise util.Abort(_('invalid local address: %s') % addr) |
|
226 | raise util.Abort(_('invalid local address: %s') % addr) | |
227 | return email.Utils.formataddr((name, addr)) |
|
227 | return email.Utils.formataddr((name, addr)) | |
228 |
|
228 | |||
229 | def addressencode(ui, address, charsets=None, display=False): |
|
229 | def addressencode(ui, address, charsets=None, display=False): | |
230 | '''Turns address into RFC-2047 compliant header.''' |
|
230 | '''Turns address into RFC-2047 compliant header.''' | |
231 | if display or not address: |
|
231 | if display or not address: | |
232 | return address or '' |
|
232 | return address or '' | |
233 | name, addr = email.Utils.parseaddr(address) |
|
233 | name, addr = email.Utils.parseaddr(address) | |
234 | return _addressencode(ui, name, addr, charsets) |
|
234 | return _addressencode(ui, name, addr, charsets) | |
235 |
|
235 | |||
236 | def addrlistencode(ui, addrs, charsets=None, display=False): |
|
236 | def addrlistencode(ui, addrs, charsets=None, display=False): | |
237 | '''Turns a list of addresses into a list of RFC-2047 compliant headers. |
|
237 | '''Turns a list of addresses into a list of RFC-2047 compliant headers. | |
238 | A single element of input list may contain multiple addresses, but output |
|
238 | A single element of input list may contain multiple addresses, but output | |
239 | always has one address per item''' |
|
239 | always has one address per item''' | |
240 | if display: |
|
240 | if display: | |
241 | return [a.strip() for a in addrs if a.strip()] |
|
241 | return [a.strip() for a in addrs if a.strip()] | |
242 |
|
242 | |||
243 | result = [] |
|
243 | result = [] | |
244 | for name, addr in email.Utils.getaddresses(addrs): |
|
244 | for name, addr in email.Utils.getaddresses(addrs): | |
245 | if name or addr: |
|
245 | if name or addr: | |
246 | result.append(_addressencode(ui, name, addr, charsets)) |
|
246 | result.append(_addressencode(ui, name, addr, charsets)) | |
247 | return result |
|
247 | return result | |
248 |
|
248 | |||
249 | def mimeencode(ui, s, charsets=None, display=False): |
|
249 | def mimeencode(ui, s, charsets=None, display=False): | |
250 | '''creates mime text object, encodes it if needed, and sets |
|
250 | '''creates mime text object, encodes it if needed, and sets | |
251 | charset and transfer-encoding accordingly.''' |
|
251 | charset and transfer-encoding accordingly.''' | |
252 | cs = 'us-ascii' |
|
252 | cs = 'us-ascii' | |
253 | if not display: |
|
253 | if not display: | |
254 | s, cs = _encode(ui, s, charsets) |
|
254 | s, cs = _encode(ui, s, charsets) | |
255 | return mimetextqp(s, 'plain', cs) |
|
255 | return mimetextqp(s, 'plain', cs) |
@@ -1,204 +1,204 | |||||
1 | # manifest.py - manifest revision class for mercurial |
|
1 | # manifest.py - manifest revision class for mercurial | |
2 | # |
|
2 | # | |
3 | # Copyright 2005-2007 Matt Mackall <mpm@selenic.com> |
|
3 | # Copyright 2005-2007 Matt Mackall <mpm@selenic.com> | |
4 | # |
|
4 | # | |
5 | # This software may be used and distributed according to the terms of the |
|
5 | # This software may be used and distributed according to the terms of the | |
6 | # GNU General Public License version 2 or any later version. |
|
6 | # GNU General Public License version 2 or any later version. | |
7 |
|
7 | |||
8 | from i18n import _ |
|
8 | from i18n import _ | |
9 | import mdiff, parsers, error, revlog, util |
|
9 | import mdiff, parsers, error, revlog, util | |
10 | import array, struct |
|
10 | import array, struct | |
11 |
|
11 | |||
12 | class manifestdict(dict): |
|
12 | class manifestdict(dict): | |
13 | def __init__(self, mapping=None, flags=None): |
|
13 | def __init__(self, mapping=None, flags=None): | |
14 | if mapping is None: |
|
14 | if mapping is None: | |
15 | mapping = {} |
|
15 | mapping = {} | |
16 | if flags is None: |
|
16 | if flags is None: | |
17 | flags = {} |
|
17 | flags = {} | |
18 | dict.__init__(self, mapping) |
|
18 | dict.__init__(self, mapping) | |
19 | self._flags = flags |
|
19 | self._flags = flags | |
20 | def flags(self, f): |
|
20 | def flags(self, f): | |
21 | return self._flags.get(f, "") |
|
21 | return self._flags.get(f, "") | |
22 | def withflags(self): |
|
22 | def withflags(self): | |
23 | return set(self._flags.keys()) |
|
23 | return set(self._flags.keys()) | |
24 | def set(self, f, flags): |
|
24 | def set(self, f, flags): | |
25 | self._flags[f] = flags |
|
25 | self._flags[f] = flags | |
26 | def copy(self): |
|
26 | def copy(self): | |
27 | return manifestdict(self, dict.copy(self._flags)) |
|
27 | return manifestdict(self, dict.copy(self._flags)) | |
28 |
|
28 | |||
29 | class manifest(revlog.revlog): |
|
29 | class manifest(revlog.revlog): | |
30 | def __init__(self, opener): |
|
30 | def __init__(self, opener): | |
31 | self._mancache = None |
|
31 | self._mancache = None | |
32 | revlog.revlog.__init__(self, opener, "00manifest.i") |
|
32 | revlog.revlog.__init__(self, opener, "00manifest.i") | |
33 |
|
33 | |||
34 | def parse(self, lines): |
|
34 | def parse(self, lines): | |
35 | mfdict = manifestdict() |
|
35 | mfdict = manifestdict() | |
36 | parsers.parse_manifest(mfdict, mfdict._flags, lines) |
|
36 | parsers.parse_manifest(mfdict, mfdict._flags, lines) | |
37 | return mfdict |
|
37 | return mfdict | |
38 |
|
38 | |||
39 | def readdelta(self, node): |
|
39 | def readdelta(self, node): | |
40 | r = self.rev(node) |
|
40 | r = self.rev(node) | |
41 | return self.parse(mdiff.patchtext(self.revdiff(self.deltaparent(r), r))) |
|
41 | return self.parse(mdiff.patchtext(self.revdiff(self.deltaparent(r), r))) | |
42 |
|
42 | |||
43 | def readfast(self, node): |
|
43 | def readfast(self, node): | |
44 | '''use the faster of readdelta or read''' |
|
44 | '''use the faster of readdelta or read''' | |
45 | r = self.rev(node) |
|
45 | r = self.rev(node) | |
46 | deltaparent = self.deltaparent(r) |
|
46 | deltaparent = self.deltaparent(r) | |
47 | if deltaparent != revlog.nullrev and deltaparent in self.parentrevs(r): |
|
47 | if deltaparent != revlog.nullrev and deltaparent in self.parentrevs(r): | |
48 | return self.readdelta(node) |
|
48 | return self.readdelta(node) | |
49 | return self.read(node) |
|
49 | return self.read(node) | |
50 |
|
50 | |||
51 | def read(self, node): |
|
51 | def read(self, node): | |
52 | if node == revlog.nullid: |
|
52 | if node == revlog.nullid: | |
53 | return manifestdict() # don't upset local cache |
|
53 | return manifestdict() # don't upset local cache | |
54 | if self._mancache and self._mancache[0] == node: |
|
54 | if self._mancache and self._mancache[0] == node: | |
55 | return self._mancache[1] |
|
55 | return self._mancache[1] | |
56 | text = self.revision(node) |
|
56 | text = self.revision(node) | |
57 | arraytext = array.array('c', text) |
|
57 | arraytext = array.array('c', text) | |
58 | mapping = self.parse(text) |
|
58 | mapping = self.parse(text) | |
59 | self._mancache = (node, mapping, arraytext) |
|
59 | self._mancache = (node, mapping, arraytext) | |
60 | return mapping |
|
60 | return mapping | |
61 |
|
61 | |||
62 | def _search(self, m, s, lo=0, hi=None): |
|
62 | def _search(self, m, s, lo=0, hi=None): | |
63 | '''return a tuple (start, end) that says where to find s within m. |
|
63 | '''return a tuple (start, end) that says where to find s within m. | |
64 |
|
64 | |||
65 | If the string is found m[start:end] are the line containing |
|
65 | If the string is found m[start:end] are the line containing | |
66 | that string. If start == end the string was not found and |
|
66 | that string. If start == end the string was not found and | |
67 | they indicate the proper sorted insertion point. |
|
67 | they indicate the proper sorted insertion point. | |
68 |
|
68 | |||
69 | m should be a buffer or a string |
|
69 | m should be a buffer or a string | |
70 | s is a string''' |
|
70 | s is a string''' | |
71 | def advance(i, c): |
|
71 | def advance(i, c): | |
72 | while i < lenm and m[i] != c: |
|
72 | while i < lenm and m[i] != c: | |
73 | i += 1 |
|
73 | i += 1 | |
74 | return i |
|
74 | return i | |
75 | if not s: |
|
75 | if not s: | |
76 | return (lo, lo) |
|
76 | return (lo, lo) | |
77 | lenm = len(m) |
|
77 | lenm = len(m) | |
78 | if not hi: |
|
78 | if not hi: | |
79 | hi = lenm |
|
79 | hi = lenm | |
80 | while lo < hi: |
|
80 | while lo < hi: | |
81 | mid = (lo + hi) // 2 |
|
81 | mid = (lo + hi) // 2 | |
82 | start = mid |
|
82 | start = mid | |
83 | while start > 0 and m[start - 1] != '\n': |
|
83 | while start > 0 and m[start - 1] != '\n': | |
84 | start -= 1 |
|
84 | start -= 1 | |
85 | end = advance(start, '\0') |
|
85 | end = advance(start, '\0') | |
86 | if m[start:end] < s: |
|
86 | if m[start:end] < s: | |
87 | # we know that after the null there are 40 bytes of sha1 |
|
87 | # we know that after the null there are 40 bytes of sha1 | |
88 | # this translates to the bisect lo = mid + 1 |
|
88 | # this translates to the bisect lo = mid + 1 | |
89 | lo = advance(end + 40, '\n') + 1 |
|
89 | lo = advance(end + 40, '\n') + 1 | |
90 | else: |
|
90 | else: | |
91 | # this translates to the bisect hi = mid |
|
91 | # this translates to the bisect hi = mid | |
92 | hi = start |
|
92 | hi = start | |
93 | end = advance(lo, '\0') |
|
93 | end = advance(lo, '\0') | |
94 | found = m[lo:end] |
|
94 | found = m[lo:end] | |
95 | if s == found: |
|
95 | if s == found: | |
96 | # we know that after the null there are 40 bytes of sha1 |
|
96 | # we know that after the null there are 40 bytes of sha1 | |
97 | end = advance(end + 40, '\n') |
|
97 | end = advance(end + 40, '\n') | |
98 | return (lo, end + 1) |
|
98 | return (lo, end + 1) | |
99 | else: |
|
99 | else: | |
100 | return (lo, lo) |
|
100 | return (lo, lo) | |
101 |
|
101 | |||
102 | def find(self, node, f): |
|
102 | def find(self, node, f): | |
103 | '''look up entry for a single file efficiently. |
|
103 | '''look up entry for a single file efficiently. | |
104 | return (node, flags) pair if found, (None, None) if not.''' |
|
104 | return (node, flags) pair if found, (None, None) if not.''' | |
105 | if self._mancache and self._mancache[0] == node: |
|
105 | if self._mancache and self._mancache[0] == node: | |
106 | return self._mancache[1].get(f), self._mancache[1].flags(f) |
|
106 | return self._mancache[1].get(f), self._mancache[1].flags(f) | |
107 | text = self.revision(node) |
|
107 | text = self.revision(node) | |
108 | start, end = self._search(text, f) |
|
108 | start, end = self._search(text, f) | |
109 | if start == end: |
|
109 | if start == end: | |
110 | return None, None |
|
110 | return None, None | |
111 | l = text[start:end] |
|
111 | l = text[start:end] | |
112 | f, n = l.split('\0') |
|
112 | f, n = l.split('\0') | |
113 | return revlog.bin(n[:40]), n[40:-1] |
|
113 | return revlog.bin(n[:40]), n[40:-1] | |
114 |
|
114 | |||
115 | def add(self, map, transaction, link, p1=None, p2=None, |
|
115 | def add(self, map, transaction, link, p1=None, p2=None, | |
116 | changed=None): |
|
116 | changed=None): | |
117 | # apply the changes collected during the bisect loop to our addlist |
|
117 | # apply the changes collected during the bisect loop to our addlist | |
118 | # return a delta suitable for addrevision |
|
118 | # return a delta suitable for addrevision | |
119 | def addlistdelta(addlist, x): |
|
119 | def addlistdelta(addlist, x): | |
120 | # start from the bottom up |
|
120 | # start from the bottom up | |
121 | # so changes to the offsets don't mess things up. |
|
121 | # so changes to the offsets don't mess things up. | |
122 | for start, end, content in reversed(x): |
|
122 | for start, end, content in reversed(x): | |
123 | if content: |
|
123 | if content: | |
124 | addlist[start:end] = array.array('c', content) |
|
124 | addlist[start:end] = array.array('c', content) | |
125 | else: |
|
125 | else: | |
126 | del addlist[start:end] |
|
126 | del addlist[start:end] | |
127 | return "".join(struct.pack(">lll", start, end, len(content)) |
|
127 | return "".join(struct.pack(">lll", start, end, len(content)) | |
128 | + content for start, end, content in x) |
|
128 | + content for start, end, content in x) | |
129 |
|
129 | |||
130 | def checkforbidden(l): |
|
130 | def checkforbidden(l): | |
131 | for f in l: |
|
131 | for f in l: | |
132 | if '\n' in f or '\r' in f: |
|
132 | if '\n' in f or '\r' in f: | |
133 | raise error.RevlogError( |
|
133 | raise error.RevlogError( | |
134 | _("'\\n' and '\\r' disallowed in filenames: %r") % f) |
|
134 | _("'\\n' and '\\r' disallowed in filenames: %r") % f) | |
135 |
|
135 | |||
136 | # if we're using the cache, make sure it is valid and |
|
136 | # if we're using the cache, make sure it is valid and | |
137 | # parented by the same node we're diffing against |
|
137 | # parented by the same node we're diffing against | |
138 | if not (changed and self._mancache and p1 and self._mancache[0] == p1): |
|
138 | if not (changed and self._mancache and p1 and self._mancache[0] == p1): | |
139 | files = sorted(map) |
|
139 | files = sorted(map) | |
140 | checkforbidden(files) |
|
140 | checkforbidden(files) | |
141 |
|
141 | |||
142 | # if this is changed to support newlines in filenames, |
|
142 | # if this is changed to support newlines in filenames, | |
143 | # be sure to check the templates/ dir again (especially *-raw.tmpl) |
|
143 | # be sure to check the templates/ dir again (especially *-raw.tmpl) | |
144 | hex, flags = revlog.hex, map.flags |
|
144 | hex, flags = revlog.hex, map.flags | |
145 | text = ''.join("%s\0%s%s\n" % (f, hex(map[f]), flags(f)) |
|
145 | text = ''.join("%s\0%s%s\n" % (f, hex(map[f]), flags(f)) | |
146 | for f in files) |
|
146 | for f in files) | |
147 | arraytext = array.array('c', text) |
|
147 | arraytext = array.array('c', text) | |
148 | cachedelta = None |
|
148 | cachedelta = None | |
149 | else: |
|
149 | else: | |
150 | added, removed = changed |
|
150 | added, removed = changed | |
151 | addlist = self._mancache[2] |
|
151 | addlist = self._mancache[2] | |
152 |
|
152 | |||
153 | checkforbidden(added) |
|
153 | checkforbidden(added) | |
154 | # combine the changed lists into one list for sorting |
|
154 | # combine the changed lists into one list for sorting | |
155 | work = [(x, False) for x in added] |
|
155 | work = [(x, False) for x in added] | |
156 | work.extend((x, True) for x in removed) |
|
156 | work.extend((x, True) for x in removed) | |
157 |
# this could use heapq.merge() (from |
|
157 | # this could use heapq.merge() (from Python 2.6+) or equivalent | |
158 | # since the lists are already sorted |
|
158 | # since the lists are already sorted | |
159 | work.sort() |
|
159 | work.sort() | |
160 |
|
160 | |||
161 | delta = [] |
|
161 | delta = [] | |
162 | dstart = None |
|
162 | dstart = None | |
163 | dend = None |
|
163 | dend = None | |
164 | dline = [""] |
|
164 | dline = [""] | |
165 | start = 0 |
|
165 | start = 0 | |
166 | # zero copy representation of addlist as a buffer |
|
166 | # zero copy representation of addlist as a buffer | |
167 | addbuf = util.buffer(addlist) |
|
167 | addbuf = util.buffer(addlist) | |
168 |
|
168 | |||
169 | # start with a readonly loop that finds the offset of |
|
169 | # start with a readonly loop that finds the offset of | |
170 | # each line and creates the deltas |
|
170 | # each line and creates the deltas | |
171 | for f, todelete in work: |
|
171 | for f, todelete in work: | |
172 | # bs will either be the index of the item or the insert point |
|
172 | # bs will either be the index of the item or the insert point | |
173 | start, end = self._search(addbuf, f, start) |
|
173 | start, end = self._search(addbuf, f, start) | |
174 | if not todelete: |
|
174 | if not todelete: | |
175 | l = "%s\0%s%s\n" % (f, revlog.hex(map[f]), map.flags(f)) |
|
175 | l = "%s\0%s%s\n" % (f, revlog.hex(map[f]), map.flags(f)) | |
176 | else: |
|
176 | else: | |
177 | if start == end: |
|
177 | if start == end: | |
178 | # item we want to delete was not found, error out |
|
178 | # item we want to delete was not found, error out | |
179 | raise AssertionError( |
|
179 | raise AssertionError( | |
180 | _("failed to remove %s from manifest") % f) |
|
180 | _("failed to remove %s from manifest") % f) | |
181 | l = "" |
|
181 | l = "" | |
182 | if dstart is not None and dstart <= start and dend >= start: |
|
182 | if dstart is not None and dstart <= start and dend >= start: | |
183 | if dend < end: |
|
183 | if dend < end: | |
184 | dend = end |
|
184 | dend = end | |
185 | if l: |
|
185 | if l: | |
186 | dline.append(l) |
|
186 | dline.append(l) | |
187 | else: |
|
187 | else: | |
188 | if dstart is not None: |
|
188 | if dstart is not None: | |
189 | delta.append([dstart, dend, "".join(dline)]) |
|
189 | delta.append([dstart, dend, "".join(dline)]) | |
190 | dstart = start |
|
190 | dstart = start | |
191 | dend = end |
|
191 | dend = end | |
192 | dline = [l] |
|
192 | dline = [l] | |
193 |
|
193 | |||
194 | if dstart is not None: |
|
194 | if dstart is not None: | |
195 | delta.append([dstart, dend, "".join(dline)]) |
|
195 | delta.append([dstart, dend, "".join(dline)]) | |
196 | # apply the delta to the addlist, and get a delta for addrevision |
|
196 | # apply the delta to the addlist, and get a delta for addrevision | |
197 | cachedelta = (self.rev(p1), addlistdelta(addlist, delta)) |
|
197 | cachedelta = (self.rev(p1), addlistdelta(addlist, delta)) | |
198 | arraytext = addlist |
|
198 | arraytext = addlist | |
199 | text = util.buffer(arraytext) |
|
199 | text = util.buffer(arraytext) | |
200 |
|
200 | |||
201 | n = self.addrevision(text, transaction, link, p1, p2, cachedelta) |
|
201 | n = self.addrevision(text, transaction, link, p1, p2, cachedelta) | |
202 | self._mancache = (n, map, arraytext) |
|
202 | self._mancache = (n, map, arraytext) | |
203 |
|
203 | |||
204 | return n |
|
204 | return n |
@@ -1,474 +1,475 | |||||
1 | # url.py - HTTP handling for mercurial |
|
1 | # url.py - HTTP handling for mercurial | |
2 | # |
|
2 | # | |
3 | # Copyright 2005, 2006, 2007, 2008 Matt Mackall <mpm@selenic.com> |
|
3 | # Copyright 2005, 2006, 2007, 2008 Matt Mackall <mpm@selenic.com> | |
4 | # Copyright 2006, 2007 Alexis S. L. Carvalho <alexis@cecm.usp.br> |
|
4 | # Copyright 2006, 2007 Alexis S. L. Carvalho <alexis@cecm.usp.br> | |
5 | # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com> |
|
5 | # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com> | |
6 | # |
|
6 | # | |
7 | # This software may be used and distributed according to the terms of the |
|
7 | # This software may be used and distributed according to the terms of the | |
8 | # GNU General Public License version 2 or any later version. |
|
8 | # GNU General Public License version 2 or any later version. | |
9 |
|
9 | |||
10 | import urllib, urllib2, httplib, os, socket, cStringIO |
|
10 | import urllib, urllib2, httplib, os, socket, cStringIO | |
11 | from i18n import _ |
|
11 | from i18n import _ | |
12 | import keepalive, util, sslutil |
|
12 | import keepalive, util, sslutil | |
13 | import httpconnection as httpconnectionmod |
|
13 | import httpconnection as httpconnectionmod | |
14 |
|
14 | |||
15 | class passwordmgr(urllib2.HTTPPasswordMgrWithDefaultRealm): |
|
15 | class passwordmgr(urllib2.HTTPPasswordMgrWithDefaultRealm): | |
16 | def __init__(self, ui): |
|
16 | def __init__(self, ui): | |
17 | urllib2.HTTPPasswordMgrWithDefaultRealm.__init__(self) |
|
17 | urllib2.HTTPPasswordMgrWithDefaultRealm.__init__(self) | |
18 | self.ui = ui |
|
18 | self.ui = ui | |
19 |
|
19 | |||
20 | def find_user_password(self, realm, authuri): |
|
20 | def find_user_password(self, realm, authuri): | |
21 | authinfo = urllib2.HTTPPasswordMgrWithDefaultRealm.find_user_password( |
|
21 | authinfo = urllib2.HTTPPasswordMgrWithDefaultRealm.find_user_password( | |
22 | self, realm, authuri) |
|
22 | self, realm, authuri) | |
23 | user, passwd = authinfo |
|
23 | user, passwd = authinfo | |
24 | if user and passwd: |
|
24 | if user and passwd: | |
25 | self._writedebug(user, passwd) |
|
25 | self._writedebug(user, passwd) | |
26 | return (user, passwd) |
|
26 | return (user, passwd) | |
27 |
|
27 | |||
28 | if not user or not passwd: |
|
28 | if not user or not passwd: | |
29 | res = httpconnectionmod.readauthforuri(self.ui, authuri, user) |
|
29 | res = httpconnectionmod.readauthforuri(self.ui, authuri, user) | |
30 | if res: |
|
30 | if res: | |
31 | group, auth = res |
|
31 | group, auth = res | |
32 | user, passwd = auth.get('username'), auth.get('password') |
|
32 | user, passwd = auth.get('username'), auth.get('password') | |
33 | self.ui.debug("using auth.%s.* for authentication\n" % group) |
|
33 | self.ui.debug("using auth.%s.* for authentication\n" % group) | |
34 | if not user or not passwd: |
|
34 | if not user or not passwd: | |
35 | if not self.ui.interactive(): |
|
35 | if not self.ui.interactive(): | |
36 | raise util.Abort(_('http authorization required')) |
|
36 | raise util.Abort(_('http authorization required')) | |
37 |
|
37 | |||
38 | self.ui.write(_("http authorization required\n")) |
|
38 | self.ui.write(_("http authorization required\n")) | |
39 | self.ui.write(_("realm: %s\n") % realm) |
|
39 | self.ui.write(_("realm: %s\n") % realm) | |
40 | if user: |
|
40 | if user: | |
41 | self.ui.write(_("user: %s\n") % user) |
|
41 | self.ui.write(_("user: %s\n") % user) | |
42 | else: |
|
42 | else: | |
43 | user = self.ui.prompt(_("user:"), default=None) |
|
43 | user = self.ui.prompt(_("user:"), default=None) | |
44 |
|
44 | |||
45 | if not passwd: |
|
45 | if not passwd: | |
46 | passwd = self.ui.getpass() |
|
46 | passwd = self.ui.getpass() | |
47 |
|
47 | |||
48 | self.add_password(realm, authuri, user, passwd) |
|
48 | self.add_password(realm, authuri, user, passwd) | |
49 | self._writedebug(user, passwd) |
|
49 | self._writedebug(user, passwd) | |
50 | return (user, passwd) |
|
50 | return (user, passwd) | |
51 |
|
51 | |||
52 | def _writedebug(self, user, passwd): |
|
52 | def _writedebug(self, user, passwd): | |
53 | msg = _('http auth: user %s, password %s\n') |
|
53 | msg = _('http auth: user %s, password %s\n') | |
54 | self.ui.debug(msg % (user, passwd and '*' * len(passwd) or 'not set')) |
|
54 | self.ui.debug(msg % (user, passwd and '*' * len(passwd) or 'not set')) | |
55 |
|
55 | |||
56 | def find_stored_password(self, authuri): |
|
56 | def find_stored_password(self, authuri): | |
57 | return urllib2.HTTPPasswordMgrWithDefaultRealm.find_user_password( |
|
57 | return urllib2.HTTPPasswordMgrWithDefaultRealm.find_user_password( | |
58 | self, None, authuri) |
|
58 | self, None, authuri) | |
59 |
|
59 | |||
60 | class proxyhandler(urllib2.ProxyHandler): |
|
60 | class proxyhandler(urllib2.ProxyHandler): | |
61 | def __init__(self, ui): |
|
61 | def __init__(self, ui): | |
62 | proxyurl = ui.config("http_proxy", "host") or os.getenv('http_proxy') |
|
62 | proxyurl = ui.config("http_proxy", "host") or os.getenv('http_proxy') | |
63 | # XXX proxyauthinfo = None |
|
63 | # XXX proxyauthinfo = None | |
64 |
|
64 | |||
65 | if proxyurl: |
|
65 | if proxyurl: | |
66 | # proxy can be proper url or host[:port] |
|
66 | # proxy can be proper url or host[:port] | |
67 | if not (proxyurl.startswith('http:') or |
|
67 | if not (proxyurl.startswith('http:') or | |
68 | proxyurl.startswith('https:')): |
|
68 | proxyurl.startswith('https:')): | |
69 | proxyurl = 'http://' + proxyurl + '/' |
|
69 | proxyurl = 'http://' + proxyurl + '/' | |
70 | proxy = util.url(proxyurl) |
|
70 | proxy = util.url(proxyurl) | |
71 | if not proxy.user: |
|
71 | if not proxy.user: | |
72 | proxy.user = ui.config("http_proxy", "user") |
|
72 | proxy.user = ui.config("http_proxy", "user") | |
73 | proxy.passwd = ui.config("http_proxy", "passwd") |
|
73 | proxy.passwd = ui.config("http_proxy", "passwd") | |
74 |
|
74 | |||
75 | # see if we should use a proxy for this url |
|
75 | # see if we should use a proxy for this url | |
76 | no_list = ["localhost", "127.0.0.1"] |
|
76 | no_list = ["localhost", "127.0.0.1"] | |
77 | no_list.extend([p.lower() for |
|
77 | no_list.extend([p.lower() for | |
78 | p in ui.configlist("http_proxy", "no")]) |
|
78 | p in ui.configlist("http_proxy", "no")]) | |
79 | no_list.extend([p.strip().lower() for |
|
79 | no_list.extend([p.strip().lower() for | |
80 | p in os.getenv("no_proxy", '').split(',') |
|
80 | p in os.getenv("no_proxy", '').split(',') | |
81 | if p.strip()]) |
|
81 | if p.strip()]) | |
82 | # "http_proxy.always" config is for running tests on localhost |
|
82 | # "http_proxy.always" config is for running tests on localhost | |
83 | if ui.configbool("http_proxy", "always"): |
|
83 | if ui.configbool("http_proxy", "always"): | |
84 | self.no_list = [] |
|
84 | self.no_list = [] | |
85 | else: |
|
85 | else: | |
86 | self.no_list = no_list |
|
86 | self.no_list = no_list | |
87 |
|
87 | |||
88 | proxyurl = str(proxy) |
|
88 | proxyurl = str(proxy) | |
89 | proxies = {'http': proxyurl, 'https': proxyurl} |
|
89 | proxies = {'http': proxyurl, 'https': proxyurl} | |
90 | ui.debug('proxying through http://%s:%s\n' % |
|
90 | ui.debug('proxying through http://%s:%s\n' % | |
91 | (proxy.host, proxy.port)) |
|
91 | (proxy.host, proxy.port)) | |
92 | else: |
|
92 | else: | |
93 | proxies = {} |
|
93 | proxies = {} | |
94 |
|
94 | |||
95 | # urllib2 takes proxy values from the environment and those |
|
95 | # urllib2 takes proxy values from the environment and those | |
96 | # will take precedence if found. So, if there's a config entry |
|
96 | # will take precedence if found. So, if there's a config entry | |
97 | # defining a proxy, drop the environment ones |
|
97 | # defining a proxy, drop the environment ones | |
98 | if ui.config("http_proxy", "host"): |
|
98 | if ui.config("http_proxy", "host"): | |
99 | for env in ["HTTP_PROXY", "http_proxy", "no_proxy"]: |
|
99 | for env in ["HTTP_PROXY", "http_proxy", "no_proxy"]: | |
100 | try: |
|
100 | try: | |
101 | if env in os.environ: |
|
101 | if env in os.environ: | |
102 | del os.environ[env] |
|
102 | del os.environ[env] | |
103 | except OSError: |
|
103 | except OSError: | |
104 | pass |
|
104 | pass | |
105 |
|
105 | |||
106 | urllib2.ProxyHandler.__init__(self, proxies) |
|
106 | urllib2.ProxyHandler.__init__(self, proxies) | |
107 | self.ui = ui |
|
107 | self.ui = ui | |
108 |
|
108 | |||
109 | def proxy_open(self, req, proxy, type_): |
|
109 | def proxy_open(self, req, proxy, type_): | |
110 | host = req.get_host().split(':')[0] |
|
110 | host = req.get_host().split(':')[0] | |
111 | if host in self.no_list: |
|
111 | if host in self.no_list: | |
112 | return None |
|
112 | return None | |
113 |
|
113 | |||
114 | # work around a bug in Python < 2.4.2 |
|
114 | # work around a bug in Python < 2.4.2 | |
115 | # (it leaves a "\n" at the end of Proxy-authorization headers) |
|
115 | # (it leaves a "\n" at the end of Proxy-authorization headers) | |
116 | baseclass = req.__class__ |
|
116 | baseclass = req.__class__ | |
117 | class _request(baseclass): |
|
117 | class _request(baseclass): | |
118 | def add_header(self, key, val): |
|
118 | def add_header(self, key, val): | |
119 | if key.lower() == 'proxy-authorization': |
|
119 | if key.lower() == 'proxy-authorization': | |
120 | val = val.strip() |
|
120 | val = val.strip() | |
121 | return baseclass.add_header(self, key, val) |
|
121 | return baseclass.add_header(self, key, val) | |
122 | req.__class__ = _request |
|
122 | req.__class__ = _request | |
123 |
|
123 | |||
124 | return urllib2.ProxyHandler.proxy_open(self, req, proxy, type_) |
|
124 | return urllib2.ProxyHandler.proxy_open(self, req, proxy, type_) | |
125 |
|
125 | |||
126 | def _gen_sendfile(orgsend): |
|
126 | def _gen_sendfile(orgsend): | |
127 | def _sendfile(self, data): |
|
127 | def _sendfile(self, data): | |
128 | # send a file |
|
128 | # send a file | |
129 | if isinstance(data, httpconnectionmod.httpsendfile): |
|
129 | if isinstance(data, httpconnectionmod.httpsendfile): | |
130 | # if auth required, some data sent twice, so rewind here |
|
130 | # if auth required, some data sent twice, so rewind here | |
131 | data.seek(0) |
|
131 | data.seek(0) | |
132 | for chunk in util.filechunkiter(data): |
|
132 | for chunk in util.filechunkiter(data): | |
133 | orgsend(self, chunk) |
|
133 | orgsend(self, chunk) | |
134 | else: |
|
134 | else: | |
135 | orgsend(self, data) |
|
135 | orgsend(self, data) | |
136 | return _sendfile |
|
136 | return _sendfile | |
137 |
|
137 | |||
138 | has_https = util.safehasattr(urllib2, 'HTTPSHandler') |
|
138 | has_https = util.safehasattr(urllib2, 'HTTPSHandler') | |
139 | if has_https: |
|
139 | if has_https: | |
140 | try: |
|
140 | try: | |
141 | _create_connection = socket.create_connection |
|
141 | _create_connection = socket.create_connection | |
142 | except AttributeError: |
|
142 | except AttributeError: | |
143 | _GLOBAL_DEFAULT_TIMEOUT = object() |
|
143 | _GLOBAL_DEFAULT_TIMEOUT = object() | |
144 |
|
144 | |||
145 | def _create_connection(address, timeout=_GLOBAL_DEFAULT_TIMEOUT, |
|
145 | def _create_connection(address, timeout=_GLOBAL_DEFAULT_TIMEOUT, | |
146 | source_address=None): |
|
146 | source_address=None): | |
147 | # lifted from Python 2.6 |
|
147 | # lifted from Python 2.6 | |
148 |
|
148 | |||
149 | msg = "getaddrinfo returns an empty list" |
|
149 | msg = "getaddrinfo returns an empty list" | |
150 | host, port = address |
|
150 | host, port = address | |
151 | for res in socket.getaddrinfo(host, port, 0, socket.SOCK_STREAM): |
|
151 | for res in socket.getaddrinfo(host, port, 0, socket.SOCK_STREAM): | |
152 | af, socktype, proto, canonname, sa = res |
|
152 | af, socktype, proto, canonname, sa = res | |
153 | sock = None |
|
153 | sock = None | |
154 | try: |
|
154 | try: | |
155 | sock = socket.socket(af, socktype, proto) |
|
155 | sock = socket.socket(af, socktype, proto) | |
156 | if timeout is not _GLOBAL_DEFAULT_TIMEOUT: |
|
156 | if timeout is not _GLOBAL_DEFAULT_TIMEOUT: | |
157 | sock.settimeout(timeout) |
|
157 | sock.settimeout(timeout) | |
158 | if source_address: |
|
158 | if source_address: | |
159 | sock.bind(source_address) |
|
159 | sock.bind(source_address) | |
160 | sock.connect(sa) |
|
160 | sock.connect(sa) | |
161 | return sock |
|
161 | return sock | |
162 |
|
162 | |||
163 | except socket.error, msg: |
|
163 | except socket.error, msg: | |
164 | if sock is not None: |
|
164 | if sock is not None: | |
165 | sock.close() |
|
165 | sock.close() | |
166 |
|
166 | |||
167 | raise socket.error, msg |
|
167 | raise socket.error, msg | |
168 |
|
168 | |||
169 | class httpconnection(keepalive.HTTPConnection): |
|
169 | class httpconnection(keepalive.HTTPConnection): | |
170 | # must be able to send big bundle as stream. |
|
170 | # must be able to send big bundle as stream. | |
171 | send = _gen_sendfile(keepalive.HTTPConnection.send) |
|
171 | send = _gen_sendfile(keepalive.HTTPConnection.send) | |
172 |
|
172 | |||
173 | def connect(self): |
|
173 | def connect(self): | |
174 | if has_https and self.realhostport: # use CONNECT proxy |
|
174 | if has_https and self.realhostport: # use CONNECT proxy | |
175 | self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) |
|
175 | self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) | |
176 | self.sock.connect((self.host, self.port)) |
|
176 | self.sock.connect((self.host, self.port)) | |
177 | if _generic_proxytunnel(self): |
|
177 | if _generic_proxytunnel(self): | |
178 | # we do not support client X.509 certificates |
|
178 | # we do not support client X.509 certificates | |
179 | self.sock = sslutil.ssl_wrap_socket(self.sock, None, None) |
|
179 | self.sock = sslutil.ssl_wrap_socket(self.sock, None, None) | |
180 | else: |
|
180 | else: | |
181 | keepalive.HTTPConnection.connect(self) |
|
181 | keepalive.HTTPConnection.connect(self) | |
182 |
|
182 | |||
183 | def getresponse(self): |
|
183 | def getresponse(self): | |
184 | proxyres = getattr(self, 'proxyres', None) |
|
184 | proxyres = getattr(self, 'proxyres', None) | |
185 | if proxyres: |
|
185 | if proxyres: | |
186 | if proxyres.will_close: |
|
186 | if proxyres.will_close: | |
187 | self.close() |
|
187 | self.close() | |
188 | self.proxyres = None |
|
188 | self.proxyres = None | |
189 | return proxyres |
|
189 | return proxyres | |
190 | return keepalive.HTTPConnection.getresponse(self) |
|
190 | return keepalive.HTTPConnection.getresponse(self) | |
191 |
|
191 | |||
192 | # general transaction handler to support different ways to handle |
|
192 | # general transaction handler to support different ways to handle | |
193 | # HTTPS proxying before and after Python 2.6.3. |
|
193 | # HTTPS proxying before and after Python 2.6.3. | |
194 | def _generic_start_transaction(handler, h, req): |
|
194 | def _generic_start_transaction(handler, h, req): | |
195 | tunnel_host = getattr(req, '_tunnel_host', None) |
|
195 | tunnel_host = getattr(req, '_tunnel_host', None) | |
196 | if tunnel_host: |
|
196 | if tunnel_host: | |
197 | if tunnel_host[:7] not in ['http://', 'https:/']: |
|
197 | if tunnel_host[:7] not in ['http://', 'https:/']: | |
198 | tunnel_host = 'https://' + tunnel_host |
|
198 | tunnel_host = 'https://' + tunnel_host | |
199 | new_tunnel = True |
|
199 | new_tunnel = True | |
200 | else: |
|
200 | else: | |
201 | tunnel_host = req.get_selector() |
|
201 | tunnel_host = req.get_selector() | |
202 | new_tunnel = False |
|
202 | new_tunnel = False | |
203 |
|
203 | |||
204 | if new_tunnel or tunnel_host == req.get_full_url(): # has proxy |
|
204 | if new_tunnel or tunnel_host == req.get_full_url(): # has proxy | |
205 | u = util.url(tunnel_host) |
|
205 | u = util.url(tunnel_host) | |
206 | if new_tunnel or u.scheme == 'https': # only use CONNECT for HTTPS |
|
206 | if new_tunnel or u.scheme == 'https': # only use CONNECT for HTTPS | |
207 | h.realhostport = ':'.join([u.host, (u.port or '443')]) |
|
207 | h.realhostport = ':'.join([u.host, (u.port or '443')]) | |
208 | h.headers = req.headers.copy() |
|
208 | h.headers = req.headers.copy() | |
209 | h.headers.update(handler.parent.addheaders) |
|
209 | h.headers.update(handler.parent.addheaders) | |
210 | return |
|
210 | return | |
211 |
|
211 | |||
212 | h.realhostport = None |
|
212 | h.realhostport = None | |
213 | h.headers = None |
|
213 | h.headers = None | |
214 |
|
214 | |||
215 | def _generic_proxytunnel(self): |
|
215 | def _generic_proxytunnel(self): | |
216 | proxyheaders = dict( |
|
216 | proxyheaders = dict( | |
217 | [(x, self.headers[x]) for x in self.headers |
|
217 | [(x, self.headers[x]) for x in self.headers | |
218 | if x.lower().startswith('proxy-')]) |
|
218 | if x.lower().startswith('proxy-')]) | |
219 | self._set_hostport(self.host, self.port) |
|
219 | self._set_hostport(self.host, self.port) | |
220 | self.send('CONNECT %s HTTP/1.0\r\n' % self.realhostport) |
|
220 | self.send('CONNECT %s HTTP/1.0\r\n' % self.realhostport) | |
221 | for header in proxyheaders.iteritems(): |
|
221 | for header in proxyheaders.iteritems(): | |
222 | self.send('%s: %s\r\n' % header) |
|
222 | self.send('%s: %s\r\n' % header) | |
223 | self.send('\r\n') |
|
223 | self.send('\r\n') | |
224 |
|
224 | |||
225 | # majority of the following code is duplicated from |
|
225 | # majority of the following code is duplicated from | |
226 | # httplib.HTTPConnection as there are no adequate places to |
|
226 | # httplib.HTTPConnection as there are no adequate places to | |
227 | # override functions to provide the needed functionality |
|
227 | # override functions to provide the needed functionality | |
228 | res = self.response_class(self.sock, |
|
228 | res = self.response_class(self.sock, | |
229 | strict=self.strict, |
|
229 | strict=self.strict, | |
230 | method=self._method) |
|
230 | method=self._method) | |
231 |
|
231 | |||
232 | while True: |
|
232 | while True: | |
233 | version, status, reason = res._read_status() |
|
233 | version, status, reason = res._read_status() | |
234 | if status != httplib.CONTINUE: |
|
234 | if status != httplib.CONTINUE: | |
235 | break |
|
235 | break | |
236 | while True: |
|
236 | while True: | |
237 | skip = res.fp.readline().strip() |
|
237 | skip = res.fp.readline().strip() | |
238 | if not skip: |
|
238 | if not skip: | |
239 | break |
|
239 | break | |
240 | res.status = status |
|
240 | res.status = status | |
241 | res.reason = reason.strip() |
|
241 | res.reason = reason.strip() | |
242 |
|
242 | |||
243 | if res.status == 200: |
|
243 | if res.status == 200: | |
244 | while True: |
|
244 | while True: | |
245 | line = res.fp.readline() |
|
245 | line = res.fp.readline() | |
246 | if line == '\r\n': |
|
246 | if line == '\r\n': | |
247 | break |
|
247 | break | |
248 | return True |
|
248 | return True | |
249 |
|
249 | |||
250 | if version == 'HTTP/1.0': |
|
250 | if version == 'HTTP/1.0': | |
251 | res.version = 10 |
|
251 | res.version = 10 | |
252 | elif version.startswith('HTTP/1.'): |
|
252 | elif version.startswith('HTTP/1.'): | |
253 | res.version = 11 |
|
253 | res.version = 11 | |
254 | elif version == 'HTTP/0.9': |
|
254 | elif version == 'HTTP/0.9': | |
255 | res.version = 9 |
|
255 | res.version = 9 | |
256 | else: |
|
256 | else: | |
257 | raise httplib.UnknownProtocol(version) |
|
257 | raise httplib.UnknownProtocol(version) | |
258 |
|
258 | |||
259 | if res.version == 9: |
|
259 | if res.version == 9: | |
260 | res.length = None |
|
260 | res.length = None | |
261 | res.chunked = 0 |
|
261 | res.chunked = 0 | |
262 | res.will_close = 1 |
|
262 | res.will_close = 1 | |
263 | res.msg = httplib.HTTPMessage(cStringIO.StringIO()) |
|
263 | res.msg = httplib.HTTPMessage(cStringIO.StringIO()) | |
264 | return False |
|
264 | return False | |
265 |
|
265 | |||
266 | res.msg = httplib.HTTPMessage(res.fp) |
|
266 | res.msg = httplib.HTTPMessage(res.fp) | |
267 | res.msg.fp = None |
|
267 | res.msg.fp = None | |
268 |
|
268 | |||
269 | # are we using the chunked-style of transfer encoding? |
|
269 | # are we using the chunked-style of transfer encoding? | |
270 | trenc = res.msg.getheader('transfer-encoding') |
|
270 | trenc = res.msg.getheader('transfer-encoding') | |
271 | if trenc and trenc.lower() == "chunked": |
|
271 | if trenc and trenc.lower() == "chunked": | |
272 | res.chunked = 1 |
|
272 | res.chunked = 1 | |
273 | res.chunk_left = None |
|
273 | res.chunk_left = None | |
274 | else: |
|
274 | else: | |
275 | res.chunked = 0 |
|
275 | res.chunked = 0 | |
276 |
|
276 | |||
277 | # will the connection close at the end of the response? |
|
277 | # will the connection close at the end of the response? | |
278 | res.will_close = res._check_close() |
|
278 | res.will_close = res._check_close() | |
279 |
|
279 | |||
280 | # do we have a Content-Length? |
|
280 | # do we have a Content-Length? | |
281 |
# NOTE: RFC 2616, |
|
281 | # NOTE: RFC 2616, section 4.4, #3 says we ignore this if | |
|
282 | # transfer-encoding is "chunked" | |||
282 | length = res.msg.getheader('content-length') |
|
283 | length = res.msg.getheader('content-length') | |
283 | if length and not res.chunked: |
|
284 | if length and not res.chunked: | |
284 | try: |
|
285 | try: | |
285 | res.length = int(length) |
|
286 | res.length = int(length) | |
286 | except ValueError: |
|
287 | except ValueError: | |
287 | res.length = None |
|
288 | res.length = None | |
288 | else: |
|
289 | else: | |
289 | if res.length < 0: # ignore nonsensical negative lengths |
|
290 | if res.length < 0: # ignore nonsensical negative lengths | |
290 | res.length = None |
|
291 | res.length = None | |
291 | else: |
|
292 | else: | |
292 | res.length = None |
|
293 | res.length = None | |
293 |
|
294 | |||
294 | # does the body have a fixed length? (of zero) |
|
295 | # does the body have a fixed length? (of zero) | |
295 | if (status == httplib.NO_CONTENT or status == httplib.NOT_MODIFIED or |
|
296 | if (status == httplib.NO_CONTENT or status == httplib.NOT_MODIFIED or | |
296 | 100 <= status < 200 or # 1xx codes |
|
297 | 100 <= status < 200 or # 1xx codes | |
297 | res._method == 'HEAD'): |
|
298 | res._method == 'HEAD'): | |
298 | res.length = 0 |
|
299 | res.length = 0 | |
299 |
|
300 | |||
300 | # if the connection remains open, and we aren't using chunked, and |
|
301 | # if the connection remains open, and we aren't using chunked, and | |
301 | # a content-length was not provided, then assume that the connection |
|
302 | # a content-length was not provided, then assume that the connection | |
302 | # WILL close. |
|
303 | # WILL close. | |
303 | if (not res.will_close and |
|
304 | if (not res.will_close and | |
304 | not res.chunked and |
|
305 | not res.chunked and | |
305 | res.length is None): |
|
306 | res.length is None): | |
306 | res.will_close = 1 |
|
307 | res.will_close = 1 | |
307 |
|
308 | |||
308 | self.proxyres = res |
|
309 | self.proxyres = res | |
309 |
|
310 | |||
310 | return False |
|
311 | return False | |
311 |
|
312 | |||
312 | class httphandler(keepalive.HTTPHandler): |
|
313 | class httphandler(keepalive.HTTPHandler): | |
313 | def http_open(self, req): |
|
314 | def http_open(self, req): | |
314 | return self.do_open(httpconnection, req) |
|
315 | return self.do_open(httpconnection, req) | |
315 |
|
316 | |||
316 | def _start_transaction(self, h, req): |
|
317 | def _start_transaction(self, h, req): | |
317 | _generic_start_transaction(self, h, req) |
|
318 | _generic_start_transaction(self, h, req) | |
318 | return keepalive.HTTPHandler._start_transaction(self, h, req) |
|
319 | return keepalive.HTTPHandler._start_transaction(self, h, req) | |
319 |
|
320 | |||
320 | if has_https: |
|
321 | if has_https: | |
321 | class httpsconnection(httplib.HTTPSConnection): |
|
322 | class httpsconnection(httplib.HTTPSConnection): | |
322 | response_class = keepalive.HTTPResponse |
|
323 | response_class = keepalive.HTTPResponse | |
323 | # must be able to send big bundle as stream. |
|
324 | # must be able to send big bundle as stream. | |
324 | send = _gen_sendfile(keepalive.safesend) |
|
325 | send = _gen_sendfile(keepalive.safesend) | |
325 | getresponse = keepalive.wrapgetresponse(httplib.HTTPSConnection) |
|
326 | getresponse = keepalive.wrapgetresponse(httplib.HTTPSConnection) | |
326 |
|
327 | |||
327 | def connect(self): |
|
328 | def connect(self): | |
328 | self.sock = _create_connection((self.host, self.port)) |
|
329 | self.sock = _create_connection((self.host, self.port)) | |
329 |
|
330 | |||
330 | host = self.host |
|
331 | host = self.host | |
331 | if self.realhostport: # use CONNECT proxy |
|
332 | if self.realhostport: # use CONNECT proxy | |
332 | _generic_proxytunnel(self) |
|
333 | _generic_proxytunnel(self) | |
333 | host = self.realhostport.rsplit(':', 1)[0] |
|
334 | host = self.realhostport.rsplit(':', 1)[0] | |
334 | self.sock = sslutil.ssl_wrap_socket( |
|
335 | self.sock = sslutil.ssl_wrap_socket( | |
335 | self.sock, self.key_file, self.cert_file, |
|
336 | self.sock, self.key_file, self.cert_file, | |
336 | **sslutil.sslkwargs(self.ui, host)) |
|
337 | **sslutil.sslkwargs(self.ui, host)) | |
337 | sslutil.validator(self.ui, host)(self.sock) |
|
338 | sslutil.validator(self.ui, host)(self.sock) | |
338 |
|
339 | |||
339 | class httpshandler(keepalive.KeepAliveHandler, urllib2.HTTPSHandler): |
|
340 | class httpshandler(keepalive.KeepAliveHandler, urllib2.HTTPSHandler): | |
340 | def __init__(self, ui): |
|
341 | def __init__(self, ui): | |
341 | keepalive.KeepAliveHandler.__init__(self) |
|
342 | keepalive.KeepAliveHandler.__init__(self) | |
342 | urllib2.HTTPSHandler.__init__(self) |
|
343 | urllib2.HTTPSHandler.__init__(self) | |
343 | self.ui = ui |
|
344 | self.ui = ui | |
344 | self.pwmgr = passwordmgr(self.ui) |
|
345 | self.pwmgr = passwordmgr(self.ui) | |
345 |
|
346 | |||
346 | def _start_transaction(self, h, req): |
|
347 | def _start_transaction(self, h, req): | |
347 | _generic_start_transaction(self, h, req) |
|
348 | _generic_start_transaction(self, h, req) | |
348 | return keepalive.KeepAliveHandler._start_transaction(self, h, req) |
|
349 | return keepalive.KeepAliveHandler._start_transaction(self, h, req) | |
349 |
|
350 | |||
350 | def https_open(self, req): |
|
351 | def https_open(self, req): | |
351 | # req.get_full_url() does not contain credentials and we may |
|
352 | # req.get_full_url() does not contain credentials and we may | |
352 | # need them to match the certificates. |
|
353 | # need them to match the certificates. | |
353 | url = req.get_full_url() |
|
354 | url = req.get_full_url() | |
354 | user, password = self.pwmgr.find_stored_password(url) |
|
355 | user, password = self.pwmgr.find_stored_password(url) | |
355 | res = httpconnectionmod.readauthforuri(self.ui, url, user) |
|
356 | res = httpconnectionmod.readauthforuri(self.ui, url, user) | |
356 | if res: |
|
357 | if res: | |
357 | group, auth = res |
|
358 | group, auth = res | |
358 | self.auth = auth |
|
359 | self.auth = auth | |
359 | self.ui.debug("using auth.%s.* for authentication\n" % group) |
|
360 | self.ui.debug("using auth.%s.* for authentication\n" % group) | |
360 | else: |
|
361 | else: | |
361 | self.auth = None |
|
362 | self.auth = None | |
362 | return self.do_open(self._makeconnection, req) |
|
363 | return self.do_open(self._makeconnection, req) | |
363 |
|
364 | |||
364 | def _makeconnection(self, host, port=None, *args, **kwargs): |
|
365 | def _makeconnection(self, host, port=None, *args, **kwargs): | |
365 | keyfile = None |
|
366 | keyfile = None | |
366 | certfile = None |
|
367 | certfile = None | |
367 |
|
368 | |||
368 | if len(args) >= 1: # key_file |
|
369 | if len(args) >= 1: # key_file | |
369 | keyfile = args[0] |
|
370 | keyfile = args[0] | |
370 | if len(args) >= 2: # cert_file |
|
371 | if len(args) >= 2: # cert_file | |
371 | certfile = args[1] |
|
372 | certfile = args[1] | |
372 | args = args[2:] |
|
373 | args = args[2:] | |
373 |
|
374 | |||
374 | # if the user has specified different key/cert files in |
|
375 | # if the user has specified different key/cert files in | |
375 | # hgrc, we prefer these |
|
376 | # hgrc, we prefer these | |
376 | if self.auth and 'key' in self.auth and 'cert' in self.auth: |
|
377 | if self.auth and 'key' in self.auth and 'cert' in self.auth: | |
377 | keyfile = self.auth['key'] |
|
378 | keyfile = self.auth['key'] | |
378 | certfile = self.auth['cert'] |
|
379 | certfile = self.auth['cert'] | |
379 |
|
380 | |||
380 | conn = httpsconnection(host, port, keyfile, certfile, *args, |
|
381 | conn = httpsconnection(host, port, keyfile, certfile, *args, | |
381 | **kwargs) |
|
382 | **kwargs) | |
382 | conn.ui = self.ui |
|
383 | conn.ui = self.ui | |
383 | return conn |
|
384 | return conn | |
384 |
|
385 | |||
385 | class httpdigestauthhandler(urllib2.HTTPDigestAuthHandler): |
|
386 | class httpdigestauthhandler(urllib2.HTTPDigestAuthHandler): | |
386 | def __init__(self, *args, **kwargs): |
|
387 | def __init__(self, *args, **kwargs): | |
387 | urllib2.HTTPDigestAuthHandler.__init__(self, *args, **kwargs) |
|
388 | urllib2.HTTPDigestAuthHandler.__init__(self, *args, **kwargs) | |
388 | self.retried_req = None |
|
389 | self.retried_req = None | |
389 |
|
390 | |||
390 | def reset_retry_count(self): |
|
391 | def reset_retry_count(self): | |
391 | # Python 2.6.5 will call this on 401 or 407 errors and thus loop |
|
392 | # Python 2.6.5 will call this on 401 or 407 errors and thus loop | |
392 | # forever. We disable reset_retry_count completely and reset in |
|
393 | # forever. We disable reset_retry_count completely and reset in | |
393 | # http_error_auth_reqed instead. |
|
394 | # http_error_auth_reqed instead. | |
394 | pass |
|
395 | pass | |
395 |
|
396 | |||
396 | def http_error_auth_reqed(self, auth_header, host, req, headers): |
|
397 | def http_error_auth_reqed(self, auth_header, host, req, headers): | |
397 | # Reset the retry counter once for each request. |
|
398 | # Reset the retry counter once for each request. | |
398 | if req is not self.retried_req: |
|
399 | if req is not self.retried_req: | |
399 | self.retried_req = req |
|
400 | self.retried_req = req | |
400 | self.retried = 0 |
|
401 | self.retried = 0 | |
401 | # In python < 2.5 AbstractDigestAuthHandler raises a ValueError if |
|
402 | # In python < 2.5 AbstractDigestAuthHandler raises a ValueError if | |
402 | # it doesn't know about the auth type requested. This can happen if |
|
403 | # it doesn't know about the auth type requested. This can happen if | |
403 | # somebody is using BasicAuth and types a bad password. |
|
404 | # somebody is using BasicAuth and types a bad password. | |
404 | try: |
|
405 | try: | |
405 | return urllib2.HTTPDigestAuthHandler.http_error_auth_reqed( |
|
406 | return urllib2.HTTPDigestAuthHandler.http_error_auth_reqed( | |
406 | self, auth_header, host, req, headers) |
|
407 | self, auth_header, host, req, headers) | |
407 | except ValueError, inst: |
|
408 | except ValueError, inst: | |
408 | arg = inst.args[0] |
|
409 | arg = inst.args[0] | |
409 | if arg.startswith("AbstractDigestAuthHandler doesn't know "): |
|
410 | if arg.startswith("AbstractDigestAuthHandler doesn't know "): | |
410 | return |
|
411 | return | |
411 | raise |
|
412 | raise | |
412 |
|
413 | |||
413 | class httpbasicauthhandler(urllib2.HTTPBasicAuthHandler): |
|
414 | class httpbasicauthhandler(urllib2.HTTPBasicAuthHandler): | |
414 | def __init__(self, *args, **kwargs): |
|
415 | def __init__(self, *args, **kwargs): | |
415 | urllib2.HTTPBasicAuthHandler.__init__(self, *args, **kwargs) |
|
416 | urllib2.HTTPBasicAuthHandler.__init__(self, *args, **kwargs) | |
416 | self.retried_req = None |
|
417 | self.retried_req = None | |
417 |
|
418 | |||
418 | def reset_retry_count(self): |
|
419 | def reset_retry_count(self): | |
419 | # Python 2.6.5 will call this on 401 or 407 errors and thus loop |
|
420 | # Python 2.6.5 will call this on 401 or 407 errors and thus loop | |
420 | # forever. We disable reset_retry_count completely and reset in |
|
421 | # forever. We disable reset_retry_count completely and reset in | |
421 | # http_error_auth_reqed instead. |
|
422 | # http_error_auth_reqed instead. | |
422 | pass |
|
423 | pass | |
423 |
|
424 | |||
424 | def http_error_auth_reqed(self, auth_header, host, req, headers): |
|
425 | def http_error_auth_reqed(self, auth_header, host, req, headers): | |
425 | # Reset the retry counter once for each request. |
|
426 | # Reset the retry counter once for each request. | |
426 | if req is not self.retried_req: |
|
427 | if req is not self.retried_req: | |
427 | self.retried_req = req |
|
428 | self.retried_req = req | |
428 | self.retried = 0 |
|
429 | self.retried = 0 | |
429 | return urllib2.HTTPBasicAuthHandler.http_error_auth_reqed( |
|
430 | return urllib2.HTTPBasicAuthHandler.http_error_auth_reqed( | |
430 | self, auth_header, host, req, headers) |
|
431 | self, auth_header, host, req, headers) | |
431 |
|
432 | |||
432 | handlerfuncs = [] |
|
433 | handlerfuncs = [] | |
433 |
|
434 | |||
434 | def opener(ui, authinfo=None): |
|
435 | def opener(ui, authinfo=None): | |
435 | ''' |
|
436 | ''' | |
436 | construct an opener suitable for urllib2 |
|
437 | construct an opener suitable for urllib2 | |
437 | authinfo will be added to the password manager |
|
438 | authinfo will be added to the password manager | |
438 | ''' |
|
439 | ''' | |
439 | if ui.configbool('ui', 'usehttp2', False): |
|
440 | if ui.configbool('ui', 'usehttp2', False): | |
440 | handlers = [httpconnectionmod.http2handler(ui, passwordmgr(ui))] |
|
441 | handlers = [httpconnectionmod.http2handler(ui, passwordmgr(ui))] | |
441 | else: |
|
442 | else: | |
442 | handlers = [httphandler()] |
|
443 | handlers = [httphandler()] | |
443 | if has_https: |
|
444 | if has_https: | |
444 | handlers.append(httpshandler(ui)) |
|
445 | handlers.append(httpshandler(ui)) | |
445 |
|
446 | |||
446 | handlers.append(proxyhandler(ui)) |
|
447 | handlers.append(proxyhandler(ui)) | |
447 |
|
448 | |||
448 | passmgr = passwordmgr(ui) |
|
449 | passmgr = passwordmgr(ui) | |
449 | if authinfo is not None: |
|
450 | if authinfo is not None: | |
450 | passmgr.add_password(*authinfo) |
|
451 | passmgr.add_password(*authinfo) | |
451 | user, passwd = authinfo[2:4] |
|
452 | user, passwd = authinfo[2:4] | |
452 | ui.debug('http auth: user %s, password %s\n' % |
|
453 | ui.debug('http auth: user %s, password %s\n' % | |
453 | (user, passwd and '*' * len(passwd) or 'not set')) |
|
454 | (user, passwd and '*' * len(passwd) or 'not set')) | |
454 |
|
455 | |||
455 | handlers.extend((httpbasicauthhandler(passmgr), |
|
456 | handlers.extend((httpbasicauthhandler(passmgr), | |
456 | httpdigestauthhandler(passmgr))) |
|
457 | httpdigestauthhandler(passmgr))) | |
457 | handlers.extend([h(ui, passmgr) for h in handlerfuncs]) |
|
458 | handlers.extend([h(ui, passmgr) for h in handlerfuncs]) | |
458 | opener = urllib2.build_opener(*handlers) |
|
459 | opener = urllib2.build_opener(*handlers) | |
459 |
|
460 | |||
460 | # 1.0 here is the _protocol_ version |
|
461 | # 1.0 here is the _protocol_ version | |
461 | opener.addheaders = [('User-agent', 'mercurial/proto-1.0')] |
|
462 | opener.addheaders = [('User-agent', 'mercurial/proto-1.0')] | |
462 | opener.addheaders.append(('Accept', 'application/mercurial-0.1')) |
|
463 | opener.addheaders.append(('Accept', 'application/mercurial-0.1')) | |
463 | return opener |
|
464 | return opener | |
464 |
|
465 | |||
465 | def open(ui, url_, data=None): |
|
466 | def open(ui, url_, data=None): | |
466 | u = util.url(url_) |
|
467 | u = util.url(url_) | |
467 | if u.scheme: |
|
468 | if u.scheme: | |
468 | u.scheme = u.scheme.lower() |
|
469 | u.scheme = u.scheme.lower() | |
469 | url_, authinfo = u.authinfo() |
|
470 | url_, authinfo = u.authinfo() | |
470 | else: |
|
471 | else: | |
471 | path = util.normpath(os.path.abspath(url_)) |
|
472 | path = util.normpath(os.path.abspath(url_)) | |
472 | url_ = 'file://' + urllib.pathname2url(path) |
|
473 | url_ = 'file://' + urllib.pathname2url(path) | |
473 | authinfo = None |
|
474 | authinfo = None | |
474 | return opener(ui, authinfo).open(url_, data) |
|
475 | return opener(ui, authinfo).open(url_, data) |
@@ -1,393 +1,393 | |||||
1 | # win32.py - utility functions that use win32 API |
|
1 | # win32.py - utility functions that use win32 API | |
2 | # |
|
2 | # | |
3 | # Copyright 2005-2009 Matt Mackall <mpm@selenic.com> and others |
|
3 | # Copyright 2005-2009 Matt Mackall <mpm@selenic.com> and others | |
4 | # |
|
4 | # | |
5 | # This software may be used and distributed according to the terms of the |
|
5 | # This software may be used and distributed according to the terms of the | |
6 | # GNU General Public License version 2 or any later version. |
|
6 | # GNU General Public License version 2 or any later version. | |
7 |
|
7 | |||
8 | import ctypes, errno, os, subprocess, random |
|
8 | import ctypes, errno, os, subprocess, random | |
9 |
|
9 | |||
10 | _kernel32 = ctypes.windll.kernel32 |
|
10 | _kernel32 = ctypes.windll.kernel32 | |
11 | _advapi32 = ctypes.windll.advapi32 |
|
11 | _advapi32 = ctypes.windll.advapi32 | |
12 | _user32 = ctypes.windll.user32 |
|
12 | _user32 = ctypes.windll.user32 | |
13 |
|
13 | |||
14 | _BOOL = ctypes.c_long |
|
14 | _BOOL = ctypes.c_long | |
15 | _WORD = ctypes.c_ushort |
|
15 | _WORD = ctypes.c_ushort | |
16 | _DWORD = ctypes.c_ulong |
|
16 | _DWORD = ctypes.c_ulong | |
17 | _UINT = ctypes.c_uint |
|
17 | _UINT = ctypes.c_uint | |
18 | _LONG = ctypes.c_long |
|
18 | _LONG = ctypes.c_long | |
19 | _LPCSTR = _LPSTR = ctypes.c_char_p |
|
19 | _LPCSTR = _LPSTR = ctypes.c_char_p | |
20 | _HANDLE = ctypes.c_void_p |
|
20 | _HANDLE = ctypes.c_void_p | |
21 | _HWND = _HANDLE |
|
21 | _HWND = _HANDLE | |
22 |
|
22 | |||
23 | _INVALID_HANDLE_VALUE = _HANDLE(-1).value |
|
23 | _INVALID_HANDLE_VALUE = _HANDLE(-1).value | |
24 |
|
24 | |||
25 | # GetLastError |
|
25 | # GetLastError | |
26 | _ERROR_SUCCESS = 0 |
|
26 | _ERROR_SUCCESS = 0 | |
27 | _ERROR_INVALID_PARAMETER = 87 |
|
27 | _ERROR_INVALID_PARAMETER = 87 | |
28 | _ERROR_INSUFFICIENT_BUFFER = 122 |
|
28 | _ERROR_INSUFFICIENT_BUFFER = 122 | |
29 |
|
29 | |||
30 | # WPARAM is defined as UINT_PTR (unsigned type) |
|
30 | # WPARAM is defined as UINT_PTR (unsigned type) | |
31 | # LPARAM is defined as LONG_PTR (signed type) |
|
31 | # LPARAM is defined as LONG_PTR (signed type) | |
32 | if ctypes.sizeof(ctypes.c_long) == ctypes.sizeof(ctypes.c_void_p): |
|
32 | if ctypes.sizeof(ctypes.c_long) == ctypes.sizeof(ctypes.c_void_p): | |
33 | _WPARAM = ctypes.c_ulong |
|
33 | _WPARAM = ctypes.c_ulong | |
34 | _LPARAM = ctypes.c_long |
|
34 | _LPARAM = ctypes.c_long | |
35 | elif ctypes.sizeof(ctypes.c_longlong) == ctypes.sizeof(ctypes.c_void_p): |
|
35 | elif ctypes.sizeof(ctypes.c_longlong) == ctypes.sizeof(ctypes.c_void_p): | |
36 | _WPARAM = ctypes.c_ulonglong |
|
36 | _WPARAM = ctypes.c_ulonglong | |
37 | _LPARAM = ctypes.c_longlong |
|
37 | _LPARAM = ctypes.c_longlong | |
38 |
|
38 | |||
39 | class _FILETIME(ctypes.Structure): |
|
39 | class _FILETIME(ctypes.Structure): | |
40 | _fields_ = [('dwLowDateTime', _DWORD), |
|
40 | _fields_ = [('dwLowDateTime', _DWORD), | |
41 | ('dwHighDateTime', _DWORD)] |
|
41 | ('dwHighDateTime', _DWORD)] | |
42 |
|
42 | |||
43 | class _BY_HANDLE_FILE_INFORMATION(ctypes.Structure): |
|
43 | class _BY_HANDLE_FILE_INFORMATION(ctypes.Structure): | |
44 | _fields_ = [('dwFileAttributes', _DWORD), |
|
44 | _fields_ = [('dwFileAttributes', _DWORD), | |
45 | ('ftCreationTime', _FILETIME), |
|
45 | ('ftCreationTime', _FILETIME), | |
46 | ('ftLastAccessTime', _FILETIME), |
|
46 | ('ftLastAccessTime', _FILETIME), | |
47 | ('ftLastWriteTime', _FILETIME), |
|
47 | ('ftLastWriteTime', _FILETIME), | |
48 | ('dwVolumeSerialNumber', _DWORD), |
|
48 | ('dwVolumeSerialNumber', _DWORD), | |
49 | ('nFileSizeHigh', _DWORD), |
|
49 | ('nFileSizeHigh', _DWORD), | |
50 | ('nFileSizeLow', _DWORD), |
|
50 | ('nFileSizeLow', _DWORD), | |
51 | ('nNumberOfLinks', _DWORD), |
|
51 | ('nNumberOfLinks', _DWORD), | |
52 | ('nFileIndexHigh', _DWORD), |
|
52 | ('nFileIndexHigh', _DWORD), | |
53 | ('nFileIndexLow', _DWORD)] |
|
53 | ('nFileIndexLow', _DWORD)] | |
54 |
|
54 | |||
55 | # CreateFile |
|
55 | # CreateFile | |
56 | _FILE_SHARE_READ = 0x00000001 |
|
56 | _FILE_SHARE_READ = 0x00000001 | |
57 | _FILE_SHARE_WRITE = 0x00000002 |
|
57 | _FILE_SHARE_WRITE = 0x00000002 | |
58 | _FILE_SHARE_DELETE = 0x00000004 |
|
58 | _FILE_SHARE_DELETE = 0x00000004 | |
59 |
|
59 | |||
60 | _OPEN_EXISTING = 3 |
|
60 | _OPEN_EXISTING = 3 | |
61 |
|
61 | |||
62 | _FILE_FLAG_BACKUP_SEMANTICS = 0x02000000 |
|
62 | _FILE_FLAG_BACKUP_SEMANTICS = 0x02000000 | |
63 |
|
63 | |||
64 | # SetFileAttributes |
|
64 | # SetFileAttributes | |
65 | _FILE_ATTRIBUTE_NORMAL = 0x80 |
|
65 | _FILE_ATTRIBUTE_NORMAL = 0x80 | |
66 | _FILE_ATTRIBUTE_NOT_CONTENT_INDEXED = 0x2000 |
|
66 | _FILE_ATTRIBUTE_NOT_CONTENT_INDEXED = 0x2000 | |
67 |
|
67 | |||
68 | # Process Security and Access Rights |
|
68 | # Process Security and Access Rights | |
69 | _PROCESS_QUERY_INFORMATION = 0x0400 |
|
69 | _PROCESS_QUERY_INFORMATION = 0x0400 | |
70 |
|
70 | |||
71 | # GetExitCodeProcess |
|
71 | # GetExitCodeProcess | |
72 | _STILL_ACTIVE = 259 |
|
72 | _STILL_ACTIVE = 259 | |
73 |
|
73 | |||
74 | class _STARTUPINFO(ctypes.Structure): |
|
74 | class _STARTUPINFO(ctypes.Structure): | |
75 | _fields_ = [('cb', _DWORD), |
|
75 | _fields_ = [('cb', _DWORD), | |
76 | ('lpReserved', _LPSTR), |
|
76 | ('lpReserved', _LPSTR), | |
77 | ('lpDesktop', _LPSTR), |
|
77 | ('lpDesktop', _LPSTR), | |
78 | ('lpTitle', _LPSTR), |
|
78 | ('lpTitle', _LPSTR), | |
79 | ('dwX', _DWORD), |
|
79 | ('dwX', _DWORD), | |
80 | ('dwY', _DWORD), |
|
80 | ('dwY', _DWORD), | |
81 | ('dwXSize', _DWORD), |
|
81 | ('dwXSize', _DWORD), | |
82 | ('dwYSize', _DWORD), |
|
82 | ('dwYSize', _DWORD), | |
83 | ('dwXCountChars', _DWORD), |
|
83 | ('dwXCountChars', _DWORD), | |
84 | ('dwYCountChars', _DWORD), |
|
84 | ('dwYCountChars', _DWORD), | |
85 | ('dwFillAttribute', _DWORD), |
|
85 | ('dwFillAttribute', _DWORD), | |
86 | ('dwFlags', _DWORD), |
|
86 | ('dwFlags', _DWORD), | |
87 | ('wShowWindow', _WORD), |
|
87 | ('wShowWindow', _WORD), | |
88 | ('cbReserved2', _WORD), |
|
88 | ('cbReserved2', _WORD), | |
89 | ('lpReserved2', ctypes.c_char_p), |
|
89 | ('lpReserved2', ctypes.c_char_p), | |
90 | ('hStdInput', _HANDLE), |
|
90 | ('hStdInput', _HANDLE), | |
91 | ('hStdOutput', _HANDLE), |
|
91 | ('hStdOutput', _HANDLE), | |
92 | ('hStdError', _HANDLE)] |
|
92 | ('hStdError', _HANDLE)] | |
93 |
|
93 | |||
94 | class _PROCESS_INFORMATION(ctypes.Structure): |
|
94 | class _PROCESS_INFORMATION(ctypes.Structure): | |
95 | _fields_ = [('hProcess', _HANDLE), |
|
95 | _fields_ = [('hProcess', _HANDLE), | |
96 | ('hThread', _HANDLE), |
|
96 | ('hThread', _HANDLE), | |
97 | ('dwProcessId', _DWORD), |
|
97 | ('dwProcessId', _DWORD), | |
98 | ('dwThreadId', _DWORD)] |
|
98 | ('dwThreadId', _DWORD)] | |
99 |
|
99 | |||
100 | _CREATE_NO_WINDOW = 0x08000000 |
|
100 | _CREATE_NO_WINDOW = 0x08000000 | |
101 | _SW_HIDE = 0 |
|
101 | _SW_HIDE = 0 | |
102 |
|
102 | |||
103 | class _COORD(ctypes.Structure): |
|
103 | class _COORD(ctypes.Structure): | |
104 | _fields_ = [('X', ctypes.c_short), |
|
104 | _fields_ = [('X', ctypes.c_short), | |
105 | ('Y', ctypes.c_short)] |
|
105 | ('Y', ctypes.c_short)] | |
106 |
|
106 | |||
107 | class _SMALL_RECT(ctypes.Structure): |
|
107 | class _SMALL_RECT(ctypes.Structure): | |
108 | _fields_ = [('Left', ctypes.c_short), |
|
108 | _fields_ = [('Left', ctypes.c_short), | |
109 | ('Top', ctypes.c_short), |
|
109 | ('Top', ctypes.c_short), | |
110 | ('Right', ctypes.c_short), |
|
110 | ('Right', ctypes.c_short), | |
111 | ('Bottom', ctypes.c_short)] |
|
111 | ('Bottom', ctypes.c_short)] | |
112 |
|
112 | |||
113 | class _CONSOLE_SCREEN_BUFFER_INFO(ctypes.Structure): |
|
113 | class _CONSOLE_SCREEN_BUFFER_INFO(ctypes.Structure): | |
114 | _fields_ = [('dwSize', _COORD), |
|
114 | _fields_ = [('dwSize', _COORD), | |
115 | ('dwCursorPosition', _COORD), |
|
115 | ('dwCursorPosition', _COORD), | |
116 | ('wAttributes', _WORD), |
|
116 | ('wAttributes', _WORD), | |
117 | ('srWindow', _SMALL_RECT), |
|
117 | ('srWindow', _SMALL_RECT), | |
118 | ('dwMaximumWindowSize', _COORD)] |
|
118 | ('dwMaximumWindowSize', _COORD)] | |
119 |
|
119 | |||
120 | _STD_ERROR_HANDLE = _DWORD(-12).value |
|
120 | _STD_ERROR_HANDLE = _DWORD(-12).value | |
121 |
|
121 | |||
122 | # types of parameters of C functions used (required by pypy) |
|
122 | # types of parameters of C functions used (required by pypy) | |
123 |
|
123 | |||
124 | _kernel32.CreateFileA.argtypes = [_LPCSTR, _DWORD, _DWORD, ctypes.c_void_p, |
|
124 | _kernel32.CreateFileA.argtypes = [_LPCSTR, _DWORD, _DWORD, ctypes.c_void_p, | |
125 | _DWORD, _DWORD, _HANDLE] |
|
125 | _DWORD, _DWORD, _HANDLE] | |
126 | _kernel32.CreateFileA.restype = _HANDLE |
|
126 | _kernel32.CreateFileA.restype = _HANDLE | |
127 |
|
127 | |||
128 | _kernel32.GetFileInformationByHandle.argtypes = [_HANDLE, ctypes.c_void_p] |
|
128 | _kernel32.GetFileInformationByHandle.argtypes = [_HANDLE, ctypes.c_void_p] | |
129 | _kernel32.GetFileInformationByHandle.restype = _BOOL |
|
129 | _kernel32.GetFileInformationByHandle.restype = _BOOL | |
130 |
|
130 | |||
131 | _kernel32.CloseHandle.argtypes = [_HANDLE] |
|
131 | _kernel32.CloseHandle.argtypes = [_HANDLE] | |
132 | _kernel32.CloseHandle.restype = _BOOL |
|
132 | _kernel32.CloseHandle.restype = _BOOL | |
133 |
|
133 | |||
134 | try: |
|
134 | try: | |
135 | _kernel32.CreateHardLinkA.argtypes = [_LPCSTR, _LPCSTR, ctypes.c_void_p] |
|
135 | _kernel32.CreateHardLinkA.argtypes = [_LPCSTR, _LPCSTR, ctypes.c_void_p] | |
136 | _kernel32.CreateHardLinkA.restype = _BOOL |
|
136 | _kernel32.CreateHardLinkA.restype = _BOOL | |
137 | except AttributeError: |
|
137 | except AttributeError: | |
138 | pass |
|
138 | pass | |
139 |
|
139 | |||
140 | _kernel32.SetFileAttributesA.argtypes = [_LPCSTR, _DWORD] |
|
140 | _kernel32.SetFileAttributesA.argtypes = [_LPCSTR, _DWORD] | |
141 | _kernel32.SetFileAttributesA.restype = _BOOL |
|
141 | _kernel32.SetFileAttributesA.restype = _BOOL | |
142 |
|
142 | |||
143 | _kernel32.OpenProcess.argtypes = [_DWORD, _BOOL, _DWORD] |
|
143 | _kernel32.OpenProcess.argtypes = [_DWORD, _BOOL, _DWORD] | |
144 | _kernel32.OpenProcess.restype = _HANDLE |
|
144 | _kernel32.OpenProcess.restype = _HANDLE | |
145 |
|
145 | |||
146 | _kernel32.GetExitCodeProcess.argtypes = [_HANDLE, ctypes.c_void_p] |
|
146 | _kernel32.GetExitCodeProcess.argtypes = [_HANDLE, ctypes.c_void_p] | |
147 | _kernel32.GetExitCodeProcess.restype = _BOOL |
|
147 | _kernel32.GetExitCodeProcess.restype = _BOOL | |
148 |
|
148 | |||
149 | _kernel32.GetLastError.argtypes = [] |
|
149 | _kernel32.GetLastError.argtypes = [] | |
150 | _kernel32.GetLastError.restype = _DWORD |
|
150 | _kernel32.GetLastError.restype = _DWORD | |
151 |
|
151 | |||
152 | _kernel32.GetModuleFileNameA.argtypes = [_HANDLE, ctypes.c_void_p, _DWORD] |
|
152 | _kernel32.GetModuleFileNameA.argtypes = [_HANDLE, ctypes.c_void_p, _DWORD] | |
153 | _kernel32.GetModuleFileNameA.restype = _DWORD |
|
153 | _kernel32.GetModuleFileNameA.restype = _DWORD | |
154 |
|
154 | |||
155 | _kernel32.CreateProcessA.argtypes = [_LPCSTR, _LPCSTR, ctypes.c_void_p, |
|
155 | _kernel32.CreateProcessA.argtypes = [_LPCSTR, _LPCSTR, ctypes.c_void_p, | |
156 | ctypes.c_void_p, _BOOL, _DWORD, ctypes.c_void_p, _LPCSTR, ctypes.c_void_p, |
|
156 | ctypes.c_void_p, _BOOL, _DWORD, ctypes.c_void_p, _LPCSTR, ctypes.c_void_p, | |
157 | ctypes.c_void_p] |
|
157 | ctypes.c_void_p] | |
158 | _kernel32.CreateProcessA.restype = _BOOL |
|
158 | _kernel32.CreateProcessA.restype = _BOOL | |
159 |
|
159 | |||
160 | _kernel32.ExitProcess.argtypes = [_UINT] |
|
160 | _kernel32.ExitProcess.argtypes = [_UINT] | |
161 | _kernel32.ExitProcess.restype = None |
|
161 | _kernel32.ExitProcess.restype = None | |
162 |
|
162 | |||
163 | _kernel32.GetCurrentProcessId.argtypes = [] |
|
163 | _kernel32.GetCurrentProcessId.argtypes = [] | |
164 | _kernel32.GetCurrentProcessId.restype = _DWORD |
|
164 | _kernel32.GetCurrentProcessId.restype = _DWORD | |
165 |
|
165 | |||
166 | _SIGNAL_HANDLER = ctypes.WINFUNCTYPE(_BOOL, _DWORD) |
|
166 | _SIGNAL_HANDLER = ctypes.WINFUNCTYPE(_BOOL, _DWORD) | |
167 | _kernel32.SetConsoleCtrlHandler.argtypes = [_SIGNAL_HANDLER, _BOOL] |
|
167 | _kernel32.SetConsoleCtrlHandler.argtypes = [_SIGNAL_HANDLER, _BOOL] | |
168 | _kernel32.SetConsoleCtrlHandler.restype = _BOOL |
|
168 | _kernel32.SetConsoleCtrlHandler.restype = _BOOL | |
169 |
|
169 | |||
170 | _kernel32.GetStdHandle.argtypes = [_DWORD] |
|
170 | _kernel32.GetStdHandle.argtypes = [_DWORD] | |
171 | _kernel32.GetStdHandle.restype = _HANDLE |
|
171 | _kernel32.GetStdHandle.restype = _HANDLE | |
172 |
|
172 | |||
173 | _kernel32.GetConsoleScreenBufferInfo.argtypes = [_HANDLE, ctypes.c_void_p] |
|
173 | _kernel32.GetConsoleScreenBufferInfo.argtypes = [_HANDLE, ctypes.c_void_p] | |
174 | _kernel32.GetConsoleScreenBufferInfo.restype = _BOOL |
|
174 | _kernel32.GetConsoleScreenBufferInfo.restype = _BOOL | |
175 |
|
175 | |||
176 | _advapi32.GetUserNameA.argtypes = [ctypes.c_void_p, ctypes.c_void_p] |
|
176 | _advapi32.GetUserNameA.argtypes = [ctypes.c_void_p, ctypes.c_void_p] | |
177 | _advapi32.GetUserNameA.restype = _BOOL |
|
177 | _advapi32.GetUserNameA.restype = _BOOL | |
178 |
|
178 | |||
179 | _user32.GetWindowThreadProcessId.argtypes = [_HANDLE, ctypes.c_void_p] |
|
179 | _user32.GetWindowThreadProcessId.argtypes = [_HANDLE, ctypes.c_void_p] | |
180 | _user32.GetWindowThreadProcessId.restype = _DWORD |
|
180 | _user32.GetWindowThreadProcessId.restype = _DWORD | |
181 |
|
181 | |||
182 | _user32.ShowWindow.argtypes = [_HANDLE, ctypes.c_int] |
|
182 | _user32.ShowWindow.argtypes = [_HANDLE, ctypes.c_int] | |
183 | _user32.ShowWindow.restype = _BOOL |
|
183 | _user32.ShowWindow.restype = _BOOL | |
184 |
|
184 | |||
185 | _WNDENUMPROC = ctypes.WINFUNCTYPE(_BOOL, _HWND, _LPARAM) |
|
185 | _WNDENUMPROC = ctypes.WINFUNCTYPE(_BOOL, _HWND, _LPARAM) | |
186 | _user32.EnumWindows.argtypes = [_WNDENUMPROC, _LPARAM] |
|
186 | _user32.EnumWindows.argtypes = [_WNDENUMPROC, _LPARAM] | |
187 | _user32.EnumWindows.restype = _BOOL |
|
187 | _user32.EnumWindows.restype = _BOOL | |
188 |
|
188 | |||
189 | def _raiseoserror(name): |
|
189 | def _raiseoserror(name): | |
190 | err = ctypes.WinError() |
|
190 | err = ctypes.WinError() | |
191 | raise OSError(err.errno, '%s: %s' % (name, err.strerror)) |
|
191 | raise OSError(err.errno, '%s: %s' % (name, err.strerror)) | |
192 |
|
192 | |||
193 | def _getfileinfo(name): |
|
193 | def _getfileinfo(name): | |
194 | fh = _kernel32.CreateFileA(name, 0, |
|
194 | fh = _kernel32.CreateFileA(name, 0, | |
195 | _FILE_SHARE_READ | _FILE_SHARE_WRITE | _FILE_SHARE_DELETE, |
|
195 | _FILE_SHARE_READ | _FILE_SHARE_WRITE | _FILE_SHARE_DELETE, | |
196 | None, _OPEN_EXISTING, _FILE_FLAG_BACKUP_SEMANTICS, None) |
|
196 | None, _OPEN_EXISTING, _FILE_FLAG_BACKUP_SEMANTICS, None) | |
197 | if fh == _INVALID_HANDLE_VALUE: |
|
197 | if fh == _INVALID_HANDLE_VALUE: | |
198 | _raiseoserror(name) |
|
198 | _raiseoserror(name) | |
199 | try: |
|
199 | try: | |
200 | fi = _BY_HANDLE_FILE_INFORMATION() |
|
200 | fi = _BY_HANDLE_FILE_INFORMATION() | |
201 | if not _kernel32.GetFileInformationByHandle(fh, ctypes.byref(fi)): |
|
201 | if not _kernel32.GetFileInformationByHandle(fh, ctypes.byref(fi)): | |
202 | _raiseoserror(name) |
|
202 | _raiseoserror(name) | |
203 | return fi |
|
203 | return fi | |
204 | finally: |
|
204 | finally: | |
205 | _kernel32.CloseHandle(fh) |
|
205 | _kernel32.CloseHandle(fh) | |
206 |
|
206 | |||
207 | def oslink(src, dst): |
|
207 | def oslink(src, dst): | |
208 | try: |
|
208 | try: | |
209 | if not _kernel32.CreateHardLinkA(dst, src, None): |
|
209 | if not _kernel32.CreateHardLinkA(dst, src, None): | |
210 | _raiseoserror(src) |
|
210 | _raiseoserror(src) | |
211 | except AttributeError: # Wine doesn't support this function |
|
211 | except AttributeError: # Wine doesn't support this function | |
212 | _raiseoserror(src) |
|
212 | _raiseoserror(src) | |
213 |
|
213 | |||
214 | def nlinks(name): |
|
214 | def nlinks(name): | |
215 | '''return number of hardlinks for the given file''' |
|
215 | '''return number of hardlinks for the given file''' | |
216 | return _getfileinfo(name).nNumberOfLinks |
|
216 | return _getfileinfo(name).nNumberOfLinks | |
217 |
|
217 | |||
218 | def samefile(path1, path2): |
|
218 | def samefile(path1, path2): | |
219 | '''Returns whether path1 and path2 refer to the same file or directory.''' |
|
219 | '''Returns whether path1 and path2 refer to the same file or directory.''' | |
220 | res1 = _getfileinfo(path1) |
|
220 | res1 = _getfileinfo(path1) | |
221 | res2 = _getfileinfo(path2) |
|
221 | res2 = _getfileinfo(path2) | |
222 | return (res1.dwVolumeSerialNumber == res2.dwVolumeSerialNumber |
|
222 | return (res1.dwVolumeSerialNumber == res2.dwVolumeSerialNumber | |
223 | and res1.nFileIndexHigh == res2.nFileIndexHigh |
|
223 | and res1.nFileIndexHigh == res2.nFileIndexHigh | |
224 | and res1.nFileIndexLow == res2.nFileIndexLow) |
|
224 | and res1.nFileIndexLow == res2.nFileIndexLow) | |
225 |
|
225 | |||
226 | def samedevice(path1, path2): |
|
226 | def samedevice(path1, path2): | |
227 | '''Returns whether path1 and path2 are on the same device.''' |
|
227 | '''Returns whether path1 and path2 are on the same device.''' | |
228 | res1 = _getfileinfo(path1) |
|
228 | res1 = _getfileinfo(path1) | |
229 | res2 = _getfileinfo(path2) |
|
229 | res2 = _getfileinfo(path2) | |
230 | return res1.dwVolumeSerialNumber == res2.dwVolumeSerialNumber |
|
230 | return res1.dwVolumeSerialNumber == res2.dwVolumeSerialNumber | |
231 |
|
231 | |||
232 | def testpid(pid): |
|
232 | def testpid(pid): | |
233 | '''return True if pid is still running or unable to |
|
233 | '''return True if pid is still running or unable to | |
234 | determine, False otherwise''' |
|
234 | determine, False otherwise''' | |
235 | h = _kernel32.OpenProcess(_PROCESS_QUERY_INFORMATION, False, pid) |
|
235 | h = _kernel32.OpenProcess(_PROCESS_QUERY_INFORMATION, False, pid) | |
236 | if h: |
|
236 | if h: | |
237 | try: |
|
237 | try: | |
238 | status = _DWORD() |
|
238 | status = _DWORD() | |
239 | if _kernel32.GetExitCodeProcess(h, ctypes.byref(status)): |
|
239 | if _kernel32.GetExitCodeProcess(h, ctypes.byref(status)): | |
240 | return status.value == _STILL_ACTIVE |
|
240 | return status.value == _STILL_ACTIVE | |
241 | finally: |
|
241 | finally: | |
242 | _kernel32.CloseHandle(h) |
|
242 | _kernel32.CloseHandle(h) | |
243 | return _kernel32.GetLastError() != _ERROR_INVALID_PARAMETER |
|
243 | return _kernel32.GetLastError() != _ERROR_INVALID_PARAMETER | |
244 |
|
244 | |||
245 | def executablepath(): |
|
245 | def executablepath(): | |
246 | '''return full path of hg.exe''' |
|
246 | '''return full path of hg.exe''' | |
247 | size = 600 |
|
247 | size = 600 | |
248 | buf = ctypes.create_string_buffer(size + 1) |
|
248 | buf = ctypes.create_string_buffer(size + 1) | |
249 | len = _kernel32.GetModuleFileNameA(None, ctypes.byref(buf), size) |
|
249 | len = _kernel32.GetModuleFileNameA(None, ctypes.byref(buf), size) | |
250 | if len == 0: |
|
250 | if len == 0: | |
251 | raise ctypes.WinError |
|
251 | raise ctypes.WinError | |
252 | elif len == size: |
|
252 | elif len == size: | |
253 | raise ctypes.WinError(_ERROR_INSUFFICIENT_BUFFER) |
|
253 | raise ctypes.WinError(_ERROR_INSUFFICIENT_BUFFER) | |
254 | return buf.value |
|
254 | return buf.value | |
255 |
|
255 | |||
256 | def getuser(): |
|
256 | def getuser(): | |
257 | '''return name of current user''' |
|
257 | '''return name of current user''' | |
258 | size = _DWORD(300) |
|
258 | size = _DWORD(300) | |
259 | buf = ctypes.create_string_buffer(size.value + 1) |
|
259 | buf = ctypes.create_string_buffer(size.value + 1) | |
260 | if not _advapi32.GetUserNameA(ctypes.byref(buf), ctypes.byref(size)): |
|
260 | if not _advapi32.GetUserNameA(ctypes.byref(buf), ctypes.byref(size)): | |
261 | raise ctypes.WinError |
|
261 | raise ctypes.WinError | |
262 | return buf.value |
|
262 | return buf.value | |
263 |
|
263 | |||
264 | _signalhandler = [] |
|
264 | _signalhandler = [] | |
265 |
|
265 | |||
266 | def setsignalhandler(): |
|
266 | def setsignalhandler(): | |
267 | '''Register a termination handler for console events including |
|
267 | '''Register a termination handler for console events including | |
268 | CTRL+C. python signal handlers do not work well with socket |
|
268 | CTRL+C. python signal handlers do not work well with socket | |
269 | operations. |
|
269 | operations. | |
270 | ''' |
|
270 | ''' | |
271 | def handler(event): |
|
271 | def handler(event): | |
272 | _kernel32.ExitProcess(1) |
|
272 | _kernel32.ExitProcess(1) | |
273 |
|
273 | |||
274 | if _signalhandler: |
|
274 | if _signalhandler: | |
275 | return # already registered |
|
275 | return # already registered | |
276 | h = _SIGNAL_HANDLER(handler) |
|
276 | h = _SIGNAL_HANDLER(handler) | |
277 | _signalhandler.append(h) # needed to prevent garbage collection |
|
277 | _signalhandler.append(h) # needed to prevent garbage collection | |
278 | if not _kernel32.SetConsoleCtrlHandler(h, True): |
|
278 | if not _kernel32.SetConsoleCtrlHandler(h, True): | |
279 | raise ctypes.WinError |
|
279 | raise ctypes.WinError | |
280 |
|
280 | |||
281 | def hidewindow(): |
|
281 | def hidewindow(): | |
282 |
|
282 | |||
283 | def callback(hwnd, pid): |
|
283 | def callback(hwnd, pid): | |
284 | wpid = _DWORD() |
|
284 | wpid = _DWORD() | |
285 | _user32.GetWindowThreadProcessId(hwnd, ctypes.byref(wpid)) |
|
285 | _user32.GetWindowThreadProcessId(hwnd, ctypes.byref(wpid)) | |
286 | if pid == wpid.value: |
|
286 | if pid == wpid.value: | |
287 | _user32.ShowWindow(hwnd, _SW_HIDE) |
|
287 | _user32.ShowWindow(hwnd, _SW_HIDE) | |
288 | return False # stop enumerating windows |
|
288 | return False # stop enumerating windows | |
289 | return True |
|
289 | return True | |
290 |
|
290 | |||
291 | pid = _kernel32.GetCurrentProcessId() |
|
291 | pid = _kernel32.GetCurrentProcessId() | |
292 | _user32.EnumWindows(_WNDENUMPROC(callback), pid) |
|
292 | _user32.EnumWindows(_WNDENUMPROC(callback), pid) | |
293 |
|
293 | |||
294 | def termwidth(): |
|
294 | def termwidth(): | |
295 | # cmd.exe does not handle CR like a unix console, the CR is |
|
295 | # cmd.exe does not handle CR like a unix console, the CR is | |
296 | # counted in the line length. On 80 columns consoles, if 80 |
|
296 | # counted in the line length. On 80 columns consoles, if 80 | |
297 | # characters are written, the following CR won't apply on the |
|
297 | # characters are written, the following CR won't apply on the | |
298 | # current line but on the new one. Keep room for it. |
|
298 | # current line but on the new one. Keep room for it. | |
299 | width = 79 |
|
299 | width = 79 | |
300 | # Query stderr to avoid problems with redirections |
|
300 | # Query stderr to avoid problems with redirections | |
301 | screenbuf = _kernel32.GetStdHandle( |
|
301 | screenbuf = _kernel32.GetStdHandle( | |
302 | _STD_ERROR_HANDLE) # don't close the handle returned |
|
302 | _STD_ERROR_HANDLE) # don't close the handle returned | |
303 | if screenbuf is None or screenbuf == _INVALID_HANDLE_VALUE: |
|
303 | if screenbuf is None or screenbuf == _INVALID_HANDLE_VALUE: | |
304 | return width |
|
304 | return width | |
305 | csbi = _CONSOLE_SCREEN_BUFFER_INFO() |
|
305 | csbi = _CONSOLE_SCREEN_BUFFER_INFO() | |
306 | if not _kernel32.GetConsoleScreenBufferInfo( |
|
306 | if not _kernel32.GetConsoleScreenBufferInfo( | |
307 | screenbuf, ctypes.byref(csbi)): |
|
307 | screenbuf, ctypes.byref(csbi)): | |
308 | return width |
|
308 | return width | |
309 | width = csbi.srWindow.Right - csbi.srWindow.Left |
|
309 | width = csbi.srWindow.Right - csbi.srWindow.Left | |
310 | return width |
|
310 | return width | |
311 |
|
311 | |||
312 | def spawndetached(args): |
|
312 | def spawndetached(args): | |
313 | # No standard library function really spawns a fully detached |
|
313 | # No standard library function really spawns a fully detached | |
314 | # process under win32 because they allocate pipes or other objects |
|
314 | # process under win32 because they allocate pipes or other objects | |
315 | # to handle standard streams communications. Passing these objects |
|
315 | # to handle standard streams communications. Passing these objects | |
316 | # to the child process requires handle inheritance to be enabled |
|
316 | # to the child process requires handle inheritance to be enabled | |
317 | # which makes really detached processes impossible. |
|
317 | # which makes really detached processes impossible. | |
318 | si = _STARTUPINFO() |
|
318 | si = _STARTUPINFO() | |
319 | si.cb = ctypes.sizeof(_STARTUPINFO) |
|
319 | si.cb = ctypes.sizeof(_STARTUPINFO) | |
320 |
|
320 | |||
321 | pi = _PROCESS_INFORMATION() |
|
321 | pi = _PROCESS_INFORMATION() | |
322 |
|
322 | |||
323 | env = '' |
|
323 | env = '' | |
324 | for k in os.environ: |
|
324 | for k in os.environ: | |
325 | env += "%s=%s\0" % (k, os.environ[k]) |
|
325 | env += "%s=%s\0" % (k, os.environ[k]) | |
326 | if not env: |
|
326 | if not env: | |
327 | env = '\0' |
|
327 | env = '\0' | |
328 | env += '\0' |
|
328 | env += '\0' | |
329 |
|
329 | |||
330 | args = subprocess.list2cmdline(args) |
|
330 | args = subprocess.list2cmdline(args) | |
331 |
# Not running the command in shell mode makes |
|
331 | # Not running the command in shell mode makes Python 2.6 hang when | |
332 | # writing to hgweb output socket. |
|
332 | # writing to hgweb output socket. | |
333 | comspec = os.environ.get("COMSPEC", "cmd.exe") |
|
333 | comspec = os.environ.get("COMSPEC", "cmd.exe") | |
334 | args = comspec + " /c " + args |
|
334 | args = comspec + " /c " + args | |
335 |
|
335 | |||
336 | res = _kernel32.CreateProcessA( |
|
336 | res = _kernel32.CreateProcessA( | |
337 | None, args, None, None, False, _CREATE_NO_WINDOW, |
|
337 | None, args, None, None, False, _CREATE_NO_WINDOW, | |
338 | env, os.getcwd(), ctypes.byref(si), ctypes.byref(pi)) |
|
338 | env, os.getcwd(), ctypes.byref(si), ctypes.byref(pi)) | |
339 | if not res: |
|
339 | if not res: | |
340 | raise ctypes.WinError |
|
340 | raise ctypes.WinError | |
341 |
|
341 | |||
342 | return pi.dwProcessId |
|
342 | return pi.dwProcessId | |
343 |
|
343 | |||
344 | def unlink(f): |
|
344 | def unlink(f): | |
345 | '''try to implement POSIX' unlink semantics on Windows''' |
|
345 | '''try to implement POSIX' unlink semantics on Windows''' | |
346 |
|
346 | |||
347 | # POSIX allows to unlink and rename open files. Windows has serious |
|
347 | # POSIX allows to unlink and rename open files. Windows has serious | |
348 | # problems with doing that: |
|
348 | # problems with doing that: | |
349 | # - Calling os.unlink (or os.rename) on a file f fails if f or any |
|
349 | # - Calling os.unlink (or os.rename) on a file f fails if f or any | |
350 | # hardlinked copy of f has been opened with Python's open(). There is no |
|
350 | # hardlinked copy of f has been opened with Python's open(). There is no | |
351 | # way such a file can be deleted or renamed on Windows (other than |
|
351 | # way such a file can be deleted or renamed on Windows (other than | |
352 | # scheduling the delete or rename for the next reboot). |
|
352 | # scheduling the delete or rename for the next reboot). | |
353 | # - Calling os.unlink on a file that has been opened with Mercurial's |
|
353 | # - Calling os.unlink on a file that has been opened with Mercurial's | |
354 | # posixfile (or comparable methods) will delay the actual deletion of |
|
354 | # posixfile (or comparable methods) will delay the actual deletion of | |
355 | # the file for as long as the file is held open. The filename is blocked |
|
355 | # the file for as long as the file is held open. The filename is blocked | |
356 | # during that time and cannot be used for recreating a new file under |
|
356 | # during that time and cannot be used for recreating a new file under | |
357 | # that same name ("zombie file"). Directories containing such zombie files |
|
357 | # that same name ("zombie file"). Directories containing such zombie files | |
358 | # cannot be removed or moved. |
|
358 | # cannot be removed or moved. | |
359 | # A file that has been opened with posixfile can be renamed, so we rename |
|
359 | # A file that has been opened with posixfile can be renamed, so we rename | |
360 | # f to a random temporary name before calling os.unlink on it. This allows |
|
360 | # f to a random temporary name before calling os.unlink on it. This allows | |
361 | # callers to recreate f immediately while having other readers do their |
|
361 | # callers to recreate f immediately while having other readers do their | |
362 | # implicit zombie filename blocking on a temporary name. |
|
362 | # implicit zombie filename blocking on a temporary name. | |
363 |
|
363 | |||
364 | for tries in xrange(10): |
|
364 | for tries in xrange(10): | |
365 | temp = '%s-%08x' % (f, random.randint(0, 0xffffffff)) |
|
365 | temp = '%s-%08x' % (f, random.randint(0, 0xffffffff)) | |
366 | try: |
|
366 | try: | |
367 | os.rename(f, temp) # raises OSError EEXIST if temp exists |
|
367 | os.rename(f, temp) # raises OSError EEXIST if temp exists | |
368 | break |
|
368 | break | |
369 | except OSError, e: |
|
369 | except OSError, e: | |
370 | if e.errno != errno.EEXIST: |
|
370 | if e.errno != errno.EEXIST: | |
371 | raise |
|
371 | raise | |
372 | else: |
|
372 | else: | |
373 | raise IOError, (errno.EEXIST, "No usable temporary filename found") |
|
373 | raise IOError, (errno.EEXIST, "No usable temporary filename found") | |
374 |
|
374 | |||
375 | try: |
|
375 | try: | |
376 | os.unlink(temp) |
|
376 | os.unlink(temp) | |
377 | except OSError: |
|
377 | except OSError: | |
378 | # The unlink might have failed because the READONLY attribute may heave |
|
378 | # The unlink might have failed because the READONLY attribute may heave | |
379 | # been set on the original file. Rename works fine with READONLY set, |
|
379 | # been set on the original file. Rename works fine with READONLY set, | |
380 | # but not os.unlink. Reset all attributes and try again. |
|
380 | # but not os.unlink. Reset all attributes and try again. | |
381 | _kernel32.SetFileAttributesA(temp, _FILE_ATTRIBUTE_NORMAL) |
|
381 | _kernel32.SetFileAttributesA(temp, _FILE_ATTRIBUTE_NORMAL) | |
382 | try: |
|
382 | try: | |
383 | os.unlink(temp) |
|
383 | os.unlink(temp) | |
384 | except OSError: |
|
384 | except OSError: | |
385 | # The unlink might have failed due to some very rude AV-Scanners. |
|
385 | # The unlink might have failed due to some very rude AV-Scanners. | |
386 | # Leaking a tempfile is the lesser evil than aborting here and |
|
386 | # Leaking a tempfile is the lesser evil than aborting here and | |
387 | # leaving some potentially serious inconsistencies. |
|
387 | # leaving some potentially serious inconsistencies. | |
388 | pass |
|
388 | pass | |
389 |
|
389 | |||
390 | def makedir(path, notindexed): |
|
390 | def makedir(path, notindexed): | |
391 | os.mkdir(path) |
|
391 | os.mkdir(path) | |
392 | if notindexed: |
|
392 | if notindexed: | |
393 | _kernel32.SetFileAttributesA(path, _FILE_ATTRIBUTE_NOT_CONTENT_INDEXED) |
|
393 | _kernel32.SetFileAttributesA(path, _FILE_ATTRIBUTE_NOT_CONTENT_INDEXED) |
@@ -1,142 +1,142 | |||||
1 | $ cat > correct.py <<EOF |
|
1 | $ cat > correct.py <<EOF | |
2 | > def toto(arg1, arg2): |
|
2 | > def toto(arg1, arg2): | |
3 | > del arg2 |
|
3 | > del arg2 | |
4 | > return (5 + 6, 9) |
|
4 | > return (5 + 6, 9) | |
5 | > EOF |
|
5 | > EOF | |
6 | $ cat > wrong.py <<EOF |
|
6 | $ cat > wrong.py <<EOF | |
7 | > def toto( arg1, arg2): |
|
7 | > def toto( arg1, arg2): | |
8 | > del(arg2) |
|
8 | > del(arg2) | |
9 | > return ( 5+6, 9) |
|
9 | > return ( 5+6, 9) | |
10 | > EOF |
|
10 | > EOF | |
11 | $ cat > quote.py <<EOF |
|
11 | $ cat > quote.py <<EOF | |
12 | > # let's use quote in comments |
|
12 | > # let's use quote in comments | |
13 | > (''' ( 4x5 ) |
|
13 | > (''' ( 4x5 ) | |
14 | > but """\\''' and finally''', |
|
14 | > but """\\''' and finally''', | |
15 | > """let's fool checkpatch""", '1+2', |
|
15 | > """let's fool checkpatch""", '1+2', | |
16 | > '"""', 42+1, """and |
|
16 | > '"""', 42+1, """and | |
17 | > ( 4-1 ) """, "( 1+1 )\" and ") |
|
17 | > ( 4-1 ) """, "( 1+1 )\" and ") | |
18 | > a, '\\\\\\\\', "\\\\\\" x-2", "c-1" |
|
18 | > a, '\\\\\\\\', "\\\\\\" x-2", "c-1" | |
19 | > EOF |
|
19 | > EOF | |
20 | $ cat > non-py24.py <<EOF |
|
20 | $ cat > non-py24.py <<EOF | |
21 | > # Using builtins that does not exist in Python 2.4 |
|
21 | > # Using builtins that does not exist in Python 2.4 | |
22 | > if any(): |
|
22 | > if any(): | |
23 | > x = all() |
|
23 | > x = all() | |
24 | > y = format(x) |
|
24 | > y = format(x) | |
25 | > |
|
25 | > | |
26 | > # Do not complain about our own definition |
|
26 | > # Do not complain about our own definition | |
27 | > def any(x): |
|
27 | > def any(x): | |
28 | > pass |
|
28 | > pass | |
29 | > |
|
29 | > | |
30 | > # try/except/finally block does not exist in Python 2.4 |
|
30 | > # try/except/finally block does not exist in Python 2.4 | |
31 | > try: |
|
31 | > try: | |
32 | > pass |
|
32 | > pass | |
33 | > except StandardError, inst: |
|
33 | > except StandardError, inst: | |
34 | > pass |
|
34 | > pass | |
35 | > finally: |
|
35 | > finally: | |
36 | > pass |
|
36 | > pass | |
37 | > |
|
37 | > | |
38 | > # nested try/finally+try/except is allowed |
|
38 | > # nested try/finally+try/except is allowed | |
39 | > try: |
|
39 | > try: | |
40 | > try: |
|
40 | > try: | |
41 | > pass |
|
41 | > pass | |
42 | > except StandardError, inst: |
|
42 | > except StandardError, inst: | |
43 | > pass |
|
43 | > pass | |
44 | > finally: |
|
44 | > finally: | |
45 | > pass |
|
45 | > pass | |
46 | > EOF |
|
46 | > EOF | |
47 | $ cat > classstyle.py <<EOF |
|
47 | $ cat > classstyle.py <<EOF | |
48 | > class newstyle_class(object): |
|
48 | > class newstyle_class(object): | |
49 | > pass |
|
49 | > pass | |
50 | > |
|
50 | > | |
51 | > class oldstyle_class: |
|
51 | > class oldstyle_class: | |
52 | > pass |
|
52 | > pass | |
53 | > |
|
53 | > | |
54 | > class empty(): |
|
54 | > class empty(): | |
55 | > pass |
|
55 | > pass | |
56 | > |
|
56 | > | |
57 | > no_class = 1: |
|
57 | > no_class = 1: | |
58 | > pass |
|
58 | > pass | |
59 | > EOF |
|
59 | > EOF | |
60 | $ check_code="$TESTDIR"/../contrib/check-code.py |
|
60 | $ check_code="$TESTDIR"/../contrib/check-code.py | |
61 | $ "$check_code" ./wrong.py ./correct.py ./quote.py ./non-py24.py ./classstyle.py |
|
61 | $ "$check_code" ./wrong.py ./correct.py ./quote.py ./non-py24.py ./classstyle.py | |
62 | ./wrong.py:1: |
|
62 | ./wrong.py:1: | |
63 | > def toto( arg1, arg2): |
|
63 | > def toto( arg1, arg2): | |
64 | gratuitous whitespace in () or [] |
|
64 | gratuitous whitespace in () or [] | |
65 | ./wrong.py:2: |
|
65 | ./wrong.py:2: | |
66 | > del(arg2) |
|
66 | > del(arg2) | |
67 | Python keyword is not a function |
|
67 | Python keyword is not a function | |
68 | ./wrong.py:3: |
|
68 | ./wrong.py:3: | |
69 | > return ( 5+6, 9) |
|
69 | > return ( 5+6, 9) | |
70 | gratuitous whitespace in () or [] |
|
70 | gratuitous whitespace in () or [] | |
71 | missing whitespace in expression |
|
71 | missing whitespace in expression | |
72 | ./quote.py:5: |
|
72 | ./quote.py:5: | |
73 | > '"""', 42+1, """and |
|
73 | > '"""', 42+1, """and | |
74 | missing whitespace in expression |
|
74 | missing whitespace in expression | |
75 | ./non-py24.py:2: |
|
75 | ./non-py24.py:2: | |
76 | > if any(): |
|
76 | > if any(): | |
77 | any/all/format not available in Python 2.4 |
|
77 | any/all/format not available in Python 2.4 | |
78 | ./non-py24.py:3: |
|
78 | ./non-py24.py:3: | |
79 | > x = all() |
|
79 | > x = all() | |
80 | any/all/format not available in Python 2.4 |
|
80 | any/all/format not available in Python 2.4 | |
81 | ./non-py24.py:4: |
|
81 | ./non-py24.py:4: | |
82 | > y = format(x) |
|
82 | > y = format(x) | |
83 | any/all/format not available in Python 2.4 |
|
83 | any/all/format not available in Python 2.4 | |
84 | ./non-py24.py:11: |
|
84 | ./non-py24.py:11: | |
85 | > try: |
|
85 | > try: | |
86 | no try/except/finally in Py2.4 |
|
86 | no try/except/finally in Python 2.4 | |
87 | ./classstyle.py:4: |
|
87 | ./classstyle.py:4: | |
88 | > class oldstyle_class: |
|
88 | > class oldstyle_class: | |
89 | old-style class, use class foo(object) |
|
89 | old-style class, use class foo(object) | |
90 | ./classstyle.py:7: |
|
90 | ./classstyle.py:7: | |
91 | > class empty(): |
|
91 | > class empty(): | |
92 | class foo() not available in Python 2.4, use class foo(object) |
|
92 | class foo() not available in Python 2.4, use class foo(object) | |
93 | [1] |
|
93 | [1] | |
94 |
|
94 | |||
95 | $ cat > is-op.py <<EOF |
|
95 | $ cat > is-op.py <<EOF | |
96 | > # is-operator comparing number or string literal |
|
96 | > # is-operator comparing number or string literal | |
97 | > x = None |
|
97 | > x = None | |
98 | > y = x is 'foo' |
|
98 | > y = x is 'foo' | |
99 | > y = x is "foo" |
|
99 | > y = x is "foo" | |
100 | > y = x is 5346 |
|
100 | > y = x is 5346 | |
101 | > y = x is -6 |
|
101 | > y = x is -6 | |
102 | > y = x is not 'foo' |
|
102 | > y = x is not 'foo' | |
103 | > y = x is not "foo" |
|
103 | > y = x is not "foo" | |
104 | > y = x is not 5346 |
|
104 | > y = x is not 5346 | |
105 | > y = x is not -6 |
|
105 | > y = x is not -6 | |
106 | > EOF |
|
106 | > EOF | |
107 |
|
107 | |||
108 | $ "$check_code" ./is-op.py |
|
108 | $ "$check_code" ./is-op.py | |
109 | ./is-op.py:3: |
|
109 | ./is-op.py:3: | |
110 | > y = x is 'foo' |
|
110 | > y = x is 'foo' | |
111 | object comparison with literal |
|
111 | object comparison with literal | |
112 | ./is-op.py:4: |
|
112 | ./is-op.py:4: | |
113 | > y = x is "foo" |
|
113 | > y = x is "foo" | |
114 | object comparison with literal |
|
114 | object comparison with literal | |
115 | ./is-op.py:5: |
|
115 | ./is-op.py:5: | |
116 | > y = x is 5346 |
|
116 | > y = x is 5346 | |
117 | object comparison with literal |
|
117 | object comparison with literal | |
118 | ./is-op.py:6: |
|
118 | ./is-op.py:6: | |
119 | > y = x is -6 |
|
119 | > y = x is -6 | |
120 | object comparison with literal |
|
120 | object comparison with literal | |
121 | ./is-op.py:7: |
|
121 | ./is-op.py:7: | |
122 | > y = x is not 'foo' |
|
122 | > y = x is not 'foo' | |
123 | object comparison with literal |
|
123 | object comparison with literal | |
124 | ./is-op.py:8: |
|
124 | ./is-op.py:8: | |
125 | > y = x is not "foo" |
|
125 | > y = x is not "foo" | |
126 | object comparison with literal |
|
126 | object comparison with literal | |
127 | ./is-op.py:9: |
|
127 | ./is-op.py:9: | |
128 | > y = x is not 5346 |
|
128 | > y = x is not 5346 | |
129 | object comparison with literal |
|
129 | object comparison with literal | |
130 | ./is-op.py:10: |
|
130 | ./is-op.py:10: | |
131 | > y = x is not -6 |
|
131 | > y = x is not -6 | |
132 | object comparison with literal |
|
132 | object comparison with literal | |
133 | [1] |
|
133 | [1] | |
134 |
|
134 | |||
135 | $ cat > warning.py <<EOF |
|
135 | $ cat > warning.py <<EOF | |
136 | > except: |
|
136 | > except: | |
137 | > EOF |
|
137 | > EOF | |
138 | $ "$check_code" warning.py --warning --nolineno |
|
138 | $ "$check_code" warning.py --warning --nolineno | |
139 | warning.py:0: |
|
139 | warning.py:0: | |
140 | > except: |
|
140 | > except: | |
141 | warning: naked except clause |
|
141 | warning: naked except clause | |
142 | [1] |
|
142 | [1] |
General Comments 0
You need to be logged in to leave comments.
Login now