D231: httppeer: add support for httppostargs when we're sending a file

2017-08-21 Thread yuja (Yuya Nishihara)
yuja added inline comments.

INLINE COMMENTS

> httppeer.py:105
> +def read(self, amt=None):
> +if amt <= 0:
> +return ''.join(f.read() for f in self._fileobjs)

Just nits:

- `read(0)` should return an empty string.
- `None <= 0` is TypeError on Python 3.

> httppeer.py:223
> +argsio.length = len(strargs)
> +data = _multifile(argsio, data)
> +headers['X-HgArgs-Post'] = len(strargs)

Does this mean `data` must have `length` attribute if it isn't
a string? A plain file object has no such attribute.

REPOSITORY
  rHG Mercurial

REVISION DETAIL
  https://phab.mercurial-scm.org/D231

To: durin42, #hg-reviewers, martinvonz
Cc: yuja, martinvonz, mercurial-devel
___
Mercurial-devel mailing list
Mercurial-devel@mercurial-scm.org
https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel


D231: httppeer: add support for httppostargs when we're sending a file

2017-08-15 Thread durin42 (Augie Fackler)
This revision was automatically updated to reflect the committed changes.
Closed by commit rHG3c91cc0c5fde: httppeer: add support for httppostargs when 
we're sending a file (authored by durin42).

REPOSITORY
  rHG Mercurial

CHANGES SINCE LAST UPDATE
  https://phab.mercurial-scm.org/D231?vs=745=949

REVISION DETAIL
  https://phab.mercurial-scm.org/D231

AFFECTED FILES
  mercurial/hgweb/protocol.py
  mercurial/httppeer.py

CHANGE DETAILS

diff --git a/mercurial/httppeer.py b/mercurial/httppeer.py
--- a/mercurial/httppeer.py
+++ b/mercurial/httppeer.py
@@ -9,6 +9,7 @@
 from __future__ import absolute_import
 
 import errno
+import io
 import os
 import socket
 import struct
@@ -86,6 +87,45 @@
 
 resp.__class__ = readerproxy
 
+class _multifile(object):
+def __init__(self, *fileobjs):
+for f in fileobjs:
+if not util.safehasattr(f, 'length'):
+raise ValueError(
+'_multifile only supports file objects that '
+'have a length but this one does not:', type(f), f)
+self._fileobjs = fileobjs
+self._index = 0
+
+@property
+def length(self):
+return sum(f.length for f in self._fileobjs)
+
+def read(self, amt=None):
+if amt <= 0:
+return ''.join(f.read() for f in self._fileobjs)
+parts = []
+while amt and self._index < len(self._fileobjs):
+parts.append(self._fileobjs[self._index].read(amt))
+got = len(parts[-1])
+if got < amt:
+self._index += 1
+amt -= got
+return ''.join(parts)
+
+def seek(self, offset, whence=os.SEEK_SET):
+if whence != os.SEEK_SET:
+raise NotImplementedError(
+'_multifile does not support anything other'
+' than os.SEEK_SET for whence on seek()')
+if offset != 0:
+raise NotImplementedError(
+'_multifile only supports seeking to start, but that '
+'could be fixed if you need it')
+for f in self._fileobjs:
+f.seek(0)
+self._index = 0
+
 class httppeer(wireproto.wirepeer):
 def __init__(self, ui, path):
 self._path = path
@@ -169,17 +209,19 @@
 # with infinite recursion when trying to look up capabilities
 # for the first time.
 postargsok = self._caps is not None and 'httppostargs' in self._caps
-# TODO: support for httppostargs when data is a file-like
-# object rather than a basestring
-canmungedata = not data or isinstance(data, basestring)
-if postargsok and canmungedata:
+if postargsok and args:
 strargs = urlreq.urlencode(sorted(args.items()))
-if strargs:
-if not data:
-data = strargs
-elif isinstance(data, basestring):
-data = strargs + data
-headers['X-HgArgs-Post'] = len(strargs)
+if not data:
+data = strargs
+else:
+if isinstance(data, basestring):
+i = io.BytesIO(data)
+i.length = len(data)
+data = i
+argsio = io.BytesIO(strargs)
+argsio.length = len(strargs)
+data = _multifile(argsio, data)
+headers['X-HgArgs-Post'] = len(strargs)
 else:
 if len(args) > 0:
 httpheader = self.capable('httpheader')
