##// END OF EJS Templates
bundle2: refuse empty parameter name...
Pierre-Yves David -
r20813:8c74b3ce default
parent child Browse files
Show More
@@ -1,178 +1,181 b''
1 1 # bundle2.py - generic container format to transmit arbitrary data.
2 2 #
3 3 # Copyright 2013 Facebook, Inc.
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7 """Handling of the new bundle2 format
8 8
9 9 The goal of bundle2 is to act as an atomically packet to transmit a set of
10 10 payloads in an application agnostic way. It consist in a sequence of "parts"
11 11 that will be handed to and processed by the application layer.
12 12
13 13
14 14 General format architecture
15 15 ===========================
16 16
17 17 The format is architectured as follow
18 18
19 19 - magic string
20 20 - stream level parameters
21 21 - payload parts (any number)
22 22 - end of stream marker.
23 23
24 24 The current implementation accept some stream level option but no part.
25 25
26 26 Details on the Binary format
27 27 ============================
28 28
29 29 All numbers are unsigned and big endian.
30 30
31 31 stream level parameters
32 32 ------------------------
33 33
34 34 Binary format is as follow
35 35
36 36 :params size: (16 bits integer)
37 37
38 38 The total number of Bytes used by the parameters
39 39
40 40 :params value: arbitrary number of Bytes
41 41
42 42 A blob of `params size` containing the serialized version of all stream level
43 43 parameters.
44 44
45 45 The blob contains a space separated list of parameters. parameter with value
46 46 are stored in the form `<name>=<value>`. Both name and value are urlquoted.
47 47
48 Empty name are obviously forbidden.
49
48 50 Stream parameters use a simple textual format for two main reasons:
49 51
50 52 - Stream level parameters should remains simple and we want to discourage any
51 53 crazy usage.
52 54 - Textual data allow easy human inspection of a the bundle2 header in case of
53 55 troubles.
54 56
55 57 Any Applicative level options MUST go into a bundle2 part instead.
56 58
57
58 59 Payload part
59 60 ------------------------
60 61
61 62 Binary format is as follow
62 63
63 64 :header size: (16 bits inter)
64 65
65 66 The total number of Bytes used by the part headers. When the header is empty
66 67 (size = 0) this is interpreted as the end of stream marker.
67 68
68 69 Currently forced to 0 in the current state of the implementation
69 70 """
70 71
71 72 import util
72 73 import struct
73 74 import urllib
74 75
75 76 import changegroup
76 77 from i18n import _
77 78
78 79 _pack = struct.pack
79 80 _unpack = struct.unpack
80 81
81 82 _magicstring = 'HG20'
82 83
83 84 _fstreamparamsize = '>H'
84 85
85 86 class bundle20(object):
86 87 """represent an outgoing bundle2 container
87 88
88 89 Use the `addparam` method to add stream level parameter. Then call
89 90 `getchunks` to retrieve all the binary chunks of datathat compose the
90 91 bundle2 container.
91 92
92 93 This object does not support payload part yet."""
93 94
94 95 def __init__(self):
95 96 self._params = []
96 97 self._parts = []
97 98
98 99 def addparam(self, name, value=None):
99 100 """add a stream level parameter"""
101 if not name:
102 raise ValueError('empty parameter name')
100 103 self._params.append((name, value))
101 104
102 105 def getchunks(self):
103 106 yield _magicstring
104 107 param = self._paramchunk()
105 108 yield _pack(_fstreamparamsize, len(param))
106 109 if param:
107 110 yield param
108 111
109 112 # no support for parts
110 113 # to be obviously fixed soon.
111 114 assert not self._parts
112 115 yield '\0\0'
113 116
114 117 def _paramchunk(self):
115 118 """return a encoded version of all stream parameters"""
116 119 blocks = []
117 120 for par, value in self._params:
118 121 par = urllib.quote(par)
119 122 if value is not None:
120 123 value = urllib.quote(value)
121 124 par = '%s=%s' % (par, value)
122 125 blocks.append(par)
123 126 return ' '.join(blocks)
124 127
125 128 class unbundle20(object):
126 129 """interpret a bundle2 stream
127 130
128 131 (this will eventually yield parts)"""
129 132
130 133 def __init__(self, fp):
131 134 self._fp = fp
132 135 header = self._readexact(4)
133 136 magic, version = header[0:2], header[2:4]
134 137 if magic != 'HG':
135 138 raise util.Abort(_('not a Mercurial bundle'))
136 139 if version != '20':
137 140 raise util.Abort(_('unknown bundle version %s') % version)
138 141
139 142 def _unpack(self, format):
140 143 """unpack this struct format from the stream"""
141 144 data = self._readexact(struct.calcsize(format))
142 145 return _unpack(format, data)
143 146
144 147 def _readexact(self, size):
145 148 """read exactly <size> bytes from the stream"""
146 149 return changegroup.readexactly(self._fp, size)
147 150
148 151 @util.propertycache
149 152 def params(self):
150 153 """dictionnary of stream level parameters"""
151 154 params = {}
152 155 paramssize = self._unpack(_fstreamparamsize)[0]
153 156 if paramssize:
154 157 for p in self._readexact(paramssize).split(' '):
155 158 p = p.split('=', 1)
156 159 p = [urllib.unquote(i) for i in p]
157 160 if len(p) < 2:
158 161 p.append(None)
159 162 params[p[0]] = p[1]
160 163 return params
161 164
162 165 def __iter__(self):
163 166 """yield all parts contained in the stream"""
164 167 # make sure param have been loaded
165 168 self.params
166 169 part = self._readpart()
167 170 while part is not None:
168 171 yield part
169 172 part = self._readpart()
170 173
171 174 def _readpart(self):
172 175 """return None when an end of stream markers is reach"""
173 176 headersize = self._readexact(2)
174 177 assert headersize == '\0\0'
175 178 return None
176 179
177 180
178 181
@@ -1,151 +1,164 b''
1 1
2 2 Create an extension to test bundle2 API
3 3
4 4 $ cat > bundle2.py << EOF
5 5 > """A small extension to test bundle2 implementation
6 6 >
7 7 > Current bundle2 implementation is far too limited to be used in any core
8 8 > code. We still need to be able to test it while it grow up.
9 9 > """
10 10 >
11 11 > import sys
12 12 > from mercurial import cmdutil
13 > from mercurial import util
13 14 > from mercurial import bundle2
14 15 > cmdtable = {}
15 16 > command = cmdutil.command(cmdtable)
16 17 >
17 18 > @command('bundle2',
18 19 > [('', 'param', [], 'stream level parameter'),],
19 20 > '')
20 21 > def cmdbundle2(ui, repo, **opts):
21 22 > """write a bundle2 container on standard ouput"""
22 23 > bundler = bundle2.bundle20()
23 24 > for p in opts['param']:
24 25 > p = p.split('=', 1)
26 > try:
25 27 > bundler.addparam(*p)
28 > except ValueError, exc:
29 > raise util.Abort('%s' % exc)
26 30 >
27 31 > for chunk in bundler.getchunks():
28 32 > ui.write(chunk)
29 33 >
30 34 > @command('unbundle2', [], '')
31 35 > def cmdunbundle2(ui, repo):
32 36 > """read a bundle2 container from standard input"""
33 37 > unbundler = bundle2.unbundle20(sys.stdin)
34 38 > ui.write('options count: %i\n' % len(unbundler.params))
35 39 > for key in sorted(unbundler.params):
36 40 > ui.write('- %s\n' % key)
37 41 > value = unbundler.params[key]
38 42 > if value is not None:
39 43 > ui.write(' %s\n' % value)
40 44 > parts = list(unbundler)
41 45 > ui.write('parts count: %i\n' % len(parts))
42 46 > EOF
43 47 $ cat >> $HGRCPATH << EOF
44 48 > [extensions]
45 49 > bundle2=$TESTTMP/bundle2.py
46 50 > EOF
47 51
48 52 The extension requires a repo (currently unused)
49 53
50 54 $ hg init main
51 55 $ cd main
52 56 $ touch a
53 57 $ hg add a
54 58 $ hg commit -m 'a'
55 59
56 60
57 61 Empty bundle
58 62 =================
59 63
60 64 - no option
61 65 - no parts
62 66
63 67 Test bundling
64 68
65 69 $ hg bundle2
66 70 HG20\x00\x00\x00\x00 (no-eol) (esc)
67 71
68 72 Test unbundling
69 73
70 74 $ hg bundle2 | hg unbundle2
71 75 options count: 0
72 76 parts count: 0
73 77
74 78 Test old style bundle are detected and refused
75 79
76 80 $ hg bundle --all ../bundle.hg
77 81 1 changesets found
78 82 $ hg unbundle2 < ../bundle.hg
79 83 abort: unknown bundle version 10
80 84 [255]
81 85
82 86 Test parameters
83 87 =================
84 88
85 89 - some options
86 90 - no parts
87 91
88 92 advisory parameters, no value
89 93 -------------------------------
90 94
91 95 Simplest possible parameters form
92 96
93 97 Test generation simple option
94 98
95 99 $ hg bundle2 --param 'caution'
96 100 HG20\x00\x07caution\x00\x00 (no-eol) (esc)
97 101
98 102 Test unbundling
99 103
100 104 $ hg bundle2 --param 'caution' | hg unbundle2
101 105 options count: 1
102 106 - caution
103 107 parts count: 0
104 108
105 109 Test generation multiple option
106 110
107 111 $ hg bundle2 --param 'caution' --param 'meal'
108 112 HG20\x00\x0ccaution meal\x00\x00 (no-eol) (esc)
109 113
110 114 Test unbundling
111 115
112 116 $ hg bundle2 --param 'caution' --param 'meal' | hg unbundle2
113 117 options count: 2
114 118 - caution
115 119 - meal
116 120 parts count: 0
117 121
118 122 advisory parameters, with value
119 123 -------------------------------
120 124
121 125 Test generation
122 126
123 127 $ hg bundle2 --param 'caution' --param 'meal=vegan' --param 'elephants'
124 128 HG20\x00\x1ccaution meal=vegan elephants\x00\x00 (no-eol) (esc)
125 129
126 130 Test unbundling
127 131
128 132 $ hg bundle2 --param 'caution' --param 'meal=vegan' --param 'elephants' | hg unbundle2
129 133 options count: 3
130 134 - caution
131 135 - elephants
132 136 - meal
133 137 vegan
134 138 parts count: 0
135 139
136 140 parameter with special char in value
137 141 ---------------------------------------------------
138 142
139 143 Test generation
140 144
141 145 $ hg bundle2 --param 'e|! 7/=babar%#==tutu' --param simple
142 146 HG20\x00)e%7C%21%207/=babar%25%23%3D%3Dtutu simple\x00\x00 (no-eol) (esc)
143 147
144 148 Test unbundling
145 149
146 150 $ hg bundle2 --param 'e|! 7/=babar%#==tutu' --param simple | hg unbundle2
147 151 options count: 2
148 152 - e|! 7/
149 153 babar%#==tutu
150 154 - simple
151 155 parts count: 0
156
157 Test buggy input
158 ---------------------------------------------------
159
160 empty parameter name
161
162 $ hg bundle2 --param '' --quiet
163 abort: empty parameter name
164 [255]
General Comments 0
You need to be logged in to leave comments. Login now