SeattleOnAndroid: test_script.repy

File test_script.repy, 77.0 KB (added by yanyan, 6 years ago)
Line 
1"""
2o3gm_pickup.py -- Open3GMap Pickup (sensor frontend)
3
4Call with any call arg to make it browse for sensors/methods.
5
6Output file is "o3gm_readings.txt". Fields are separated by one space.
7To parse into Lukas' "daten.csv" format, use something like
8awk '{ print $3 "-" $4 "," $6 "," $9 "," $10 "," $7 ",data" }' o3gm_readings.txt
9
10Field names and descriptions:
11getruntime()...local vessel runtime since this script started
12timestamp......network or GPS timestamp in milliseconds, see loc_source
13mcc............mobile country code
14mnc............mobile network code
15cell_id........Cell ID (no less!)
16nw_type........GSM, GPRS, EDGE, UMTS, HS[,U,D]PA[,P]
17rssi...........Signal strength
18loc_source.....Source of location data, ['GPS'|'network']
19lat, lon.......Latitude and longitude acoording to loc_source
20alt............Altitude as per loc_source. loc_source=='network' => alt=-99999.0
21accuracy.......Location accuracy
22battery_level
23  .............Charge level in percent. We voluntarily stop if we reach <16%.
24tac, vendor, model
25   ............Data about the phone model
26
27
28
29Version history:
30
31v 0.0.4, 2012-11-02 0952 AR
32  Yet another XML-RPC interface update
33
34v 0.0.3, 2012-10-02 1607 AR
35  Yet another XMLRPC interface update
36
37v 0.0.2, 2012-10-02 0947 AR
38  SeattleSensors "lattitude" has proper spelling now.
39
40v 0.0.1, 2012-10-01 2209 AR
41  It's all comments, really!
42
43
44P.S.: Check out this fun 3G mapping stuff:
45http://www.erodocdb.dk/docs/doc98/official/pdf/ECCRep103.pdf
46"""
47
48#begin include xmlrpc_client.repy
49"""
50<Program Name>
51  xmlrpc_client.py
52
53<Started>
54  May 3, 2009
55
56<Author>
57  Michael Phan-Ba
58
59<Purpose>
60  Implements the client-side XML-RPC protocol.
61
62"""
63
64
65#begin include urlparse.repy
66"""
67<Program Name>
68  urlparse.repy
69
70<Started>
71  May 15, 2009
72
73<Author>
74  Michael Phan-Ba
75
76<Purpose>
77  Provides utilities for parsing URLs, based on the Python 2.6.1 module urlparse.
78
79"""
80
81
82def urlparse_urlsplit(urlstring, default_scheme="", allow_fragments=True):
83  """
84  <Purpose>
85    Parse a URL into five components, returning a dictionary.  This corresponds
86    to the general structure of a URL:
87    scheme://netloc/path;parameters?query#fragment.  The parameters are not
88    split from the URL and individual componenets are not separated.
89
90    Only absolute server-based URIs are currently supported (all URLs will be
91    parsed into the components listed, regardless of the scheme).
92
93  <Arguments>
94    default_scheme:
95      Optional: defaults to the empty string.  If specified, gives the default
96      addressing scheme, to be used only if the URL does not specify one.
97
98    allow_fragments:
99      Optional: defaults to True.  If False, fragment identifiers are not
100      allowed, even if the URL's addressing scheme normally does support them.
101
102  <Exceptions>
103    ValueError on parsing a non-numeric port value.
104
105  <Side Effects>
106    None.
107
108  <Returns>
109    A dictionary containing:
110
111    Key         Value                               Value if not present
112    ============================================================================
113    scheme      URL scheme specifier                empty string
114    netloc      Network location part               empty string
115    path        Hierarchical path                   empty string
116    query       Query component                     empty string
117    fragment    Fragment identifier                 empty string
118    username    User name                           None
119    password    Password                            None
120    hostname    Host name (lower case)              None
121    port        Port number as integer, if present  None
122
123  """
124
125  components = {"scheme": default_scheme, "netloc": "", "path": "", "query": "",
126    "fragment": "", "username": None, "password": None, "hostname": None,
127    "port": None }
128
129  # Extract the scheme, if present.
130  (lpart, rpart) = _urlparse_splitscheme(urlstring)
131  if lpart:
132    components["scheme"] = lpart
133
134  # Extract the server information, if present.
135  if rpart.startswith("//"):
136    (lpart, rpart) = _urlparse_splitnetloc(rpart, 2)
137    components["netloc"] = lpart
138
139    (components["username"], components["password"], components["hostname"],
140      components["port"]) = _urlparse_splitauthority(lpart)
141
142  # Extract the fragment.
143  if allow_fragments:
144    (rpart, components["fragment"]) = _urlparse_splitfragment(rpart)
145
146
147  # Extract the query.
148  (components["path"], components["query"]) = _urlparse_splitquery(rpart)
149
150  return components
151
152
153def _urlparse_splitscheme(url):
154  """Parse the scheme portion of the URL"""
155  # The scheme is valid only if it contains these characters.
156  scheme_chars = \
157    "abcdefghijklmnopqrstuvwxyz0123456789+-."
158
159  scheme = ""
160  rest = url
161
162  spart = url.split(":", 1)
163  if len(spart) == 2:
164
165    # Normalize the scheme.
166    spart[0] = spart[0].lower()
167
168    # A scheme is valid only if it starts with an alpha character.
169    if spart[0] and spart[0][0].isalpha():
170      for char in spart[0]:
171        if char not in scheme_chars:
172          break
173      (scheme, rest) = spart
174
175  return scheme, rest
176
177
178def _urlparse_splitnetloc(url, start=0):
179  """Parse the netloc portion of the URL"""
180
181  # By default, the netloc is delimited by the end of the URL.
182  delim = len(url)
183
184  # Find the left-most delimiter.
185  for char in "/?#":
186    xdelim = url.find(char, start)
187    if xdelim >= 0:
188      delim = min(delim, xdelim)
189
190  # Return the netloc and the rest of the URL.
191  return url[start:delim], url[delim:]
192
193
194def _urlparse_splitauthority(netloc):
195  """Parse the authority portion of the netloc"""
196
197  # The authority can have a userinfo portion delimited by "@".
198  authority = netloc.split("@", 1)
199
200  # Default values.
201  username = None
202  password = None
203  hostname = None
204  port = None
205
206  # Is there a userinfo portion?
207  if len(authority) == 2:
208
209    # userinfo can be split into username:password
210    userinfo = authority[0].split(":", 1)
211
212    # hostport can be split into hostname:port
213    hostport = authority[1].split(":", 1)
214
215    if userinfo[0]:
216      username = userinfo[0]
217    if len(userinfo) == 2:
218      password = userinfo[1]
219
220  # No userinfo portion found.
221  else:
222
223    # hostport can be split into hostname:port
224    hostport = netloc.split(":", 1)
225
226  # Is there a port value?
227  if hostport[0]:
228    hostname = hostport[0]
229  if len(hostport) == 2:
230    port = int(hostport[1], 10)
231
232  # Return the values.
233  return username, password, hostname, port
234
235
236def _urlparse_splitquery(url):
237  """Parse the query portion of the url"""
238
239  qpart = url.split("?", 1)
240  if len(qpart) == 2:
241    query = qpart[1]
242  else:
243    query = ""
244
245  return qpart[0], query
246
247
248def _urlparse_splitfragment(url):
249  """Parse the query portion of the url"""
250
251  fpart = url.split("#", 1)
252  if len(fpart) == 2:
253    fragment = fpart[1]
254  else:
255    fragment = ""
256
257  return fpart[0], fragment
258
259#end include urlparse.repy
260#begin include httpretrieve.repy
261"""
262<Program Name>
263  httpretrieve.repy
264
265<Started>
266  August 19, 2009
267
268<Authors>
269  Yafete Yemuru
270  Conrad Meyer
271 
272<Purpose>
273  Provides a method for retrieving content from web servers using the HTTP
274  protocol. The content can be accessed as a file like object, or saved to
275  a file or returned as a string.
276"""
277
278
279
280#begin include urlparse.repy
281#already included urlparse.repy
282#end include urlparse.repy
283#begin include sockettimeout.repy
284"""
285<Author>
286  Justin Cappos, Armon Dadgar
287  This is a rewrite of the previous version by Richard Jordan
288
289<Start Date>
290  26 Aug 2009
291
292<Description>
293  A library that causes sockets to timeout if a recv / send call would
294  block for more than an allotted amount of time.
295
296"""
297
298
299class SocketTimeoutError(Exception):
300  """The socket timed out before receiving a response"""
301
302
303class _timeout_socket():
304  """
305  <Purpose>
306    Provides a socket like object which supports custom timeouts
307    for send() and recv().
308  """
309
310  # Initialize with the socket object and a default timeout
311  def __init__(self,socket,timeout=10, checkintv='fibonacci'):
312    """
313    <Purpose>
314      Initializes a timeout socket object.
315
316    <Arguments>
317      socket:
318              A socket like object to wrap. Must support send,recv,close, and willblock.
319
320      timeout:
321              The default timeout for send() and recv().
322
323      checkintv:
324              How often socket operations (send,recv) should check if
325              they can run. The smaller the interval the more time is
326              spent busy waiting.
327    """
328    # Store the socket, timeout and check interval
329    self.socket = socket
330    self.timeout = timeout
331    self.checkintv = checkintv
332
333
334  # Allow changing the default timeout
335  def settimeout(self,timeout=10):
336    """
337    <Purpose>
338      Allows changing the default timeout interval.
339
340    <Arguments>
341      timeout:
342              The new default timeout interval. Defaults to 10.
343              Use 0 for no timeout. Given in seconds.
344
345    """
346    # Update
347    self.timeout = timeout
348 
349 
350  # Wrap willblock
351  def willblock(self):
352    """
353    See socket.willblock()
354    """
355    return self.socket.willblock()
356
357
358  # Wrap close
359  def close(self):
360    """
361    See socket.close()
362    """
363    return self.socket.close()
364
365
366  # Provide a recv() implementation
367  def recv(self,bytes,timeout=None):
368    """
369    <Purpose>
370      Allows receiving data from the socket object with a custom timeout.
371
372    <Arguments>
373      bytes:
374          The maximum amount of bytes to read
375
376      timeout:
377          (Optional) Defaults to the value given at initialization, or by settimeout.
378          If provided, the socket operation will timeout after this amount of time (sec).
379          Use 0 for no timeout.
380
381    <Exceptions>
382      As with socket.recv(), socket.willblock(). Additionally, SocketTimeoutError is
383      raised if the operation times out.
384
385    <Returns>
386      The data received from the socket.
387    """
388
389    # It's worth noting that this fibonacci backoff begins with a 2ms poll rate, and
390    # provides a simple exponential backoff scheme.
391
392    fibonacci_backoff = False
393    backoff_cap = 100 # Never use more than 100ms poll rate.
394
395    pre_value = 1.0     # Our iterators for Fibonacci sequence.
396    pre_pre_value = 1.0 #
397
398    # Since we want to be able to initialize with static poll rates (backwards
399    # compatibility) we specify a string if we're using the fibonacci backoff.
400    if type(self.checkintv) is str:
401      if self.checkintv == 'fibonacci':
402        fibonacci_backoff = True
403
404    # Set the timeout if None
405    if timeout is None:
406      timeout = self.timeout
407
408    # Get the start time
409    starttime = getruntime()
410
411    # Block until we can read
412    rblock, wblock = self.socket.willblock()
413    while rblock:
414      # Check if we should break
415      if timeout > 0:
416        # Get the elapsed time
417        diff = getruntime() - starttime
418
419        # Raise an exception
420        if diff > timeout:
421          raise SocketTimeoutError,"recv() timed out!"
422
423      if fibonacci_backoff:
424        # Iterate the sequence once
425        sleep_length = pre_value + pre_pre_value
426        pre_pre_value = pre_value
427        pre_value = sleep_length
428
429        # Make sure we don't exceed maximum backoff.
430        if sleep_length > backoff_cap:
431          sleep_length = backoff_cap
432
433        # Unit conversion to seconds
434        sleep_length = sleep_length / 1000.0
435
436        # Sleep
437        sleep(sleep_length)
438      else: # Classic functionality.
439        # Sleep
440        try:
441          sleep(float(self.checkintv))
442        except:
443          sleep(0.1)
444
445      # If available, move to the next value of checkintv.
446     
447
448      # Update rblock
449      rblock, wblock = self.socket.willblock()
450
451    # Do the recv
452    return self.socket.recv(bytes)
453
454
455  # Provide a send() implementation
456  def send(self,data,timeout=None):
457    """
458    <Purpose>
459      Allows sending data with the socket object with a custom timeout.
460
461    <Arguments>
462      data:
463          The data to send
464
465      timeout:
466          (Optional) Defaults to the value given at initialization, or by settimeout.
467          If provided, the socket operation will timeout after this amount of time (sec).
468          Use 0 for no timeout.
469
470    <Exceptions>
471      As with socket.send(), socket.willblock(). Additionally, SocketTimeoutError is
472      raised if the operation times out.
473
474    <Returns>
475      The number of bytes sent.
476    """
477    # Set the timeout if None
478    if timeout is None:
479      timeout = self.timeout
480
481    # Get the start time
482    starttime = getruntime()
483
484    # Block until we can write
485    rblock, wblock = self.socket.willblock()
486    while wblock:
487      # Check if we should break
488      if timeout > 0:
489        # Get the elapsed time
490        diff = getruntime() - starttime
491
492        # Raise an exception
493        if diff > timeout:
494          raise SocketTimeoutError,"send() timed out!"
495
496      # Sleep
497      # Since switching to the fibonacci backoff, the nature of
498      # this field has changed. Rather than implement the backoff
499      # for checking block status (seems wasteful) we'll just use
500      # a constant value. Ten ms seems appropriate.
501      sleep(0.010)
502
503      # Update rblock
504      rblock, wblock = self.socket.willblock()
505
506    # Do the recv
507    return self.socket.send(data) 
508
509
510
511
512def timeout_openconn(desthost, destport, localip=None, localport=None, timeout=5):
513  """
514  <Purpose>
515    Wrapper for openconn.   Very, very similar
516
517  <Args>
518    Same as Repy openconn
519
520  <Exception>
521    Raises the same exceptions as openconn.
522
523  <Side Effects>
524    Creates a socket object for the user
525
526  <Returns>
527    socket obj on success
528  """
529
530  realsocketlikeobject = openconn(desthost, destport, localip, localport, timeout)
531
532  thissocketlikeobject = _timeout_socket(realsocketlikeobject, timeout)
533  return thissocketlikeobject
534
535
536
537
538
539def timeout_waitforconn(localip, localport, function, timeout=5):
540  """
541  <Purpose>
542    Wrapper for waitforconn.   Essentially does the same thing...
543
544  <Args>
545    Same as Repy waitforconn with the addition of a timeout argument.
546
547  <Exceptions>
548    Same as Repy waitforconn
549
550  <Side Effects>
551    Sets up event listener which calls function on messages.
552
553  <Returns>
554    Handle to listener.
555  """
556
557  # We use a closure for the callback we pass to waitforconn so that we don't
558  # have to map mainch's to callback functions or deal with potential race
559  # conditions if we did maintain such a mapping.
560  def _timeout_waitforconn_callback(localip, localport, sockobj, ch, mainch):
561    # 'timeout' is the free variable 'timeout' that was the argument to
562    #  timeout_waitforconn.
563    thissocketlikeobject = _timeout_socket(sockobj, timeout)
564
565    # 'function' is the free variable 'function' that was the argument to
566    #  timeout_waitforconn.
567    return function(localip, localport, thissocketlikeobject, ch, mainch)
568
569  return waitforconn(localip, localport, _timeout_waitforconn_callback)
570
571 
572 
573
574
575# a wrapper for stopcomm
576def timeout_stopcomm(commhandle):
577  """
578    Wrapper for stopcomm.   Does the same thing...
579  """
580
581  return stopcomm(commhandle)
582 
583   
584
585
586#end include sockettimeout.repy
587#begin include urllib.repy
588def urllib_quote(inputstring, safestring="/"):
589  """
590  <Purpose>
591    Encode an inputstring such that it can be used safely in a URL or XML
592    document.
593
594  <Arguments>
595    inputstring:
596           The string to urlencode.
597
598    safestring (optional):
599           Specifies additional characters that should not be quoted --
600           defaults to "/".
601
602  <Exceptions>
603    TypeError if the inputstring or safestring parameters aren't strings.
604
605  <Side Effects>
606    None.
607
608  <Returns>
609    Urlencoded version of the passed string.
610  """
611
612  if type(inputstring) is not str:
613    raise TypeError("urllib_quote's inputstring parameter must be a string, not '"+str(type(inputstring))+"'")
614  if type(safestring) is not str:
615    raise TypeError("urllib_quote's safestring parameter must be a string, not '"+str(type(safestring))+"'")
616 
617
618  resultstr = ""
619
620  # We go through each character in the string; if it's not in [0-9a-zA-Z]
621  # we wrap it.
622
623  safeset = set(safestring)
624
625  for char in inputstring:
626    asciicode = ord(char)
627    if (asciicode >= ord("0") and asciicode <= ord("9")) or \
628        (asciicode >= ord("A") and asciicode <= ord("Z")) or \
629        (asciicode >= ord("a") and asciicode <= ord("z")) or \
630        asciicode == ord("_") or asciicode == ord(".") or \
631        asciicode == ord("-") or char in safeset:
632      resultstr += char
633    else:
634      resultstr += "%%%02X" % asciicode
635
636  return resultstr
637
638
639
640
641def urllib_quote_plus(inputstring, safestring=""):
642  """
643  <Purpose>
644    Encode a string to go in the query fragment of a URL.
645
646  <Arguments>
647    inputstring:
648           The string to urlencode.
649
650    safestring (optional):
651           Specifies additional characters that should not be quoted --
652           defaults to the empty string.
653
654  <Exceptions>
655    TypeError if the inputstring or safestring parameters aren't strings.
656
657  <Side Effects>
658    None.
659
660  <Returns>
661    Urlencoded version of the passed string.
662  """
663
664  if type(inputstring) is not str:
665    raise TypeError("urllib_quote_plus' inputstring parameter must be a string, not '"+str(type(inputstring))+"'")
666  if type(safestring) is not str:
667    raise TypeError("urllib_quote_plus' safestring parameter must be a string, not '"+str(type(safestring))+"'")
668 
669
670  return urllib_quote(inputstring, safestring + " ").replace(" ", "+")
671
672
673
674
675def urllib_unquote(inputstring):
676  """
677  <Purpose>
678    Unquote a urlencoded string.
679
680  <Arguments>
681    inputstring:
682           The string to unquote.
683
684  <Exceptions>
685    TypeError if the inputstring isn't a string
686    ValueError thrown if the last wrapped octet isn't a valid wrapped octet
687    (i.e. if the string ends in "%" or "%x" rather than "%xx". Also throws
688    ValueError if the nibbles aren't valid hex digits.
689
690  <Side Effects>
691    None.
692
693  <Returns>
694    The decoded string.
695  """
696
697  if type(inputstring) is not str:
698    raise TypeError("urllib_unquote's inputstring parameter must be a string, not '"+str(type(inputstring))+"'")
699 
700
701  resultstr = ""
702
703  # We go through the inputstring from end to beginning, looking for wrapped
704  # octets. When one is found we add it (unwrapped) and the following
705  # string to the resultant string, and shorten the original inputstring.
706
707  while True:
708    lastpercentlocation = inputstring.rfind("%")
709    if lastpercentlocation < 0:
710      break
711
712    wrappedoctetstr = inputstring[lastpercentlocation+1:lastpercentlocation+3]
713    if len(wrappedoctetstr) != 2:
714      raise ValueError("Quoted string is poorly formed")
715
716    resultstr = \
717        chr(int(wrappedoctetstr, 16)) + \
718        inputstring[lastpercentlocation+3:] + \
719        resultstr
720    inputstring = inputstring[:lastpercentlocation]
721
722  resultstr = inputstring + resultstr
723  return resultstr
724
725
726
727
728def urllib_unquote_plus(inputstring):
729  """
730  <Purpose>
731    Unquote the urlencoded query fragment of a URL.
732
733  <Arguments>
734    inputstring:
735           The string to unquote.
736
737  <Exceptions>
738    TypeError if the inputstring isn't a string
739    ValueError thrown if the last wrapped octet isn't a valid wrapped octet
740    (i.e. if the inputstring ends in "%" or "%x" rather than "%xx". Also throws
741    ValueError if the nibbles aren't valid hex digits.
742
743  <Side Effects>
744    None.
745
746  <Returns>
747    The decoded string.
748  """
749  if type(inputstring) is not str:
750    raise TypeError("urllib_unquote_plus' inputstring parameter must be a string, not '"+str(type(inputstring))+"'")
751
752  return urllib_unquote(inputstring.replace("+", " "))
753
754
755
756
757def urllib_quote_parameters(inputdictionary):
758  """
759  <Purpose>
760    Encode a dictionary of (key, value) pairs into an HTTP query string or
761    POST body (same form).
762
763  <Arguments>
764    dictionary:
765           The dictionary to quote.
766
767  <Exceptions>
768    TypeError if the inputdictionary isn't a dict.
769
770  <Side Effects>
771    None.
772
773  <Returns>
774    The quoted dictionary.
775  """
776  if type(inputdictionary) is not dict:
777    raise TypeError("urllib_quote_parameters' inputstringdictionary parameter must be a dict, not '"+str(type(inputstring))+"'")
778
779  quoted_keyvals = []
780  for key, val in inputdictionary.items():
781    quoted_keyvals.append("%s=%s" % (urllib_quote(key), urllib_quote(val)))
782
783  return "&".join(quoted_keyvals)
784
785
786
787
788def urllib_unquote_parameters(inputstring):
789  """
790  <Purpose>
791    Decode a urlencoded query string or POST body.
792
793  <Arguments>
794    inputstring:
795           The string to decode.
796
797  <Exceptions>
798    TypeError if the inputstring isn't a string
799    ValueError if the inputstring is poorly formed.
800
801  <Side Effects>
802    None.
803
804  <Returns>
805    A dictionary mapping keys to values.
806  """
807
808  if type(inputstring) is not str:
809    raise TypeError("urllib_unquote_parameters' inputstring parameter must be a string, not '"+str(type(inputstring))+"'")
810
811  keyvalpairs = inputstring.split("&")
812  res = {}
813
814  for quotedkeyval in keyvalpairs:
815    # Throw ValueError if there is more or less than one '='.
816    quotedkey, quotedval = quotedkeyval.split("=")
817    key = urllib_unquote_plus(quotedkey)
818    val = urllib_unquote_plus(quotedval)
819    res[key] = val
820
821  return res
822
823#end include urllib.repy
824
825
826
827class HttpConnectionError(Exception):
828  """
829  Error indicating that the web server has unexpectedly dropped the
830  connection.
831  """
832
833
834
835
836class HttpBrokenServerError(Exception):
837  """
838  Error indicating that the web server has sent us complete garbage instead
839  of something resembling HTTP.
840  """
841
842
843
844
845def httpretrieve_open(url, querydata=None, postdata=None,\
846    httpheaders=None, proxy=None, timeout=None):
847  """
848  <Purpose>
849     Returns a file-like object that can be used to read the content from
850     an HTTP server. Follows 3xx redirects.
851
852  <Arguments>
853    url:
854           The URL to perform a GET or POST request on.
855    postdata (optional):
856           A dictionary of form data or a string to POST to the server.
857           Passing a non-None value results in a POST request being sent
858           to the server.
859    querydata (optional):
860           A dictionary of form data or a string to send as the query
861           string to the server.
862
863           If postdata is omitted, the URL is retrieved with GET. If
864           both postdata and querydata are omitted, there is no query
865           string sent in the request.
866
867           For both querydata and postdata, strings are sent *unmodified*.
868           This means you probably should encode them first, with
869           urllib_quote().
870    httpheaders (optional):
871           A dictionary of supplemental HTTP request headers to add to the
872           request.
873    proxy (optional):
874           A proxy server 2-tuple to bind to: ('host', port).       
875    timeout (optional):
876           A timeout for establishing a connection to the web server,
877           sending headers, and reading the response headers.
878
879           If excluded or None, never times out.
880
881  <Exceptions>
882    ValueError if given an invalid URL, or malformed limit or timeout
883      values. This is also raised if the user attempts to call a method
884      on the file-like object after closing it.
885
886    HttpConnectionError if opening the connection fails, or if the
887      connection is closed by the server before we expect.
888
889    SocketTimeoutError if the timeout is exceeded.
890
891    HttpBrokenServerError if the response or the Location response header
892      is malformed.
893
894  <Side Effects>
895    None
896
897  <Returns>
898    Returns a file-like object which can be used to read the body of
899    the response from the web server. The protocol version spoken by the
900    server, status code, and response headers are available as members of
901    the object.
902  """
903
904  starttimefloat = getruntime()
905
906  # Check if the URL is valid and get host, path, port and query
907  parsedurldict = urlparse_urlsplit(url)
908  hoststr = parsedurldict['hostname']
909  pathstr = parsedurldict['path']
910  portint = parsedurldict.get('port')
911  portint = portint or 80
912
913  if parsedurldict['scheme'] != 'http':
914    raise ValueError("URL doesn't seem to be for the HTTP protocol.")
915  if hoststr is None:
916    raise ValueError("Missing hostname.")
917  if parsedurldict['query'] is not None and parsedurldict['query'] != "":
918    raise ValueError("URL cannot include a query string.")
919
920  # Typical HTTP sessions consist of (optionally, a series of pairs of) HTTP
921  # requests followed by HTTP responses. These happen serially.
922
923  # JAC: Set this up so that we can raise the right error if the
924  # timeout_openconn doesn't work.
925  sockobj = None
926
927  # Open connection to the web server
928  try:
929    if proxy is not None:
930      # if there is a proxy, open a connection with the proxy instead of the actual server
931      # use the timeout we are given (or none)
932      sockobj = timeout_openconn(proxy[0], proxy[1], timeout=timeout) 
933    else:
934      # if there is no proxy open a connection with server directly
935      # use the timeout we are given (or none)
936      sockobj = timeout_openconn(hoststr, portint, timeout=timeout)
937
938  except Exception, e:
939    # If a socket object was created, we want to clean in up.
940    if sockobj:
941      sockobj.close()
942
943    if repr(e).startswith("timeout("):
944      raise HttpConnectionError("Socket timed out connecting to host/port.")
945    raise
946
947  try:
948    # Builds the HTTP request:
949    httprequeststr = _httpretrieve_build_request(hoststr, portint, pathstr, \
950        querydata, postdata, httpheaders, proxy)
951
952    # Send the full HTTP request to the web server.
953    _httpretrieve_sendall(sockobj, httprequeststr)
954
955    # Now, we're done with the HTTP request part of the session, and we need
956    # to get the HTTP response.
957
958    # Check if we've timed out (if the user requested a timeout); update the
959    # socket timeout to reflect the time taken sending the request.
960    if timeout is None:
961      sockobj.settimeout(0)
962    elif getruntime() - starttimefloat >= timeout:
963      raise SocketTimeoutError("Timed out")
964    else:
965      sockobj.settimeout(timeout - (getruntime() - starttimefloat))
966
967    # Receive the header lines from the web server (a series of CRLF-terminated
968    # lines, terminated by an empty line, or by the server closing the
969    # connection.
970    headersstr = ""
971    while not headersstr.endswith("\r\n\r\n"):
972      try:
973        # This should probably be replaced with page-sized reads in the future,
974        # but for now, the behavior is at least correct.
975        headersstr += sockobj.recv(1)
976      except Exception, e:
977        if str(e) == "Socket closed":
978          break
979        else:
980          raise
981
982    httpheaderlist = headersstr.split("\r\n")
983    # Ignore (a) trailing blank line(s) (for example, the response header-
984    # terminating blank line).
985    while len(httpheaderlist) > 0 and httpheaderlist[-1] == "":
986      httpheaderlist = httpheaderlist[:-1]
987
988    # Get the status code and status message from the HTTP response.
989    statuslinestr, httpheaderlist = httpheaderlist[0], httpheaderlist[1:]
990
991    # The status line should be in the form: "HTTP/1.X NNN SSSSS", where
992    # X is 0 or 1, NNN is a 3-digit status code, and SSSSS is a 'user-friendly'
993    # string representation of the status code (may contain spaces).
994    statuslinelist = statuslinestr.split(' ', 2)
995
996    if len(statuslinelist) < 3:
997      raise HttpBrokenServerError("Server returned garbage for HTTP " + \
998        "response (status line missing one or more fields).")
999
1000    if not statuslinelist[0].startswith('HTTP'):
1001      raise HttpBrokenServerError("Server returned garbage for HTTP " + \
1002          "response (invalid response protocol in status line).")
1003
1004    friendlystatusstr = statuslinelist[2]
1005    try:
1006      statusint = int(statuslinelist[1])
1007    except ValueError, e:
1008      raise HttpBrokenServerError("Server returned garbage for HTTP " + \
1009        "response (status code isn't integer).")
1010
1011    httpheaderdict = _httpretrieve_parse_responseheaders(httpheaderlist)
1012
1013    # If we got any sort of redirect response, follow the redirect. Note: we
1014    # do *not* handle the 305 status code (use the proxy as specified in the
1015    # Location header) at all; I think this is best handled at a higher layer
1016    # anyway.
1017    if statusint in (301, 302, 303, 307):
1018      sockobj.close()
1019      try:
1020        redirecturlstr = httpheaderdict["Location"][0]
1021      except (KeyError, IndexError), ke:
1022        # When a server returns a redirect status code (3xx) but no Location
1023        # header, some clients, e.g. Firefox, just show the response body
1024        # as they would normally for a 2xx or 4xx response. So, I think we
1025        # should ignore a missing Location header and just return the page
1026        # to the caller.
1027        pass
1028      else:
1029        # If the server did send a redirect location, let's go there.
1030        return httpretrieve_open(redirecturlstr)
1031
1032    # If we weren't requested to redirect, and we didn't, return a read-only
1033    # file-like object (representing the response body) to the caller.
1034    return _httpretrieve_filelikeobject(sockobj, httpheaderdict, \
1035        (statuslinelist[0], statusint, friendlystatusstr))
1036 
1037  except:
1038    # If any exception occured after the socket was open, we want to make
1039    # sure that the socket is cleaned up if it is still open before we
1040    # raise the exception.
1041    if sockobj:
1042      sockobj.close()
1043
1044    raise
1045
1046
1047
1048def httpretrieve_save_file(url, filename, querydata=None, postdata=None, \
1049    httpheaders=None, proxy=None, timeout=None):
1050  """
1051  <Purpose>
1052    Perform an HTTP request, and save the content of the response to a
1053    file.
1054
1055  <Arguments>
1056    filename:
1057           The file name to save the response to.
1058    Other arguments:
1059           See documentation for httpretrieve_open().
1060
1061  <Exceptions>
1062    This function will raise any exception raised by Repy file objects
1063    in opening, writing to, and closing the file.
1064
1065    This function will all also raise any exception raised by
1066    httpretrieve_open(), for the same reasons.
1067
1068  <Side Effects>
1069    Writes the body of the response to 'filename'.
1070
1071  <Returns>
1072    None
1073  """
1074
1075  # Open the output file object and http file-like object.
1076  outfileobj = open(filename, 'w')
1077  httpobj = httpretrieve_open(url, querydata=querydata, postdata=postdata, \
1078      httpheaders=httpheaders, proxy=proxy, timeout=timeout)
1079
1080  # Repeatedly read from the file-like HTTP object into our file, until the
1081  # response is finished.
1082  responsechunkstr = None
1083  while responsechunkstr != '':
1084    responsechunkstr = httpobj.read(4096)
1085    outfileobj.write(responsechunkstr)
1086
1087  outfileobj.close()
1088  httpobj.close()
1089
1090
1091
1092
1093def httpretrieve_get_string(url, querydata=None, postdata=None, \
1094    httpheaders=None, proxy=None, timeout=30):
1095  """
1096  <Purpose>
1097    Performs an HTTP request on the given URL, using POST or GET,
1098    returning the content of the response as a string. Uses
1099    httpretrieve_open.
1100
1101  <Arguments>
1102    See httpretrieve_open.
1103
1104  <Exceptions>
1105    See httpretrieve_open.
1106
1107  <Side Effects>
1108    None.
1109
1110  <Returns>
1111    Returns the body of the HTTP response (no headers).
1112  """
1113
1114  # Open a read-only file-like object for the HTTP request.
1115  httpobj = httpretrieve_open(url, querydata=querydata, postdata=postdata, \
1116      httpheaders=httpheaders, proxy=proxy, timeout=timeout)
1117
1118  # Read all of the response and return it.
1119  try:
1120    return httpobj.read()
1121  finally:
1122    httpobj.close()
1123
1124
1125
1126
1127class _httpretrieve_filelikeobject:
1128  # This class implements a file-like object used for performing HTTP
1129  # requests and retrieving responses.
1130
1131  def __init__(self, sock, headers, httpstatus):
1132    # The socket-like object connected to the HTTP server. Headers have
1133    # already been read.
1134    self._sockobj = sock
1135
1136    # If this is set, the close() method has already been called, so we
1137    # don't accept future reads.
1138    self._fileobjclosed = False
1139
1140    # This flag is set if we've finished recieving the entire response
1141    # from the server.
1142    self._totalcontentisreceived = False
1143
1144    # This integer represents the number of bytes read so far.
1145    self._totalread = 0
1146
1147    # This is the dictionary of HTTP response headers associated with this
1148    # file-like object.
1149    self.headers = headers
1150
1151    # The HTTP status tuple of this response, e.g. ("HTTP/1.0", 200, "OK")
1152    self.httpstatus = httpstatus
1153
1154
1155
1156  def read(self, limit=None, timeout=None):
1157    """
1158    <Purpose>
1159      Behaves like Python's file.read(), with the potential to raise
1160      additional informative exceptions.
1161
1162    <Arguments>
1163      limit (optional):
1164            The maximum amount of data to read. If omitted or None, this
1165            reads all available data.
1166
1167    <Exceptions>
1168      See file.read()'s documentation, as well as that of
1169      httpretrieve_open().
1170
1171    <Side Effects>
1172      None.
1173
1174    <Returns>
1175      See file.read().
1176    """
1177
1178    # Raise an error if the caller has already close()d this object.
1179    if self._fileobjclosed:
1180      raise ValueError("I/O operation on closed file")
1181
1182    # If we've finished reading everything we can from the server, return the
1183    # empty string.
1184    if self._totalcontentisreceived:
1185      return ''
1186
1187    lefttoread = None
1188    if limit is not None:
1189      lefttoread = limit
1190
1191      # Sanity check type/value of limit.
1192      if type(limit) is not int:
1193        raise TypeError("Expected an integer or None for read() limit")
1194      elif limit < 0:
1195        raise ValueError("Expected a non-negative integer for read() limit")
1196
1197    if timeout is None:
1198      self._sockobj.settimeout(0)
1199    else:
1200      self._sockobj.settimeout(timeout)
1201
1202    # Try to read up to limit, or until there is nothing left.
1203    httpcontentstr = ''
1204    while True:
1205      try:
1206        contentchunkstr = self._sockobj.recv(lefttoread or 4096)
1207      except Exception, e:
1208        if str(e) == "Socket closed":
1209          self._totalcontentisreceived = True
1210          break
1211        else:
1212          raise
1213     
1214      httpcontentstr += contentchunkstr
1215      self._totalread += len(contentchunkstr)
1216      if limit is not None:
1217        if len(contentchunkstr) == lefttoread:
1218          break
1219        else:
1220          lefttoread -= len(contentchunkstr)
1221      if contentchunkstr == "":
1222        self._totalcontentisreceived = True
1223        break
1224
1225    return httpcontentstr
1226
1227
1228
1229  def close(self):
1230    """
1231    <Purpose>
1232      Close the file-like object.
1233
1234    <Arguments>
1235      None
1236
1237    <Exceptions>
1238      None
1239
1240    <Side Effects>
1241      Disconnects from the HTTP server.
1242
1243    <Returns>
1244      Nothing
1245    """
1246    self._fileobjclosed = True
1247    self._sockobj.close()
1248
1249
1250
1251
1252def _httpserver_put_in_headerdict(res, lastheader, lastheader_str):
1253  # Helper function that tries to put the header into a dictionary of lists,
1254  # 'res'.
1255  if lastheader is not None:
1256    if lastheader not in res:
1257      res[lastheader] = []
1258    res[lastheader].append(lastheader_str.strip())
1259
1260
1261
1262
1263def _httpretrieve_parse_responseheaders(headerlines):
1264  # Parse rfc822-style headers (this could be abstracted out to an rfc822
1265  # library that would be quite useful for internet protocols). Returns
1266  # a dictionary mapping headers to arrays of values. E.g.:
1267  #
1268  # Foo: a
1269  # Bar:
1270  #   b
1271  # Bar: c
1272  #
1273  # Becomes: {"Foo": ["a"], "Bar": ["b", "c"]}
1274
1275  # These variables represent the key and value of the last header we found,
1276  # unless we are parsing the very first header. E.g., if we've just read:
1277  #   Content-Type: text/html
1278  # Then, lastheaderkeystr == "Content-Type",
1279  # lastheadervaluestr == "text/html"
1280
1281  lastheaderkeystr = None
1282  lastheadervaluestr = ""
1283
1284  resdict = {}
1285 
1286  if len(headerlines) == 0:
1287    return {}
1288
1289  try:
1290    # Iterate over the request header lines:
1291    for i in range(len(headerlines)):
1292      # Lines with leading non-CRLF whitespace characters are part of the
1293      # previous line (see rfc822 for details).
1294      if headerlines[i][0] in (" ", "\t") and lastheaderkeystr is not None:
1295        lastheadervaluestr += headerlines[i]
1296      else:
1297        _httpserver_put_in_headerdict(resdict, lastheaderkeystr, lastheadervaluestr)
1298        lastheaderkeystr, lastheadervaluestr = headerlines[i].split(":", 1)
1299
1300    # Add the last line to the result dictionary.
1301    _httpserver_put_in_headerdict(resdict, lastheaderkeystr, lastheadervaluestr)
1302
1303    return resdict
1304
1305  except IndexError, idx:
1306    raise HttpBrokenServerError("Server returned garbage for HTTP" + \
1307        " response. Bad headers.")
1308
1309
1310
1311
1312def _httpretrieve_build_request(host, port, path, querydata, postdata, \
1313    httpheaders, proxy):
1314  # Builds an HTTP request from these parameters, returning it as
1315  # a string.
1316
1317  # Sanity checks:
1318  if path == "":
1319    raise ValueError("Invalid path -- empty string.")
1320  if postdata is not None and type(postdata) not in (str, dict):
1321    raise TypeError("Postdata should be a dict of form-data or a string")
1322  if querydata is not None and type(querydata) not in (str, dict):
1323    raise TypeError("Querydata should be a dict of form-data or a string")
1324  if httpheaders is not None and type(httpheaders) is not dict:
1325    raise TypeError("Expected HTTP headers as a dictionary.")
1326
1327  # Type-conversions:
1328  if type(querydata) is dict:
1329    querydata = urllib_quote_parameters(querydata)
1330  elif querydata is None:
1331    querydata = ""
1332
1333  if type(postdata) is dict:
1334    postdata = urllib_quote_parameters(postdata)
1335
1336  # Default to GET, unless the caller specifies a message body to send.
1337  methodstr = "GET"
1338  if postdata is not None:
1339    methodstr = "POST"
1340
1341  # Encode the path and querystring part of the request.
1342  resourcestr = querydata
1343  if querydata != "":
1344    resourcestr = "?" + resourcestr
1345
1346  # Encode the HTTP request line and headers:
1347  if proxy is not None:
1348    # proxy exists thus the request header should include the original requested url 
1349    requeststr = methodstr + ' http://' + host + ':' + str(port) + path + resourcestr + ' HTTP/1.0\r\n'
1350  else:
1351    # there is no proxy; send normal http request   
1352    requeststr = methodstr + ' ' + path + resourcestr + ' HTTP/1.0\r\n'
1353   
1354  if httpheaders is not None:
1355    # Most servers require a 'Host' header for normal functionality
1356    # (especially in the case of multiple domains being hosted on a
1357    # single server).
1358    if "Host" not in httpheaders:
1359      requeststr += "Host: " + host + ':' + str(port) + "\r\n"
1360
1361    for key, val in httpheaders.items():
1362      requeststr += key + ": " + val + '\r\n'
1363
1364  # Affix post-data related headers and content:
1365  if methodstr == "POST":
1366    requeststr += 'Content-Length: ' + str(len(postdata)) + '\r\n'
1367
1368  # The empty line terminates HTTP headers.
1369  requeststr += '\r\n'
1370
1371  # If we're a POST request, affix any requested data to the message body.
1372  if methodstr == "POST":
1373    requeststr += postdata
1374
1375  return requeststr
1376
1377
1378
1379
1380def _httpretrieve_sendall(sockobj, datastr):
1381  # Helper function that attempts to dump all of the data in datastr to the
1382  # socket sockobj (data is any arbitrary bytes).
1383  while len(datastr) > 0:
1384    datastr = datastr[sockobj.send(datastr):]
1385
1386#end include httpretrieve.repy
1387#begin include xmlrpc_common.repy
1388"""
1389<Program Name>
1390  $Id: xmlrpc_common.repy 3260 2009-12-09 18:26:31Z cemeyer $
1391
1392<Started>
1393  April 26, 2009
1394
1395<Author>
1396  Michael Phan-Ba
1397
1398<Purpose>
1399  Provides common methods related to XML-RPC.
1400
1401  Encoding dateTime.iso8601 are not currently supported.
1402
1403<Changes>
1404
1405  2009-04-26  Michael Phan-Ba  <mdphanba@gmail.com>
1406
1407  * Initial release
1408
1409  2009-05-24  Michael Phan-Ba  <mdphanba@gmail.com>
1410
1411  * Added change log
1412  * Fixed base64 name error
1413  * Set property svn:keyword to "Id"
1414
1415"""
1416
1417
1418#begin include base64.repy
1419"""
1420<Program Name>
1421  $Id: base64.repy 2527 2009-07-26 22:48:38Z cemeyer $
1422
1423<Started>
1424  April 12, 2009
1425
1426<Author>
1427  Michael Phan-Ba
1428
1429<Purpose>
1430  Provides data encoding and decoding as specified in RFC 3548. This
1431  module implements a subset of the Python module base64 interface.
1432
1433  b32encode(), b32decode(), b16encode(), b16decode(), decode(),
1434  decodestring(), encode(), and encodestring() are not currently
1435  implemented.
1436
1437<Changes>
1438
1439  2009-04-12  Michael Phan-Ba  <mdphanba@gmail.com>
1440
1441  * Initial release
1442
1443  2009-05-23  Michael Phan-Ba  <mdphanba@gmail.com>
1444
1445  * (b64encode, b64decode, standard_b64encode, standard_b64decode,
1446    urlsafe_encode, urlsafe_decode): Renamed functions with base64 prefix
1447
1448  2009-05-24  Michael Phan-Ba  <mdphanba@gmail.com>
1449
1450  * Set property svn:keyword to "Id"
1451
1452"""
1453
1454# The Base64 for use in encoding
1455BASE64_ALPHABET = \
1456  "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
1457
1458def base64_b64encode(s, altchars=None):
1459  """
1460  <Purpose>
1461    Encode a string using Base64.
1462
1463  <Arguments>
1464    s:
1465      The string to encode.
1466
1467    altchars:
1468      An optional string of at least length 2 (additional characters are
1469      ignored) which specifies an alternative alphabet for the + and /
1470      characters.  The default is None, for which the standard Base64
1471      alphabet is used.
1472
1473  <Exceptions>
1474    None.
1475
1476  <Side Effects>
1477    None.
1478
1479  <Returns>
1480    The encoded string.
1481
1482  """
1483  # Build the local alphabet.
1484  if altchars is None:
1485    base64_alphabet = BASE64_ALPHABET
1486  else:
1487    base64_alphabet = BASE64_ALPHABET[:62] + altchars
1488
1489  # Change from characters to integers for binary operations.
1490  bytes = []
1491  for x in s:
1492    bytes.append(ord(x))
1493
1494  # Encode the 8-bit words into 6-bit words.
1495  x6bit_words = []
1496  index = 0
1497  while True:
1498
1499    # Encode the first 6 bits from three 8-bit values.
1500    try:
1501      x8bits = bytes[index]
1502    except IndexError:
1503      break
1504    else:
1505      x6bits = x8bits >> 2
1506      leftover_bits = x8bits & 3
1507      x6bit_words.append(base64_alphabet[x6bits])
1508
1509    # Encode the next 8 bits.
1510    try:
1511      x8bits = bytes[index + 1]
1512    except IndexError:
1513      x6bits = leftover_bits << 4
1514      x6bit_words.extend([base64_alphabet[x6bits], "=="])
1515      break
1516    else:
1517      x6bits = (leftover_bits << 4) | (x8bits >> 4)
1518      leftover_bits = x8bits & 15
1519      x6bit_words.append(base64_alphabet[x6bits])
1520
1521    # Encode the final 8 bits.
1522    try:
1523      x8bits = bytes[index + 2]
1524    except IndexError:
1525      x6bits = leftover_bits << 2
1526      x6bit_words.extend([base64_alphabet[x6bits], "="])
1527      break
1528    else:
1529      x6bits = (leftover_bits << 2) | (x8bits >> 6)
1530      x6bit_words.append(base64_alphabet[x6bits])
1531      x6bits = x8bits & 63
1532      x6bit_words.append(base64_alphabet[x6bits])
1533
1534    index += 3
1535
1536  return "".join(x6bit_words)
1537
1538def base64_b64decode(s, altchars=None):
1539  """
1540  <Purpose>
1541    Decode a Base64 encoded string.  The decoder ignores all non
1542    characters not in the Base64 alphabet for compatibility with the
1543    Python library.  However, this introduces a security loophole in
1544    which covert or malicious data may be passed.
1545
1546  <Arguments>
1547    s:
1548      The string to decode.
1549
1550    altchars:
1551      An optional string of at least length 2 (additional characters are
1552      ignored) which specifies an alternative alphabet for the + and /
1553      characters.  The default is None, for which the standard Base64
1554      alphabet is used.
1555
1556  <Exceptions>
1557    None.
1558
1559  <Side Effects>
1560    TypeError on decoding error.
1561
1562  <Returns>
1563    The decoded string.
1564
1565  """
1566  # Build the local alphabet.
1567  if altchars is None:
1568    base64_alphabet = BASE64_ALPHABET
1569  else:
1570    base64_alphabet = BASE64_ALPHABET[:62] + altchars
1571
1572  # Generate the translation maps for decoding a Base64 string.
1573  translate_chars = []
1574  for x in xrange(256):
1575    char = chr(x)
1576    translate_chars.append(char)
1577
1578  # Build the strings of characters to delete.
1579  delete_chars = []
1580  for x in translate_chars:
1581    if x not in base64_alphabet:
1582      delete_chars.append(x)
1583  delete_chars = "".join(delete_chars)
1584
1585  # Insert the 6-bit Base64 values into the translation string.
1586  k = 0
1587  for v in base64_alphabet:
1588    translate_chars[ord(v)] = chr(k)
1589    k += 1
1590  translate_chars = "".join(translate_chars)
1591
1592  # Count the number of padding characters at the end of the string.
1593  num_pad = 0
1594  i = len(s) - 1
1595  while i >= 0:
1596    if s[i] == "=":
1597      num_pad += 1
1598    else:
1599      break
1600    i -= 1
1601
1602  # Translate the string into 6-bit characters and delete extraneous
1603  # characters.
1604  s = s.translate(translate_chars, delete_chars)
1605
1606  # Determine correct alignment by calculating the number of padding
1607  # characters needed for compliance to the specification.
1608  align = (4 - (len(s) & 3)) & 3
1609  if align == 3:
1610    raise TypeError("Incorrectly encoded base64 data (has 6 bits of trailing garbage)")
1611  if align > num_pad:
1612    # Technically, this isn't correctly padded. But it's recoverable, so let's
1613    # not care.
1614    pass
1615
1616  # Change from characters to integers for binary operations.
1617  x6bit_words = []
1618  for x in s:
1619    x6bit_words.append(ord(x))
1620  for x in xrange(align):
1621    x6bit_words.append(-1)
1622
1623  # Decode the 6-bit words into 8-bit words.
1624  bytes = []
1625  index = 0
1626  while True:
1627
1628    # Work on four 6-bit quantities at a time.  End when no more data is
1629    # available.
1630    try:
1631      (x6bits1, x6bits2, x6bits3, x6bits4) = x6bit_words[index:index + 4]
1632    except ValueError:
1633      break
1634
1635    # Save an 8-bit quantity.
1636    bytes.append((x6bits1 << 2) | (x6bits2 >> 4))
1637
1638    # End of valid data.
1639    if x6bits3 < 0:
1640      break
1641
1642    # Save an 8-bit quantity.
1643    bytes.append(((x6bits2 & 15) << 4) | (x6bits3 >> 2))
1644
1645    # End of valid data.
1646    if x6bits4 < 0:
1647      break
1648
1649    # Save an 8-bit quantity.
1650    bytes.append(((x6bits3 & 3) << 6) | x6bits4)
1651
1652    # Next four 6-bit quantities.
1653    index += 4
1654
1655  return "".join([chr(x) for x in bytes])
1656
1657def base64_standard_b64encode(s):
1658  """
1659  <Purpose>
1660    Encode a string using the standard Base64 alphabet.
1661
1662  <Arguments>
1663    s:
1664      The string to encode.
1665
1666  <Exceptions>
1667    None.
1668
1669  <Side Effects>
1670    None.
1671
1672  <Returns>
1673    The encoded string.
1674
1675  """
1676  return base64_b64encode(s)
1677
1678def base64_standard_b64decode(s):
1679  """
1680  <Purpose>
1681    Decode a Base64 encoded string using the standard Base64 alphabet.
1682
1683  <Arguments>
1684    s:
1685      The string to decode.
1686
1687  <Exceptions>
1688    None.
1689
1690  <Side Effects>
1691    TypeError on decoding error.
1692
1693  <Returns>
1694    The decoded string.
1695
1696  """
1697  return base64_b64decode(s)
1698
1699
1700def base64_urlsafe_b64encode(s):
1701  """
1702  <Purpose>
1703    Encode a string using a URL-safe alphabet, which substitutes -
1704    instead of + and _ instead of / in the standard Base64 alphabet.
1705
1706  <Arguments>
1707    s:
1708      The string to encode.
1709
1710  <Exceptions>
1711    None.
1712
1713  <Side Effects>
1714    None.
1715
1716  <Returns>
1717    The encoded string.
1718
1719  """
1720  return base64_b64encode(s, "-_")
1721
1722
1723def base64_urlsafe_b64decode(s):
1724  """
1725  <Purpose>
1726    Decode a Base64 encoded string using a URL-safe alphabet, which
1727    substitutes - instead of + and _ instead of / in the standard Base64
1728    alphabet.
1729
1730  <Arguments>
1731    s:
1732      The string to decode.
1733
1734  <Exceptions>
1735    None.
1736
1737  <Side Effects>
1738    TypeError on decoding error.
1739
1740  <Returns>
1741    The decoded string.
1742
1743  """
1744  return base64_b64decode(s, "-_")
1745
1746#end include base64.repy
1747#begin include xmlparse.repy
1748"""
1749<Program Name>
1750  xmlparse.repy
1751
1752<Started>
1753  April 2009
1754
1755<Author>
1756  Conrad Meyer <cemeyer@u.washington.edu>
1757
1758<Purpose>
1759  Provide a relatively simplistic but usable xml parsing library for
1760  RePy.
1761"""
1762
1763class xmlparse_XMLParseError(Exception):
1764  """Exception raised when an error is encountered parsing the XML."""
1765  pass
1766
1767
1768
1769
1770class xmlparse_XMLTreeNode:
1771  """
1772  <Purpose>
1773    Provide a simple tree structure for XML data.
1774
1775  <Exceptions>
1776    None.
1777
1778  <Example Use>
1779    node = xmlparse_parse("<Some><xml><data></data></xml></Some>")
1780  """   
1781
1782
1783  def __init__(self, tag_name):
1784    self.tag_name = tag_name
1785    self.children = None
1786    self.content = None
1787    self.attributes = {}
1788
1789
1790  def __repr__(self):
1791    """Provide a pretty representation of an XML tree."""
1792
1793    if self.content is not None:
1794      return "%s:\"%s\"" % (self.tag_name, self.content)
1795    else:
1796      return "%s:%s" % (self.tag_name, str(self.children))
1797
1798
1799  def to_string(self):
1800    result = "<" + self.tag_name
1801    for attribute_name in self.attributes.keys():
1802      attribute_value_escaped = \
1803          self.attributes[attribute_name].replace("\"", "\\\"")
1804      result += " " + attribute_name + "=\"" + attribute_value_escaped + "\""
1805   
1806    if self.content is None:
1807      result += ">"
1808      for childnode in self.children:
1809        result += childnode.to_string()
1810      result += "</" + self.tag_name + ">"
1811    else:
1812      if len(self.content) == 0:
1813        result += "/>"
1814      else:
1815        result += ">" + self.content + "</" + self.tag_name + ">"
1816
1817    return result
1818
1819
1820
1821
1822def xmlparse_parse(data):
1823  """
1824  <Purpose>
1825    Parses an XML string into an xmlparse_XMLTreeNode containing the root
1826    item.
1827
1828  <Arguments>
1829    data:
1830           The data to parse.
1831
1832  <Exceptions>
1833    xmlparse_XMLParseError if parsing fails.
1834
1835  <Side Effects>
1836    None.
1837
1838  <Returns>
1839    An xmlparse_XMLTreeNode tree.
1840  """
1841
1842  data = data.lstrip()
1843  if data.startswith("<?xml"):
1844    data = data[data.find("?>")+2:]
1845 
1846  # Well-formed XML Documents have exactly one root node
1847  parsed_elements = _xmlparse_parse(data)
1848  if len(parsed_elements) != 1:
1849    raise xmlparse_XMLParseError("XML response from server contained more than one root node")
1850
1851  return parsed_elements[0]
1852
1853
1854
1855
1856def _xmlparse_read_attributes(string):
1857  # Returns a pair containing the dictionary of attributes and remainder
1858  # of the string on success; excepts on failure.
1859
1860  # Q_n corresponds to the state_* constant of the same value. The starting
1861  # node is Q_1.
1862  #
1863  #  [ Done ]
1864  #     ^
1865  #     |
1866  #     | (>, /)
1867  #     |
1868  #     \--------\
1869  #              |
1870  #        ,-.   | v-----------------------------------\
1871  # space (   [ Q_1 ]                                  |
1872  #        `->   | ^-----------------------\ (')       |
1873  #              |                         |           |
1874  #              |                (')      |   <-.     | (")
1875  #              | non-space   /------->[ Q_4 ]   )    |
1876  #              |             |               `-'     |
1877  #  (space)     v     (=)     |             (other)   |
1878  #     /-----[ Q_2 ]------>[ Q_3 ]-------->[ Q_5 ]----/
1879  #     |      ^   )           |      (")    ^   )
1880  #     |       `-'            |              `-'
1881  #     |     (other)   (other)|             (other)
1882  #     |                      |
1883  #     v                      |
1884  #[Exception]<----------------/
1885
1886  # Basically the state machine is used to read a list of attribute-pairs,
1887  # terminated by a '/' or '>'. Attribute pairs either look like:
1888  #   name='value'
1889  # or:
1890  #   name="value"
1891  # Single-quoted attributes can contain double-quotes, and vice-versa, but
1892  # single-quotes in single-quoted attributes must be escaped.
1893  #
1894  # To do this we start in Q_1, which consumes input until we arrive at
1895  # something that looks like an attribute name. In Q_2 we consume characters
1896  # for the attribute name until it looks like the attribute name is finished.
1897  # In Q_3 we read a single character to determine what type of quoting is
1898  # used for the attribute value. In Q_4 or Q_5, we read the attribute's value
1899  # until the string is closed, then go back to Q_1 (saving the attribute name
1900  # and value into the dictionary). We decide we are done when we encounter a
1901  # '>' or '/' in Q_1.
1902
1903  # Constant states:
1904  state_EXPECTING_ATTRNAME = 1
1905  state_READING_ATTRNAME = 2
1906  state_EXPECTING_ATTRVALUE = 3
1907  state_READING_ATTRVALUE_SINGLEQUOTE = 4
1908  state_READING_ATTRVALUE_DOUBLEQUOTE = 5
1909
1910  current_position = 0
1911  current_state = 1
1912  current_attrname = ""
1913  current_attrvalue = ""
1914  attributes = {}
1915
1916  while True:
1917    if current_position >= len(string):
1918      raise xmlparse_XMLParseError(
1919          "Failed to parse element attribute list -- input ran out " + \
1920              "before we found a closing '>' or '/'")
1921
1922    current_character = string[current_position]
1923
1924    if current_state == state_EXPECTING_ATTRNAME:
1925      if current_character.isspace():
1926        pass    # We stay in this state
1927      elif current_character == '>' or current_character == '/':
1928        # We're finished reading attributes
1929        return (attributes, string[current_position:])
1930      else:
1931        current_attrname += current_character
1932        current_state = state_READING_ATTRNAME
1933
1934    elif current_state == state_READING_ATTRNAME:
1935      if current_character.isspace():
1936        raise xmlparse_XMLParseError(
1937            "Failed to parse element attribute list -- attribute " + \
1938                "ended unexpectedly with a space")
1939      elif current_character == "=":
1940        current_state = state_EXPECTING_ATTRVALUE
1941      else:
1942        current_attrname += current_character
1943
1944    elif current_state == state_EXPECTING_ATTRVALUE:
1945      if current_character == '\'':
1946        current_state = state_READING_ATTRVALUE_SINGLEQUOTE
1947      elif current_character == '"':
1948        current_state = state_READING_ATTRVALUE_DOUBLEQUOTE
1949      else:
1950        raise xmlparse_XMLParseError(
1951            "Failed to parse element attribute list -- attribute " + \
1952                "values must be quoted")
1953
1954    elif current_state == state_READING_ATTRVALUE_SINGLEQUOTE:
1955      if current_character == '\'':
1956        attributes[current_attrname] = current_attrvalue
1957        current_state = state_EXPECTING_ATTRNAME
1958        current_attrname = ""
1959        current_attrvalue = ""
1960      else:
1961        current_attrvalue += current_character
1962
1963    elif current_state == state_READING_ATTRVALUE_DOUBLEQUOTE:
1964      if current_character == '"':
1965        attributes[current_attrname] = current_attrvalue
1966        current_state = state_EXPECTING_ATTRNAME
1967        current_attrname = ""
1968        current_attrvalue = ""
1969      else:
1970        current_attrvalue += current_character
1971
1972    current_position += 1
1973
1974
1975
1976
1977def _xmlparse_node_from_string(string):
1978  # string:
1979  #   <tag attr1="value" attr2='value'>content</tag>
1980  # Content may be a string or a list of children nodes depending on if the
1981  # first non-space character is a '<' or not.
1982
1983  string = string.lstrip()
1984  if not string.startswith("<"):
1985    raise xmlparse_XMLParseError("Error parsing XML -- doesn't " + \
1986        "start with '<'")
1987
1988  string = string[1:]
1989
1990  read_pos = 0
1991  while True:
1992    if read_pos >= len(string):
1993      raise xmlparse_XMLParseError("Error parsing XML -- parser " + \
1994          "ran out of input trying to read a tag")
1995
1996    # The tag name is ended with a space or a closing angle-brace or
1997    # a "/".
1998    curchar = string[read_pos]
1999    if curchar.isspace() or curchar == ">" or curchar == "/":
2000      break
2001
2002    read_pos += 1
2003
2004  tag = string[0:read_pos]
2005  string = string[read_pos:]
2006
2007  # Get the attribute dictionary and remaining string (which will be
2008  # "> ... [ inner stuff ] </[tag_name]>" or "/>" for well-formed XML).
2009  attributes, string = _xmlparse_read_attributes(string)
2010
2011  # "Empty" elements look like: "<[tag_name] [... maybe attributes] />" and
2012  # not "Empty" elements look like:
2013  # "<[tag_name] [... maybe attributes]> [inner content] </[tag_name]>".
2014  empty_element = False
2015  if string.startswith(">"):
2016    string = string[1:]
2017  elif string.startswith("/>"):
2018    string = string[2:]
2019    empty_element = True
2020
2021  xmlnode = xmlparse_XMLTreeNode(tag)
2022  xmlnode.attributes = attributes
2023
2024  if empty_element:
2025    xmlnode.content = ""
2026
2027  else:
2028    # Locate the end-boundary of the inner content of this element.
2029    ending_tag_position = string.rfind("</")
2030    if ending_tag_position < 0:
2031      raise xmlparse_XMLParseError("XML parse error -- could not " + \
2032          "locate closing tag")
2033
2034    # If this elements starting and closing tag names do not match, this XML
2035    # is not well-formed.
2036    if not string.startswith("</" + tag, ending_tag_position):
2037      raise xmlparse_XMLParseError("XML parse error -- different " + \
2038          "opening / closing tags at the same nesting level")
2039
2040    # If the inner content starts with another element, this element has
2041    # children.  Otherwise, it has content, which is just a string containing
2042    # the inner content.
2043    tag_body = string[:ending_tag_position]
2044    if tag_body.lstrip().startswith("<"):
2045      xmlnode.children = _xmlparse_parse(tag_body.lstrip())
2046    else:
2047      xmlnode.content = tag_body
2048
2049  return xmlnode
2050
2051
2052
2053
2054def _xmlparse_find_next_tag(xmldata):
2055  # Finds the position of the start of the next same-level tag in this XML
2056  # document.
2057
2058  read_position = 0
2059  nested_depth = 0
2060
2061  original_length = len(xmldata)
2062  xmldata = xmldata.lstrip()
2063  length_difference = original_length - len(xmldata)
2064
2065  # Seek to another XML tag at the same depth.
2066  while True:
2067    if xmldata.startswith("</", read_position) or \
2068        xmldata.startswith("/>", read_position):
2069      nested_depth -= 1
2070    elif xmldata.startswith("<", read_position):
2071      nested_depth += 1
2072
2073    read_position += 1
2074
2075    if read_position >= len(xmldata):
2076      return read_position + length_difference
2077
2078    if nested_depth == 0:
2079      nexttagposition = xmldata.find("<", read_position)
2080
2081      if nexttagposition < 0:
2082        return original_length
2083      else:
2084        return nexttagposition + length_difference
2085
2086
2087
2088
2089def _xmlparse_parse(xmldata):
2090  # Takes a raw XML stream and returns a list of XMLTreeNodes.
2091
2092  nodelist = []
2093
2094  while True:
2095    # Whitespace between tags isn't important to the content of
2096    # an XML document.
2097    xmldata = xmldata.strip()
2098
2099    # Strip out XML comments.
2100    if xmldata.startswith("<!--"):
2101      commentendloc = xmldata.find("-->", 4)
2102      if commentendloc < 0:
2103        raise xmlparse_XMLParseError("XML parse error -- comment " + \
2104            "missing close tag ('-->')")
2105      xmldata = xmldata[commentendloc+3:]
2106      continue
2107
2108    # Find the end of the current tag.
2109    nexttagend = _xmlparse_find_next_tag(xmldata)
2110
2111    thisnode_str = xmldata[0:nexttagend]
2112    xmldata = xmldata[nexttagend:]
2113
2114    # Parse a tag out of the string we just found.
2115    thisnode = _xmlparse_node_from_string(thisnode_str)
2116    nodelist.append(thisnode)
2117
2118    if not xmldata.strip().startswith("<"):
2119      break
2120
2121  return nodelist
2122
2123#end include xmlparse.repy
2124
2125
2126
2127
2128
2129class xmlrpc_common_Binary(object):
2130  """
2131  <Purpose>
2132    Wrapper class for base64-encoded binary data in XML-RPC requests and
2133    responses.  This class is used when sending and receiving binary
2134    data through XML-RPC.
2135
2136  <Side Effects>
2137    None.
2138
2139  <Example Use>
2140    blob = xmlrpc_common_Binary("\x00\x01\x00")
2141
2142  """
2143
2144  def __init__(self, data=""):
2145    """
2146    <Purpose>
2147      Create a new Binary wrapper object for use with the XML-RPC
2148      libraries.
2149
2150    <Arguments>
2151      data:
2152        The unencoded binary data.
2153
2154    <Exceptions>
2155      None.
2156
2157    """
2158    self.data = data
2159
2160
2161
2162
2163
2164class xmlrpc_common_Fault(ValueError):
2165  """
2166  <Purpose>
2167    Exception representing a XML-RPC Fault.  The exception is returned
2168    by the parsing functions when a XML-RPC server returns a fault.
2169
2170  <Side Effects>
2171    None.
2172
2173  <Example Use>
2174    raise xmlrpc_common_Fault("An error occurred", -1)
2175
2176  """
2177
2178  def __init__(self, message, code):
2179    """
2180    <Purpose>
2181      Create a new Fault exception.
2182
2183    <Arguments>
2184      message:
2185        A string describing the fault.
2186
2187      code:
2188        The integer code associated with the fault.
2189
2190    <Exceptions>
2191      None.
2192
2193    """
2194    self.strerror = message
2195    self.code = code
2196    ValueError.__init__(self, message)
2197
2198
2199
2200
2201
2202class xmlrpc_common_Timeout(Exception):
2203  """
2204  <Purpose>
2205    Exception representing a normal timeout occuring.
2206
2207  <Side Effects>
2208    None.
2209
2210  <Example Use>
2211    raise xmlrpc_common_Timeout()
2212
2213  """
2214
2215
2216
2217
2218
2219class xmlrpc_common_XMLParseError(ValueError):
2220  """
2221  <Purpose>
2222    Exception representing an error in parsing XML-RPC data.  The
2223    exception is thrown when bad XML data is encountered.
2224
2225  <Side Effects>
2226    None.
2227
2228  <Example Use>
2229    raise xmlrpc_common_XMLParseError()
2230
2231  """
2232
2233
2234
2235
2236
2237class xmlrpc_common_ConnectionError(ValueError):
2238  """
2239  <Purpose>
2240    Exception representing an error in the connection to an XMLRPC server.
2241    Thrown when the server closes the connection unexpectedly.
2242
2243  <Side Effects>
2244    None.
2245
2246  <Example Use>
2247    raise xmlrpc_common_ConnectionError()
2248
2249  """
2250
2251
2252
2253
2254
2255def xmlrpc_common_call2xml(method_name, params):
2256  """
2257  <Purpose>
2258    Build a XML-RPC method call to send to a XML-RPC server.
2259
2260  <Arguments>
2261    method_name:
2262      The method name.
2263
2264    params:
2265      A sequence type of XML-RPC parameters.  A dictionary may also be
2266      passed, but the keys are ignored.
2267
2268  <Exceptions>
2269    None.
2270
2271  <Side Effects>
2272    None.
2273
2274  <Returns>
2275    The XML-RPC method call string.
2276
2277  """
2278  xml_string = ['<?xml version="1.0"?>',
2279    "<methodCall><methodName>%s</methodName>" % method_name,
2280    _xmlrpc_common_params2xml(params),
2281    "</methodCall>"]
2282
2283  return "".join(xml_string)
2284
2285
2286def xmlrpc_common_response2xml(param):
2287  """
2288  <Purpose>
2289    Build a XML-RPC method response to send to a XML-RPC client.  This
2290    is the XML document that represents the return values or fault from
2291    a XML-RPC call.
2292
2293  <Arguments>
2294    param:
2295      The value to be returned.
2296
2297  <Exceptions>
2298    None.
2299
2300  <Side Effects>
2301    None.
2302
2303  <Returns>
2304    The XML-RPC method response string.
2305
2306  """
2307  xml_string = ['<?xml version="1.0"?><methodResponse>',
2308    _xmlrpc_common_params2xml((param,)),
2309    "</methodResponse>"]
2310
2311  return "".join(xml_string)
2312
2313
2314def xmlrpc_common_fault2xml(message, code):
2315  """
2316  <Purpose>
2317    Build a XML-RPC fault response to send to a XML-RPC client.  A fault
2318    response can occur from a server failure, an incorrectly generated
2319    XML request, or bad program arguments.
2320
2321  <Arguments>
2322    message:
2323      A string describing the fault.
2324
2325    code:
2326      The integer code associated with the fault.
2327
2328  <Exceptions>
2329    None.
2330
2331  <Side Effects>
2332    None.
2333
2334  <Returns>
2335    The XML-RPC fault response string.
2336
2337  """
2338  struct = {"faultCode": code, "faultString": message}
2339  xml_string = ['<?xml version="1.0"?><methodResponse><fault>',
2340    _xmlrpc_common_value2xml(struct),
2341    "</fault></methodResponse>"]
2342
2343  return "".join(xml_string)
2344
2345
2346def _xmlrpc_common_params2xml(params):
2347  """
2348  <Purpose>
2349    Translate Python parameter values to XML-RPC for use in building a
2350    XML-RPC request or response.
2351
2352  <Arguments>
2353    params:
2354      A sequence type of XML-RPC parameters.  A dictionary may also be
2355      passed, but the keys are ignored.
2356
2357  <Exceptions>
2358    None.
2359
2360  <Side Effects>
2361    None.
2362
2363  <Returns>
2364    The XML-RPC parameters string.
2365
2366  """
2367  if params is None or params is ():
2368    return ""
2369
2370  xml_string = ["<params>"]
2371
2372  for param in params:
2373    xml_string.append("<param>%s</param>" % _xmlrpc_common_value2xml(param))
2374
2375  xml_string.append("</params>")
2376
2377  return "".join(xml_string)
2378
2379
2380def _xmlrpc_common_value2xml(obj):
2381  """
2382  <Purpose>
2383    Translate a Python value to XML-RPC for use in building the params
2384    portion of a request or response.
2385
2386  <Arguments>
2387    obj:
2388      The Python object to convert.
2389
2390  <Exceptions>
2391    None.
2392
2393  <Side Effects>
2394    None.
2395
2396  <Returns>
2397    The XML-RPC value string.
2398
2399  """
2400  object_type = type(obj)
2401
2402  xml_string = ["<value>"]
2403
2404  if obj is None:
2405    xml_string.append("<nil/>")
2406
2407  elif object_type is bool:
2408    xml_string.append("<boolean>%d</boolean>" % int(obj))
2409
2410  elif object_type in (int, long):
2411    xml_string.append("<int>%d</int>" % obj)
2412
2413  elif object_type is float:
2414    xml_string.append("<double>%f</double>" % obj)
2415
2416  elif object_type in (str, unicode, basestring):
2417    xml_string.append("<string>%s</string>" % obj)
2418
2419  elif object_type in (list, tuple, xrange, set, frozenset):
2420    xml_string.append("<array><data>")
2421    for value in obj:
2422      xml_string.append(_xmlrpc_common_value2xml(value))
2423    xml_string.append("</data></array>")
2424
2425  elif object_type is dict:
2426    xml_string.append("<struct>")
2427    for key, value in obj.iteritems():
2428      xml_string.append("<member><name>%s</name>" % key)
2429      xml_string.append(_xmlrpc_common_value2xml(value))
2430      xml_string.append("</member>")
2431    xml_string.append("</struct>")
2432
2433  # This requires the new object inheritance model to be used. e.g. do
2434  #   class Foo(object): pass
2435  # rather than
2436  #   class Foo: pass
2437  elif object_type is xmlrpc_common_Binary:
2438    xml_string.append("<base64>%s</base64>" % base64_standard_b64encode(obj.data))
2439
2440  else:
2441    raise ValueError("Marshaller: Unsupported type '%s'" % type(obj))
2442
2443  xml_string.append("</value>")
2444
2445  return "".join(xml_string)
2446
2447
2448def xmlrpc_common_call2python(xml):
2449  """
2450  <Purpose>
2451    Convert a XML-RPC method call to its Python equivalent.
2452
2453    The request from a XML-RPC client is parsed into native Python
2454    types so that the server may use the data to execute a method, as
2455    appropriate.
2456
2457  <Arguments>
2458    xml:
2459      The XML-RPC string to convert.
2460
2461  <Exceptions>
2462    xmlrpc_common_XMLParseError on a XML-RPC structural parse error.
2463    xmlparse_XMLParseError on a general XML parse error.
2464
2465  <Side Effects>
2466    None.
2467
2468  <Returns>
2469    A tuple containing (1) the method name and (2) a list of the
2470    parameters.
2471
2472  """
2473  xml_node = xmlparse_parse(xml)
2474
2475  if xml_node.tag_name != "methodCall":
2476    message = "Unexpected root node: %s" % xml_node.tag_name
2477    raise xmlrpc_common_XMLParseError(message)
2478  elif xml_node.children is None:
2479    raise xmlrpc_common_XMLParseError("No parameters found")
2480  elif len(xml_node.children) > 2:
2481    raise xmlrpc_common_XMLParseError("Too many children for 'methodCall'")
2482
2483  try:
2484    method_name_node = xml_node.children[0]
2485    if method_name_node.tag_name != "methodName":
2486      message = "Unexpected XML node: %s" % method_name_node.tag_name
2487      raise xmlrpc_common_XMLParseError(message)
2488    method_name = method_name_node.content
2489  except IndexError:
2490    raise xmlrpc_common_XMLParseError("No method name found")
2491
2492  try:
2493    params = _xmlrpc_common_params2python(xml_node.children[1])
2494  except IndexError:
2495    return (method_name, ())
2496
2497  if not params:
2498    raise xmlrpc_common_XMLParseError("No parameters found")
2499
2500  return (method_name, params)
2501
2502
2503def xmlrpc_common_response2python(xml):
2504  """
2505  <Purpose>
2506    Convert a XML-RPC method response to its Python equivalent.
2507
2508    The response from a XML-RPC server is parsed into native Python
2509    types so that the client may use the data as appropriate.
2510
2511  <Arguments>
2512    xml:
2513      The XML-RPC string to convert.
2514
2515  <Exceptions>
2516    xmlrpc_common_XMLParseError on a XML-RPC structural parse error.
2517    xmlparse_XMLParseError on a general XML parse error.
2518
2519  <Side Effects>
2520    None.
2521
2522  <Returns>
2523    The method results or a xmlrpc_common_Fault on reading a fault.
2524
2525  """
2526  xml_node = xmlparse_parse(xml)
2527
2528  if xml_node.tag_name != "methodResponse":
2529    message = "Unexpected root node: %s" % xml_node.tag_name
2530    raise xmlrpc_common_XMLParseError(message)
2531  elif xml_node.children is None:
2532    raise xmlrpc_common_XMLParseError("No parameters found")
2533  elif len(xml_node.children) > 1:
2534    raise xmlrpc_common_XMLParseError("Too many children for 'methodCall'")
2535
2536  fault_node = xml_node.children[0]
2537  if fault_node.tag_name == "fault":
2538    if fault_node.children is None:
2539      raise xmlrpc_common_XMLParseError("No children found for 'fault'")
2540    elif len(fault_node.children) != 1:
2541      raise xmlrpc_common_XMLParseError("Too many children for 'fault'")
2542    params = _xmlrpc_common_value2python(fault_node.children[0])
2543    try:
2544      return xmlrpc_common_Fault(params["faultString"], params["faultCode"])
2545    except KeyError:
2546      raise xmlrpc_common_XMLParseError("Invalid fault object")
2547
2548  try:
2549    params = _xmlrpc_common_params2python(xml_node.children[0])
2550  except KeyError:
2551    raise xmlrpc_common_XMLParseError("No parameters found")
2552
2553  if len(params) != 1:
2554    raise xmlrpc_common_XMLParseError("Too many children for 'params'")
2555
2556  return params[0]
2557
2558
2559def _xmlrpc_common_params2python(xml_node):
2560  """
2561  <Purpose>
2562    Convert XML-RPC params the Python equivalent.
2563
2564    The parameters portion of a XML-RPC request or response is parsed
2565    into Python equivalents so that the method request and response
2566    parsing functions can return the relevant data.
2567
2568  <Arguments>
2569    xml_node:
2570      The XML node to consider.
2571
2572  <Exceptions>
2573    xmlrpc_common_XMLParseError on a XML-RPC structural parse error.
2574
2575  <Side Effects>
2576    None.
2577
2578  <Returns>
2579    The method results.
2580
2581  """
2582  if xml_node.tag_name != "params":
2583    message = "Unexpected XML node: %s" % xml_node.tag_name
2584    raise xmlrpc_common_XMLParseError(message)
2585
2586  if xml_node.children is None or len(xml_node.children) < 1:
2587    return []
2588
2589  params = []
2590
2591  for param_node in xml_node.children:
2592    if param_node.tag_name != "param":
2593      message = "Unexpected XML node: %s" % param_node.tag_name
2594      raise xmlrpc_common_XMLParseError(message)
2595    elif param_node.children is None:
2596      raise xmlrpc_common_XMLParseError("Unexpected empty param node")
2597    elif len(param_node.children) > 1:
2598      raise xmlrpc_common_XMLParseError("Too many children for 'param'")
2599    params.append(_xmlrpc_common_value2python(param_node.children[0]))
2600
2601  return params
2602
2603
2604def _xmlrpc_common_value2python(xml_node):
2605  """
2606  <Purpose>
2607    Convert a XML-RPC value the Python equivalent.
2608
2609    A XML-RPC value is converted to its Python equivalent for use in the
2610    parameters parser.
2611
2612  <Arguments>
2613    xml_node:
2614      The XML node to consider.
2615
2616  <Exceptions>
2617    xmlrpc_common_XMLParseError on a XML-RPC structural parse error.
2618
2619  <Side Effects>
2620    None.
2621
2622  <Returns>
2623    The method results.
2624
2625  """
2626
2627  if xml_node.tag_name not in ("value",):
2628    message = "Unexpected XML node: %s" % xml_node.tag_name
2629    raise xmlrpc_common_XMLParseError(message)
2630
2631  # The values that XMLRPC can encode have an optional type-specifier.
2632  # If the type-specifier is not included, the data is simply a string
2633  # and doesn't need any other special interpretation. Additionally, there
2634  # is an optional <string> type specifier, but e.g. openDHT doesn't use
2635  # it. If xml_node.children is None here, the data lacks a type-specifying
2636  # tag, so it is to be interpreted as a string.
2637  elif xml_node.children is not None and len(xml_node.children) > 1:
2638    raise xmlrpc_common_XMLParseError("Too many children for 'value'")
2639
2640  value_node = xml_node
2641
2642  # Assume string by default, as explained earlier.
2643  tag = "string"
2644  if xml_node.children is not None:
2645    # If the xml specifies a type, override the default.
2646    value_node = xml_node.children[0]
2647    tag = value_node.tag_name
2648
2649  # The string contents of the <value> tag (or of the type-specifying tag
2650  # inside <value>, if one exists).
2651  value = value_node.content
2652
2653  if tag == "nil":
2654    return None
2655
2656  elif tag == "boolean":
2657    return bool(int(value))
2658
2659  elif tag in ("i4", "i8", "int"): ### AR hack to include i8. Could be "long".
2660    return int(value)
2661
2662  elif tag == "double":
2663    return float(value)
2664
2665  elif tag == "string":
2666    return value
2667
2668  elif tag == "array":
2669    if len(value_node.children) > 1:
2670      raise xmlrpc_common_XMLParseError("Too many children for 'array'")
2671    # Arrays are encoded as:  <array><data>
2672    #                           <value>...</value>
2673    #                           ...
2674    #                         </data></array>
2675    data_node = value_node.children[0]
2676    result = []
2677    if data_node.children:
2678      for item_node in data_node.children:
2679        result.append(_xmlrpc_common_value2python(item_node))
2680    return result
2681
2682  elif tag == "struct":
2683    result = {}
2684
2685    # Structs are encoded as: <struct>
2686    #                           <member><name>...</name><value>...</value></member>
2687    #                           ...
2688    #                         </struct>
2689    # Keys (<name>) do not contain type information, so they are strings
2690    # as far as XMLRPC is concerned.
2691    for member_node in value_node.children:
2692      if len(member_node.children) != 2:
2693        message = "Incorrect number of children for 'member'"
2694        raise xmlrpc_common_XMLParseError(message)
2695
2696      key = member_node.children[0].content
2697      value = _xmlrpc_common_value2python(member_node.children[1])
2698
2699      result[key] = value
2700
2701    return result
2702
2703  elif tag == "base64":
2704    return xmlrpc_common_Binary(base64_standard_b64decode(value_node.content))
2705
2706  else:
2707    message = "Demarshaller: Unsupported value type: %s" % value_node.tag_name
2708    raise xmlrpc_common_XMLParseError(message)
2709
2710#end include xmlrpc_common.repy
2711
2712
2713class xmlrpc_client_Client(object):
2714  """
2715  <Purpose>
2716    XML-RPC client implementation.
2717
2718  <Side Effects>
2719    None.
2720
2721  <Example Use>
2722    client = xmlrpc_client_Client("http://phpxmlrpc.sourceforge.net/server.php")
2723    print client.send_request("examples.getStateName", (1,))
2724
2725  """
2726
2727
2728  USER_AGENT = "seattlelib/1.0.0"
2729
2730
2731  def __init__(self, url):
2732    """
2733    <Purpose>
2734      Create a new XML-RPC Client object to do RPC calls to the given
2735      server.
2736
2737    <Arguments>
2738      url:
2739        A url containing the hostname, port, and path of the xmlrpc
2740        server. For example, "http://phpxmlrpc.soureforge.net/server.php".
2741
2742    <Exceptions>
2743      None.
2744
2745    """
2746
2747    if not isinstance(url, (str, unicode)):
2748      raise ValueError("Invalid argument: url must be a URL string")
2749
2750    urlcomponents = urlparse_urlsplit(url, "http", False)
2751
2752    self.server_host = urlcomponents["hostname"]
2753    self.server_port = urlcomponents["port"] or 80
2754    self.server_path = urlcomponents["path"] or "/"
2755    if urlcomponents["query"]:
2756      self.server_path += "?" + urlcomponents["query"]
2757
2758    if not self.server_host:
2759      raise ValueError("Invalid argument: url must have a valid host")
2760
2761
2762  def send_request(self, method_name, params, timeout=None):
2763    """
2764    <Purpose>
2765      Send a XML-RPC request to a XML-RPC server to do a RPC call.
2766
2767    <Arguments>
2768      method_name:
2769        The method name.
2770
2771      params:
2772        The method parameters.
2773
2774    <Exceptions>
2775      socket.error on socket errors, including server timeouts.
2776      xmlrpc_common_Fault on a XML-RPC response fault.
2777      xmlrpc_common_XMLParseError on a XML-RPC structural parse error.
2778      xmlparse_XMLParseError on a general XML parse error.
2779      xmlrpc_common_ConnectionError on unexpected disconnects.
2780      xmlrpc_common_Timeout if the time limit is exceeded.
2781
2782    <Side Effects>
2783      None.
2784
2785    <Returns>
2786      The XML-RPC method return values.
2787
2788    """
2789
2790    starttime = getruntime()
2791
2792    # Prepare the XML request.
2793    request_xml = xmlrpc_common_call2xml(method_name, params)
2794
2795    response = httpretrieve_get_string("http://%s:%s%s" % (self.server_host, \
2796        self.server_port, self.server_path), postdata=request_xml, \
2797        timeout=timeout, httpheaders={\
2798        "User-Agent": self.USER_AGENT, "Content-Type": "text/xml"})
2799
2800    # Timeout if the POST took too long.
2801    if timeout is not None and getruntime() - starttime > timeout:
2802      raise xmlrpc_common_Timeout()
2803
2804    # Parse the XML response body into Python values.
2805    response_value = xmlrpc_common_response2python(response)
2806
2807    # If a fault was decoded, raise the exception.
2808    if isinstance(response_value, xmlrpc_common_Fault):
2809      raise response_value
2810
2811    # Otherwise, return the results.
2812    return response_value
2813
2814#end include xmlrpc_client.repy
2815
2816
2817
2818
2819
2820def find_available_sensors_and_methods(xmlrpc_socket):
2821  s = xmlrpc_socket
2822  # Get the list of available methods
2823  methodlist = s.send_request("system.listMethods", ())
2824
2825  # Make sure it is a list, not a single string
2826  if not isinstance(methodlist, list):
2827    methodlist = [methodlist]
2828
2829  print methodlist
2830
2831  for methodname in methodlist:
2832    # NOTE: Wrap the methodname string inside a tuple, otherwise
2833    # xmlrpc_common will treat the string's characters as separate params
2834    thissignature = s.send_request("system.methodSignature", (methodname,))
2835
2836    # BUG: This is true for all signatures, but shouldn't be.
2837    if thissignature == 'signatures not supported':
2838      print 'Method:', methodname, 'has an unknown signature.'
2839    else: 
2840      print 'Method:',methodname,'has the signature', thissignature
2841
2842    if thissignature == ():
2843      print 'Empty call returns:', s.send_request(methodname, ())
2844
2845
2846
2847
2848
2849if callfunc=="initialize":
2850  print "*** Hello!!"
2851  for sensorport in [63090, 63096, 63097, 63098, 63099, 63095, 
2852    63091, 63092, 63093, 63094]:
2853    try:
2854      s = xmlrpc_client_Client('http://localhost:'+str(sensorport))
2855    except socket.error:
2856      print 'port:', sensorport, 'failed.   Trying backup port...' 
2857      continue
2858    print "*** Connected to port", sensorport
2859    break
2860  else:
2861    print 'Could not locate a port!'
2862    exitall()
2863
2864
2865  # Sometimes the XML-RPC interface changes. Give o3gm_pickup
2866  # any call arg to make it browse the sensor methods.
2867  if len(callargs) > 1:
2868    print "*** Browsing for sensors and methods...."
2869    find_available_sensors_and_methods(s)
2870    "*** Done finding sensor methods, exiting."
2871    exitall()
2872
2873
2874
2875  # should check the error status...
2876  try: 
2877    if not s.send_request("isSeattleSensor", []):
2878      print 'something other than a Seattle sensor was bound to the port...'
2879      exitall()
2880  except Exception, e:
2881    print 'Got an error when checking if this is a Seattle sensor:', str(e)
2882    exitall()
2883
2884
2885  # Collect device vendor/name/tac once, and radio mcc/mnc/cid/nwtype/rssi,
2886  # gps data, and network location (in case gps fails) regularly.
2887
2888  vendor = s.send_request("DeviceInfoSensor.vendorname", ())
2889  model = s.send_request("DeviceInfoSensor.modelname", ())
2890  tac = s.send_request("DeviceInfoSensor.tac", ())
2891  print "*** This is a", model, "by", vendor, "with TAC", tac
2892
2893  results_file = open("o3gm_readings.txt", "a")
2894
2895  gps_last_timestamp = 0L
2896
2897  while True:
2898    try:
2899      # Back off if we're running low on battery
2900      BATTERY_LOW_LEVEL = 15
2901      battery_level = s.send_request("BatterySensor.level", ())
2902      while battery_level < BATTERY_LOW_LEVEL:
2903        pause = (BATTERY_LOW_LEVL - battery_level) * 120
2904        print "*** We've been draining the battery. Let's sleep", pause, "seconds."
2905        sleep(pause)
2906        battery_level = s.send_request("BatterySensor.level", ())
2907
2908      gps_timestamp = s.send_request("GPSLocationSensor.timestamp", ())
2909      if gps_last_timestamp == gps_timestamp:
2910        # This GPS datum is stale. Let's use NW locations instead...
2911        loc_source = "network"
2912        timestamp = s.send_request("RadioSensor.timestamp", ())
2913        lat = s.send_request("NetworkLocationSensor.latitude", ())
2914        lon = s.send_request("NetworkLocationSensor.longitude", ())
2915        alt = -99999.
2916        accuracy = s.send_request("NetworkLocationSensor.accuracy", ())
2917      else: # We have current GPS data
2918        loc_source = "GPS"
2919        timestamp = gps_last_timestamp = gps_timestamp
2920        lat = s.send_request("GPSLocationSensor.latitude", ())
2921        lon = s.send_request("GPSLocationSensor.longitude", ())
2922        alt = s.send_request("GPSLocationSensor.altitude", ())
2923        accuracy = s.send_request("GPSLocationSensor.accuracy", ())
2924
2925      print "Time: ", getruntime()
2926      print "Location Source: ", loc_source
2927      print "Location: (%f, %f), Accuracy: %f " % (lat, lon, accuracy)
2928      print "Battery level: %d percent, Tac: %s, Vendor: %s, Model: %s" % (battery_level, tac, vendor, model)
2929      print
2930
2931    except Exception, e:
2932      print "*** Oops,", str(e)
2933      pause = 15
2934      print "*** Sleeping", pause, "seconds, will continue..."
2935      sleep(pause)