diff --git a/mercurial/hgweb/protocol.py b/mercurial/hgweb/protocol.py
--- a/mercurial/hgweb/protocol.py
+++ b/mercurial/hgweb/protocol.py
@@ -75,6 +75,9 @@
 return args
 def getfile(self, fp):
 length = int(self.req.env['CONTENT_LENGTH'])
+# If httppostargs is used, we need to read Content-Length
+# minus the amount that was consumed by args.
+length -= int(self.req.env.get('HTTP_X_HGARGS_POST', 0))
 for s in util.filechunkiter(self.req, limit=length):
 fp.write(s)
 def redirect(self):



To: durin42, #hg-reviewers, martinvonz
Cc: martinvonz, mercurial-devel
___
Mercurial-devel mailing list
Mercurial-devel@mercurial-scm.org
https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel


D231: httppeer: add support for httppostargs when we're sending a file

2017-08-08 Thread durin42 (Augie Fackler)
durin42 added inline comments.

INLINE COMMENTS

> martinvonz wrote in httppeer.py:96
> It doesn't matter much since it's just a programming error if it happens, but 
> how will these arguments to ValueError be rendered?

ValueError: ('_multifile only supports file objects that have a length but this 
one does not:', , )

It ain't pretty, but it's got all the information we need.

> martinvonz wrote in httppeer.py:111
> nit: i think this can be "if got <= amt" to avoid an unnecessary 0-length 
> read the next time _multifile.read() (and it does look like that will be the 
> normal case, that the reader will read exactly the size of the first "file" 
> first)

No, because consider these file buffers:

'foo', 'bar'

if we read(2), we'll pull 2 bytes off the first buffer, which is not yet 
exhausted, but got == amt.

REPOSITORY
  rHG Mercurial

REVISION DETAIL
  https://phab.mercurial-scm.org/D231

To: durin42, #hg-reviewers
Cc: martinvonz, mercurial-devel
___
Mercurial-devel mailing list
Mercurial-devel@mercurial-scm.org
https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel


D231: httppeer: add support for httppostargs when we're sending a file

2017-08-08 Thread durin42 (Augie Fackler)
durin42 updated this revision to Diff 627.
durin42 marked 3 inline comments as done.

REPOSITORY
  rHG Mercurial

CHANGES SINCE LAST UPDATE
  https://phab.mercurial-scm.org/D231?vs=559=627

REVISION DETAIL
  https://phab.mercurial-scm.org/D231

AFFECTED FILES
  mercurial/hgweb/protocol.py
  mercurial/httppeer.py

CHANGE DETAILS

diff --git a/mercurial/httppeer.py b/mercurial/httppeer.py
--- a/mercurial/httppeer.py
+++ b/mercurial/httppeer.py
@@ -9,6 +9,7 @@
 from __future__ import absolute_import
 
 import errno
+import io
 import os
 import socket
 import struct
@@ -86,6 +87,45 @@
 
 resp.__class__ = readerproxy
 
+class _multifile(object):
+def __init__(self, *fileobjs):
+for f in fileobjs:
+if not util.safehasattr(f, 'length'):
+raise ValueError(
+'_multifile only supports file objects that '
+'have a length but this one does not:', type(f), f)
+self._fileobjs = fileobjs
+self._index = 0
+
+@property
+def length(self):
+return sum(f.length for f in self._fileobjs)
+
+def read(self, amt=None):
+if amt <= 0:
+return ''.join(f.read() for f in self._fileobjs)
+parts = []
+while amt and self._index < len(self._fileobjs):
+parts.append(self._fileobjs[self._index].read(amt))
+got = len(parts[-1])
+if got < amt:
+self._index += 1
+amt -= got
+return ''.join(parts)
+
+def seek(self, offset, whence=os.SEEK_SET):
+if whence != os.SEEK_SET:
+raise NotImplementedError(
+'_multifile does not support anything other'
+' than os.SEEK_SET for whence on seek()')
+if offset != 0:
+raise NotImplementedError(
+'_multifile only supports seeking to start, but that '
+'could be fixed if you need it')
+for f in self._fileobjs:
+f.seek(0)
+self._index = 0
+
 class httppeer(wireproto.wirepeer):
 def __init__(self, ui, path):
 self.path = path
@@ -149,17 +189,19 @@
 # with infinite recursion when trying to look up capabilities
 # for the first time.
 postargsok = self.caps is not None and 'httppostargs' in self.caps
-# TODO: support for httppostargs when data is a file-like
-# object rather than a basestring
-canmungedata = not data or isinstance(data, basestring)
-if postargsok and canmungedata:
+if postargsok and args:
 strargs = urlreq.urlencode(sorted(args.items()))
-if strargs:
-if not data:
-data = strargs
-elif isinstance(data, basestring):
-data = strargs + data
-headers['X-HgArgs-Post'] = len(strargs)
+if not data:
+data = strargs
+else:
+if isinstance(data, basestring):
+i = io.BytesIO(data)
+i.length = len(data)
+data = i
+argsio = io.BytesIO(strargs)
+argsio.length = len(strargs)
+data = _multifile(argsio, data)
+headers['X-HgArgs-Post'] = len(strargs)
 else:
 if len(args) > 0:
 httpheader = self.capable('httpheader')
diff --git a/mercurial/hgweb/protocol.py b/mercurial/hgweb/protocol.py
--- a/mercurial/hgweb/protocol.py
+++ b/mercurial/hgweb/protocol.py
@@ -75,6 +75,9 @@
 return args
 def getfile(self, fp):
 length = int(self.req.env['CONTENT_LENGTH'])
+# If httppostargs is used, we need to read Content-Length
+# minus the amount that was consumed by args.
+length -= int(self.req.env.get('HTTP_X_HGARGS_POST', 0))
 for s in util.filechunkiter(self.req, limit=length):
 fp.write(s)
 def redirect(self):



To: durin42, #hg-reviewers
Cc: martinvonz, mercurial-devel
___
Mercurial-devel mailing list
Mercurial-devel@mercurial-scm.org
https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel


D231: httppeer: add support for httppostargs when we're sending a file

2017-08-04 Thread martinvonz (Martin von Zweigbergk)
martinvonz added inline comments.

INLINE COMMENTS

> httppeer.py:95
> +raise ValueError(
> +'fileprepender only supports file objects that '
> +'have a length but this one does not:', type(f), f)

I'm guessing 'fileprepender' here and a few placed below is the old name for 
'_multifile' (so please update)

> httppeer.py:96
> +'fileprepender only supports file objects that '
> +'have a length but this one does not:', type(f), f)
> +self._fileobjs = fileobjs

