Strip netrc hack, rewrite file.

Strip the netrc hack; apparently it only works on some versions of
Python, and the bots have many different versions.

Instead, we will actually emit the modified netrc as a new file without
comments or newlines, then read that directly from the vanilla netrc
module.

BUG=chromium:664664
TEST=None

Change-Id: I245759e2eae4e6ddcdffa0aaef59866a62bd0240
Reviewed-on: https://chromium-review.googlesource.com/411416
Commit-Queue: Daniel Jacques <dnj@chromium.org>
Reviewed-by: Ryan Tseng <hinoka@chromium.org>
Reviewed-by: Michael Moss <mmoss@chromium.org>
diff --git a/gerrit_util.py b/gerrit_util.py
index ff37858..eff98e3 100755
--- a/gerrit_util.py
+++ b/gerrit_util.py
@@ -9,6 +9,7 @@
 """
 
 import base64
+import contextlib
 import cookielib
 import httplib
 import json
@@ -16,14 +17,17 @@
 import netrc
 import os
 import re
+import shutil
 import socket
 import stat
 import sys
+import tempfile
 import time
 import urllib
 import urlparse
 from cStringIO import StringIO
 
+import gclient_utils
 
 LOGGER = logging.getLogger()
 TRY_LIMIT = 5
@@ -34,21 +38,6 @@
 GERRIT_PROTOCOL = 'https'
 
 
-# Processing comments in "netrc" can trigger a bug in Windows.
-# See crbug.com/664664
-class safeNetrc(netrc.netrc):
-  # pylint: disable=redefined-builtin
-  def __init__(self, file=None):
-    self._orig_parse, self._parse = self._parse, self._safe_parse
-    netrc.netrc.__init__(self, file=file)
-
-  # pylint: disable=redefined-builtin
-  def _safe_parse(self, file, fp, default_netrc):
-    # Buffer the file.
-    sio = StringIO(''.join(l for l in fp
-                           if l.strip() and not l.strip().startswith('#')))
-    return self._orig_parse(file, sio, default_netrc)
-
 
 class GerritError(Exception):
   """Exception class for errors commuicating with the gerrit-on-borg service."""
@@ -129,27 +118,41 @@
 
   @classmethod
   def _get_netrc(cls):
+    # Buffer the '.netrc' path. Use an empty file if it doesn't exist.
     path = cls.get_netrc_path()
-    if not os.path.exists(path):
-      return safeNetrc(os.devnull)
-
-    try:
-      return safeNetrc(path)
-    except IOError:
-      print >> sys.stderr, 'WARNING: Could not read netrc file %s' % path
-      return safeNetrc(os.devnull)
-    except netrc.NetrcParseError:
+    content = ''
+    if os.path.exists(path):
       st = os.stat(path)
       if st.st_mode & (stat.S_IRWXG | stat.S_IRWXO):
         print >> sys.stderr, (
             'WARNING: netrc file %s cannot be used because its file '
             'permissions are insecure.  netrc file permissions should be '
             '600.' % path)
-      else:
-        print >> sys.stderr, ('ERROR: Cannot use netrc file %s due to a '
-                              'parsing error.' % path)
-        raise
-      return safeNetrc(os.devnull)
+      with open(path) as fd:
+        content = fd.read()
+
+    # Load the '.netrc' file. We strip comments from it because processing them
+    # can trigger a bug in Windows. See crbug.com/664664.
+    content = '\n'.join(l for l in content.splitlines()
+                        if l.strip() and not l.strip().startswith('#'))
+    with tempdir() as tdir:
+      netrc_path = os.path.join(tdir, 'netrc')
+      with open(netrc_path, 'w') as fd:
+        fd.write(content)
+      os.chmod(netrc_path, (stat.S_IRUSR | stat.S_IWUSR))
+      return cls._get_netrc_from_path(netrc_path)
+
+  @classmethod
+  def _get_netrc_from_path(cls, path):
+    try:
+      return netrc.netrc(path)
+    except IOError:
+      print >> sys.stderr, 'WARNING: Could not read netrc file %s' % path
+      return netrc.netrc(os.devnull)
+    except netrc.NetrcParseError as e:
+      print >> sys.stderr, ('ERROR: Cannot use netrc file %s due to a '
+                            'parsing error: %s' % (path, e))
+      return netrc.netrc(os.devnull)
 
   @classmethod
   def get_gitcookies_path(cls):
@@ -749,3 +752,14 @@
   elif jmsg[0]['current_revision'] != revision:
     raise GerritError(200, 'While resetting labels on change "%s", '
                    'a new patchset was uploaded.' % change)
+
+
+@contextlib.contextmanager
+def tempdir():
+  tdir = None
+  try:
+    tdir = tempfile.mkdtemp(suffix='gerrit_util')
+    yield tdir
+  finally:
+    if tdir:
+      gclient_utils.rmtree(tdir)