mercurial/hgweb/request.py
changeset 36862 ec0af9c59270
parent 36861 a88d68dc3ee8
child 36866 cf69df7ea385
equal deleted inserted replaced
36861:a88d68dc3ee8 36862:ec0af9c59270
    25 from .. import (
    25 from .. import (
    26     error,
    26     error,
    27     pycompat,
    27     pycompat,
    28     util,
    28     util,
    29 )
    29 )
       
    30 
       
    31 class multidict(object):
       
    32     """A dict like object that can store multiple values for a key.
       
    33 
       
    34     Used to store parsed request parameters.
       
    35 
       
    36     This is inspired by WebOb's class of the same name.
       
    37     """
       
    38     def __init__(self):
       
    39         # Stores (key, value) 2-tuples. This isn't the most efficient. But we
       
    40         # don't rely on parameters that much, so it shouldn't be a perf issue.
       
    41         # we can always add dict for fast lookups.
       
    42         self._items = []
       
    43 
       
    44     def __getitem__(self, key):
       
    45         """Returns the last set value for a key."""
       
    46         for k, v in reversed(self._items):
       
    47             if k == key:
       
    48                 return v
       
    49 
       
    50         raise KeyError(key)
       
    51 
       
    52     def __setitem__(self, key, value):
       
    53         """Replace a values for a key with a new value."""
       
    54         try:
       
    55             del self[key]
       
    56         except KeyError:
       
    57             pass
       
    58 
       
    59         self._items.append((key, value))
       
    60 
       
    61     def __delitem__(self, key):
       
    62         """Delete all values for a key."""
       
    63         oldlen = len(self._items)
       
    64 
       
    65         self._items[:] = [(k, v) for k, v in self._items if k != key]
       
    66 
       
    67         if oldlen == len(self._items):
       
    68             raise KeyError(key)
       
    69 
       
    70     def __contains__(self, key):
       
    71         return any(k == key for k, v in self._items)
       
    72 
       
    73     def __len__(self):
       
    74         return len(self._items)
       
    75 
       
    76     def get(self, key, default=None):
       
    77         try:
       
    78             return self.__getitem__(key)
       
    79         except KeyError:
       
    80             return default
       
    81 
       
    82     def add(self, key, value):
       
    83         """Add a new value for a key. Does not replace existing values."""
       
    84         self._items.append((key, value))
       
    85 
       
    86     def getall(self, key):
       
    87         """Obtains all values for a key."""
       
    88         return [v for k, v in self._items if k == key]
       
    89 
       
    90     def getone(self, key):
       
    91         """Obtain a single value for a key.
       
    92 
       
    93         Raises KeyError if key not defined or it has multiple values set.
       
    94         """
       
    95         vals = self.getall(key)
       
    96 
       
    97         if not vals:
       
    98             raise KeyError(key)
       
    99 
       
   100         if len(vals) > 1:
       
   101             raise KeyError('multiple values for %r' % key)
       
   102 
       
   103         return vals[0]
       
   104 
       
   105     def asdictoflists(self):
       
   106         d = {}
       
   107         for k, v in self._items:
       
   108             if k in d:
       
   109                 d[k].append(v)
       
   110             else:
       
   111                 d[k] = [v]
       
   112 
       
   113         return d
    30 
   114 
    31 @attr.s(frozen=True)
   115 @attr.s(frozen=True)
    32 class parsedrequest(object):
   116 class parsedrequest(object):
    33     """Represents a parsed WSGI request.
   117     """Represents a parsed WSGI request.
    34 
   118 
    54     # Whether there is a path component to this request. This can be true
   138     # Whether there is a path component to this request. This can be true
    55     # when ``dispatchpath`` is empty due to REPO_NAME muckery.
   139     # when ``dispatchpath`` is empty due to REPO_NAME muckery.
    56     havepathinfo = attr.ib()
   140     havepathinfo = attr.ib()
    57     # Raw query string (part after "?" in URL).
   141     # Raw query string (part after "?" in URL).
    58     querystring = attr.ib()
   142     querystring = attr.ib()
    59     # List of 2-tuples of query string arguments.
   143     # multidict of query string parameters.
    60     querystringlist = attr.ib()
   144     qsparams = attr.ib()
    61     # Dict of query string arguments. Values are lists with at least 1 item.
       
    62     querystringdict = attr.ib()
       
    63     # wsgiref.headers.Headers instance. Operates like a dict with case
   145     # wsgiref.headers.Headers instance. Operates like a dict with case
    64     # insensitive keys.
   146     # insensitive keys.
    65     headers = attr.ib()
   147     headers = attr.ib()
    66     # Request body input stream.
   148     # Request body input stream.
    67     bodyfh = attr.ib()
   149     bodyfh = attr.ib()
   155 
   237 
   156     querystring = env.get('QUERY_STRING', '')
   238     querystring = env.get('QUERY_STRING', '')
   157 
   239 
   158     # We store as a list so we have ordering information. We also store as
   240     # We store as a list so we have ordering information. We also store as
   159     # a dict to facilitate fast lookup.
   241     # a dict to facilitate fast lookup.
   160     querystringlist = util.urlreq.parseqsl(querystring, keep_blank_values=True)
   242     qsparams = multidict()
   161 
   243     for k, v in util.urlreq.parseqsl(querystring, keep_blank_values=True):
   162     querystringdict = {}
   244         qsparams.add(k, v)
   163     for k, v in querystringlist:
       
   164         if k in querystringdict:
       
   165             querystringdict[k].append(v)
       
   166         else:
       
   167             querystringdict[k] = [v]
       
   168 
   245 
   169     # HTTP_* keys contain HTTP request headers. The Headers structure should
   246     # HTTP_* keys contain HTTP request headers. The Headers structure should
   170     # perform case normalization for us. We just rewrite underscore to dash
   247     # perform case normalization for us. We just rewrite underscore to dash
   171     # so keys match what likely went over the wire.
   248     # so keys match what likely went over the wire.
   172     headers = []
   249     headers = []
   195                          advertisedbaseurl=advertisedbaseurl,
   272                          advertisedbaseurl=advertisedbaseurl,
   196                          apppath=apppath,
   273                          apppath=apppath,
   197                          dispatchparts=dispatchparts, dispatchpath=dispatchpath,
   274                          dispatchparts=dispatchparts, dispatchpath=dispatchpath,
   198                          havepathinfo='PATH_INFO' in env,
   275                          havepathinfo='PATH_INFO' in env,
   199                          querystring=querystring,
   276                          querystring=querystring,
   200                          querystringlist=querystringlist,
   277                          qsparams=qsparams,
   201                          querystringdict=querystringdict,
       
   202                          headers=headers,
   278                          headers=headers,
   203                          bodyfh=bodyfh)
   279                          bodyfh=bodyfh)
   204 
   280 
   205 class wsgiresponse(object):
   281 class wsgiresponse(object):
   206     """Represents a response to a WSGI request.
   282     """Represents a response to a WSGI request.
   348         self.threaded = wsgienv[r'wsgi.multithread']
   424         self.threaded = wsgienv[r'wsgi.multithread']
   349         self.multiprocess = wsgienv[r'wsgi.multiprocess']
   425         self.multiprocess = wsgienv[r'wsgi.multiprocess']
   350         self.run_once = wsgienv[r'wsgi.run_once']
   426         self.run_once = wsgienv[r'wsgi.run_once']
   351         self.env = wsgienv
   427         self.env = wsgienv
   352         self.req = parserequestfromenv(wsgienv, inp)
   428         self.req = parserequestfromenv(wsgienv, inp)
   353         self.form = self.req.querystringdict
   429         self.form = self.req.qsparams.asdictoflists()
   354         self.res = wsgiresponse(self.req, start_response)
   430         self.res = wsgiresponse(self.req, start_response)
   355         self._start_response = start_response
   431         self._start_response = start_response
   356         self.server_write = None
   432         self.server_write = None
   357         self.headers = []
   433         self.headers = []
   358 
   434