It doesn't matter much since it's just a programming error if it happens, but 
how will these arguments to ValueError be rendered?

> httppeer.py:105
> +def read(self, amt=None):
> +if not amt:
> +return ''.join(f.read() for f in self._fileobjs)

i read that negative amount means the same thing (read all). do we care to 
follow that contract here?

> httppeer.py:111
> +got = len(parts[-1])
> +if got < amt:
> +self._index += 1

nit: i think this can be "if got <= amt" to avoid an unnecessary 0-length read 
the next time _multifile.read() (and it does look like that will be the normal 
case, that the reader will read exactly the size of the first "file" first)

> httppeer.py:125
> +'could be fixed if you need it')
> +for f in self._fileobjs:
> +f.seek(0)

also need to set self._index=0, no?

> httppeer.py:193
>  strargs = urlreq.urlencode(sorted(args.items()))
>  if strargs:
>  if not data:

Nit: to reduce indentation, it looks like you can change the condition above to 
"if postargsok and args" instead (because bool(strargs) == bool(args) AFAICT)

REPOSITORY
  rHG Mercurial

REVISION DETAIL
  https://phab.mercurial-scm.org/D231

To: durin42, #hg-reviewers
Cc: martinvonz, mercurial-devel
___
Mercurial-devel mailing list
Mercurial-devel@mercurial-scm.org
https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel


D231: httppeer: add support for httppostargs when we're sending a file

2017-08-04 Thread durin42 (Augie Fackler)
durin42 created this revision.
Herald added a subscriber: mercurial-devel.
Herald added a reviewer: hg-reviewers.

REVISION SUMMARY
  This is probably only used in the 'unbundle' command, but the code
  ended up being cleaner to make it generic and treat *all* httppostargs
  with a non-args request body as though they were file-like in
  nature. It also means we get test coverage more or less for free. A
  previous version of this change didn't use io.BytesIO, and it was a
  lot more complicated.
  
  This also fixes a server-side bug, so anyone using httppostargs should
  update all of their servers to this revision or later *before* this
  gets to their clients, otherwise servers will hang trying to over-read
  the POST body.

REPOSITORY
  rHG Mercurial

REVISION DETAIL
  https://phab.mercurial-scm.org/D231

AFFECTED FILES
  mercurial/hgweb/protocol.py
  mercurial/httppeer.py

CHANGE DETAILS

diff --git a/mercurial/httppeer.py b/mercurial/httppeer.py
--- a/mercurial/httppeer.py
+++ b/mercurial/httppeer.py
@@ -9,6 +9,7 @@
 from __future__ import absolute_import
 
 import errno
+import io
 import os
 import socket
 import struct
@@ -86,6 +87,44 @@
 
 resp.__class__ = readerproxy
 
+class _multifile(object):
+def __init__(self, *fileobjs):
+for f in fileobjs:
+if not util.safehasattr(f, 'length'):
+raise ValueError(
+'fileprepender only supports file objects that '
+'have a length but this one does not:', type(f), f)
+self._fileobjs = fileobjs
+self._index = 0
+
+@property
+def length(self):
+return sum(f.length for f in self._fileobjs)
+
+def read(self, amt=None):
+if not amt:
+return ''.join(f.read() for f in self._fileobjs)
+parts = []
+while amt and self._index < len(self._fileobjs):
+parts.append(self._fileobjs[self._index].read(amt))
+got = len(parts[-1])
+if got < amt:
+self._index += 1
+amt -= got
+return ''.join(parts)
+
+def seek(self, offset, whence=os.SEEK_SET):
+if whence != os.SEEK_SET:
+raise NotImplementedError(
+'fileprepender does not support anything other'
+' than os.SEEK_SET for whence on seek()')
+if offset != 0:
+raise NotImplementedError(
+'fileprepender only supports seeking to start, but that '
+'could be fixed if you need it')
+for f in self._fileobjs:
+f.seek(0)
+
 class httppeer(wireproto.wirepeer):
 def __init__(self, ui, path):
 self.path = path
@@ -149,16 +188,19 @@
 # with infinite recursion when trying to look up capabilities
 # for the first time.
 postargsok = self.caps is not None and 'httppostargs' in self.caps
-# TODO: support for httppostargs when data is a file-like
-# object rather than a basestring
-canmungedata = not data or isinstance(data, basestring)
-if postargsok and canmungedata:
+if postargsok:
 strargs = urlreq.urlencode(sorted(args.items()))
 if strargs:
 if not data:
 data = strargs
-elif isinstance(data, basestring):
-data = strargs + data
+else:
+if isinstance(data, basestring):
+i = io.BytesIO(data)
+i.length = len(data)
+data = i
+argsio = io.BytesIO(strargs)
+argsio.length = len(strargs)
+data = _multifile(argsio, data)
 headers['X-HgArgs-Post'] = len(strargs)
 else:
 if len(args) > 0:
diff --git a/mercurial/hgweb/protocol.py b/mercurial/hgweb/protocol.py
--- a/mercurial/hgweb/protocol.py
+++ b/mercurial/hgweb/protocol.py
@@ -75,6 +75,9 @@
 return args
 def getfile(self, fp):
 length = int(self.req.env['CONTENT_LENGTH'])
+# If httppostargs is used, we need to read Content-Length
+# minus the amount that was consumed by args.
+length -= int(self.req.env.get('HTTP_X_HGARGS_POST', 0))
 for s in util.filechunkiter(self.req, limit=length):
 fp.write(s)
 def redirect(self):



To: durin42, #hg-reviewers
Cc: mercurial-devel
___
Mercurial-devel mailing list
Mercurial-devel@mercurial-scm.org
https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel