Initial commit of uma stuff
This commit is contained in:
Родитель
daa62a3d21
Коммит
83e1f51459
|
@ -3,3 +3,4 @@
|
|||
*.swp
|
||||
*.DS_Store
|
||||
.sass-cache
|
||||
*oauth2.data
|
|
@ -0,0 +1,205 @@
|
|||
from __future__ import division
|
||||
from __future__ import print_function
|
||||
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright 2013 Google Inc.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License")
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
__author__ = ('ericbidelman@chromium.org (Eric Bidelman), '
|
||||
'kassycoan@google.com (Kassy Coan)')
|
||||
|
||||
|
||||
import datetime
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
import sys
|
||||
import webapp2
|
||||
|
||||
# Appengine imports.
|
||||
from google.appengine.api import files
|
||||
from google.appengine.api import urlfetch
|
||||
from google.appengine.ext import blobstore
|
||||
from google.appengine.ext.webapp import blobstore_handlers
|
||||
|
||||
# File imports.
|
||||
import common
|
||||
import models
|
||||
import settings
|
||||
import uma
|
||||
|
||||
|
||||
BIGSTORE_BUCKET = '/gs/uma-dashboards/'
|
||||
BIGSTORE_RESTFUL_URI = 'https://uma-dashboards.storage.googleapis.com/'
|
||||
BIGSTORE_HISTOGRAM_ID = str(0xb7f3cf359f13d209)
|
||||
|
||||
# For fetching files from the production BigStore during development.
|
||||
OAUTH2_CREDENTIALS_FILENAME = os.path.join(
|
||||
settings.ROOT_DIR, 'scripts', 'oauth2.data')
|
||||
|
||||
|
||||
class YesterdayHandler(blobstore_handlers.BlobstoreDownloadHandler):
|
||||
"""Loads yesterday's UMA data from BigStore."""
|
||||
|
||||
def _SaveData(self, data, yesterday):
|
||||
# Response format is "bucket-bucket+1=hits".
|
||||
# Example: 10-11=2175995,11-12=56635467,12-13=2432539420
|
||||
values_list = data['kTempHistograms'][BIGSTORE_HISTOGRAM_ID]['b'].split(',')
|
||||
|
||||
#sum_total = int(data['kTempHistograms'][BIGSTORE_HISTOGRAM_ID]['s']) # TODO: use this.
|
||||
|
||||
# Stores a hit count for each CSS property (properties_dict[bucket] = hits).
|
||||
properties_dict = {}
|
||||
|
||||
for val in values_list:
|
||||
bucket_range, hits_string = val.split('=') # e.g. "10-11=2175995"
|
||||
|
||||
parts = bucket_range.split('-')
|
||||
|
||||
beginning_range = int(parts[0])
|
||||
end_range = int(parts[1])
|
||||
|
||||
# Range > 1 indicates malformed data. Skip it.
|
||||
if end_range - beginning_range > 1:
|
||||
continue
|
||||
|
||||
# beginning_range is our bucket number; the stable CSSPropertyID.
|
||||
properties_dict[beginning_range] = int(hits_string)
|
||||
|
||||
# Bucket 1 is total pages visited. We're guaranteed to have it.
|
||||
# TODO(ericbidelman): If we don't have it, don't set to 1!
|
||||
total_pages = properties_dict.get(1, 1)
|
||||
|
||||
for bucket_id, num_hits in properties_dict.items():
|
||||
# If the id is not in the map, the name will be 'ERROR'.
|
||||
# TODO(ericbidelman): Probably better to leave non-matched bucket ids out.
|
||||
property_name = uma.CSS_PROPERTY_BUCKETS.get(bucket_id, 'ERROR')
|
||||
|
||||
query = models.StableInstance.all()
|
||||
query.filter('bucket_id = ', bucket_id)
|
||||
query.filter('date =', yesterday)
|
||||
|
||||
# Only add this entity if one doesn't already exist with the same
|
||||
# bucket_id and date.
|
||||
if query.count() > 0:
|
||||
continue
|
||||
|
||||
# TODO(ericbidelman): Calculate a rolling average here
|
||||
# This will be done using a GQL query to grab information
|
||||
# for the past 6 days.
|
||||
# We average those past 6 days with the new day's data
|
||||
# and store the result in rolling_percentage
|
||||
|
||||
entity = models.StableInstance(
|
||||
property_name=property_name,
|
||||
bucket_id=bucket_id,
|
||||
date=yesterday,
|
||||
hits=num_hits,
|
||||
total_pages=total_pages,
|
||||
day_percentage=float("%.2f" % (num_hits / total_pages))
|
||||
#rolling_percentage=
|
||||
)
|
||||
entity.put()
|
||||
|
||||
def get(self):
|
||||
"""Loads the data file located at |filename|.
|
||||
|
||||
Args:
|
||||
filename: The filename for the data file to be loaded.
|
||||
"""
|
||||
yesterday = datetime.date.today() - datetime.timedelta(1)
|
||||
yesterday_formatted = yesterday.strftime("%Y.%m.%d")
|
||||
|
||||
filename = 'histograms/daily/%s/Everything' % (yesterday_formatted)
|
||||
|
||||
if settings.PROD:
|
||||
try:
|
||||
with files.open(BIGSTORE_BUCKET + filename, 'r') as unused_f:
|
||||
pass
|
||||
except files.file.ExistenceError, e:
|
||||
self.response.write(e)
|
||||
return
|
||||
|
||||
# The file exists; serve it.
|
||||
blob_key = blobstore.create_gs_key(BIGSTORE_BUCKET + filename)
|
||||
blob_reader = blobstore.BlobReader(blob_key, buffer_size=3510000)
|
||||
try:
|
||||
result = blob_reader.read()
|
||||
finally:
|
||||
blob_reader.close()
|
||||
else:
|
||||
# From the development server, use the RESTful API to read files from the
|
||||
# production BigStore instance, rather than needing to stage them to the
|
||||
# local BigStore instance.
|
||||
result, response_code = self._FetchFromBigstoreREST(filename)
|
||||
|
||||
if response_code != 200:
|
||||
self.error(response_code)
|
||||
self.response.out.write(
|
||||
('%s - Error doing BigStore API request. '
|
||||
'Try refreshing your OAuth token?' % response_code))
|
||||
return
|
||||
|
||||
if result:
|
||||
data = json.loads(result)
|
||||
self._SaveData(data, yesterday)
|
||||
|
||||
def _FetchFromBigstoreREST(self, filename):
|
||||
# Read the OAuth2 access token from disk.
|
||||
try:
|
||||
with open(OAUTH2_CREDENTIALS_FILENAME, 'r') as f:
|
||||
credentials_json = json.load(f)
|
||||
except IOError, e:
|
||||
logging.error(e)
|
||||
return [None, 404]
|
||||
|
||||
# Attempt to fetch the file from the production BigStore instance.
|
||||
url = BIGSTORE_RESTFUL_URI + filename
|
||||
headers = {
|
||||
'x-goog-api-version': '2',
|
||||
'Authorization': 'OAuth ' + credentials_json.get('access_token', '')
|
||||
}
|
||||
result = urlfetch.fetch(url, headers=headers)
|
||||
return (result.content, result.status_code)
|
||||
|
||||
|
||||
class FeatureHandler(common.ContentHandler):
|
||||
|
||||
def get(self, path):
|
||||
# feature = models.Feature(
|
||||
# type=models.Resource.Type.ARTICLE, #TODO: use correct type for content.
|
||||
# title=doc.title(),
|
||||
# text=doc.summary(),#.decode('utf-8').decode('ascii','ignore'),
|
||||
# publication_date=datetime.datetime.today(), #TODO: save real date.
|
||||
# url=db.Link(result.final_url or url),
|
||||
# #fetch_date=datetime.date.today(),
|
||||
# #sharers
|
||||
# )
|
||||
|
||||
# Remove trailing slash from URL and redirect. e.g. /metrics/ -> /metrics
|
||||
if path[-1] == '/':
|
||||
return self.redirect('/' + path.rstrip('/'))
|
||||
|
||||
template_file = path + '.html'
|
||||
template_data = {
|
||||
'feature_form': models.FeatureForm()
|
||||
}
|
||||
|
||||
self.render(data=template_data, template_path=os.path.join(template_file))
|
||||
|
||||
app = webapp2.WSGIApplication([
|
||||
('/cron/metrics', YesterdayHandler),
|
||||
('/(.*)', FeatureHandler),
|
||||
], debug=settings.DEBUG)
|
||||
|
56
app.yaml
56
app.yaml
|
@ -1,5 +1,5 @@
|
|||
application: cr-status
|
||||
version: 1
|
||||
version: v2dash
|
||||
runtime: python27
|
||||
threadsafe: true
|
||||
api_version: 1
|
||||
|
@ -11,10 +11,43 @@ libraries:
|
|||
version: "latest"
|
||||
- name: django
|
||||
version: "latest" #version: "1.3"
|
||||
# - name: setuptools
|
||||
# version: latest
|
||||
|
||||
env_variables:
|
||||
DJANGO_SETTINGS_MODULE: 'settings'
|
||||
|
||||
handlers:
|
||||
|
||||
# Static handlers ---------------------------------------------------------------
|
||||
- url: /favicon\.ico
|
||||
static_files: static/img/chromium-128.png
|
||||
upload: static/img/chromium-128\.png
|
||||
|
||||
- url: /static
|
||||
static_dir: static
|
||||
|
||||
# Metrics data handlers --------------------------------------------------------
|
||||
- url: /data/.*
|
||||
script: metrics.app
|
||||
|
||||
# Admin ------------------------------------------------------------------------
|
||||
- url: /admin/gae/.*
|
||||
script: google.appengine.ext.admin.application
|
||||
login: admin
|
||||
|
||||
- url: /cron/metrics
|
||||
script: admin.app
|
||||
login: admin # Prevents raw access to this handler. Cron runs as admin.
|
||||
|
||||
- url: /admin/features/.*
|
||||
script: admin.app
|
||||
login: admin
|
||||
|
||||
# Main server ------------------------------------------------------------------
|
||||
- url: /.*
|
||||
script: server.app
|
||||
|
||||
skip_files:
|
||||
- ^(.*/)?app\.yaml
|
||||
- ^(.*/)?app\.yml
|
||||
|
@ -30,24 +63,3 @@ skip_files:
|
|||
- ^(.*/)?.*\.sql[3]$
|
||||
- ^(.*/)?.*\.sh$
|
||||
- ^(.*/)?.*\.scss$
|
||||
|
||||
handlers:
|
||||
- url: /favicon\.ico
|
||||
static_files: static/img/chrome_logo.svg
|
||||
upload: static/img/chrome_logo\.svg
|
||||
|
||||
- url: /static
|
||||
static_dir: static
|
||||
|
||||
- url: /admin/.*
|
||||
script: google.appengine.ext.admin.application
|
||||
login: admin
|
||||
|
||||
- url: /newfeature
|
||||
script: server.app
|
||||
login: admin
|
||||
|
||||
- url: /.*
|
||||
script: server.app
|
||||
|
||||
|
||||
|
|
|
@ -0,0 +1,86 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Copyright 2013 Google Inc.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License")
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
__author__ = 'ericbidelman@chromium.org (Eric Bidelman)'
|
||||
|
||||
|
||||
import json
|
||||
import logging
|
||||
import webapp2
|
||||
|
||||
import settings
|
||||
|
||||
from django.template.loader import render_to_string
|
||||
|
||||
|
||||
|
||||
class BaseHandler(webapp2.RequestHandler):
|
||||
|
||||
def __init__(self, request, response):
|
||||
self.initialize(request, response)
|
||||
|
||||
# Add CORS and Chrome Frame to all responses.
|
||||
self.response.headers.add_header('Access-Control-Allow-Origin', '*')
|
||||
self.response.headers.add_header('X-UA-Compatible', 'IE=Edge,chrome=1')
|
||||
|
||||
# Settings can't be global in python 2.7 env.
|
||||
logging.getLogger().setLevel(logging.DEBUG)
|
||||
|
||||
|
||||
class JSONHandler(BaseHandler):
|
||||
|
||||
def get(self, data):
|
||||
self.response.headers['Content-Type'] = 'application/json'
|
||||
self.response.write(json.dumps([entity.to_dict() for entity in data]))
|
||||
|
||||
|
||||
class ContentHandler(BaseHandler):
|
||||
|
||||
def render(self, data={}, template_path=None, status=None, message=None,
|
||||
relpath=None):
|
||||
if status is not None and status != 200:
|
||||
self.response.set_status(status, message)
|
||||
|
||||
# Add template data to every request.
|
||||
template_data = {
|
||||
#'is_mobile': self.is_awesome_mobile_device(),
|
||||
'prod': settings.PROD,
|
||||
'APP_TITLE': settings.APP_TITLE,
|
||||
'current_path': self.request.path
|
||||
}
|
||||
template_data.update(data) # merge in other data.
|
||||
|
||||
try:
|
||||
self.response.out.write(render_to_string(template_path, template_data))
|
||||
except Exception:
|
||||
handle_404(self.request, self.response, Exception)
|
||||
|
||||
|
||||
def handle_404(request, response, exception):
|
||||
ERROR_404 = (
|
||||
'<title>404 Not Found</title>\n'
|
||||
'<h1>Error: Not Found</h1>\n'
|
||||
'<h2>The requested URL <code>%s</code> was not found on this server.'
|
||||
'</h2>' % request.url)
|
||||
response.write(ERROR_404)
|
||||
response.set_status(404)
|
||||
|
||||
def handle_500(request, response, exception):
|
||||
logging.exception(exception)
|
||||
ERROR_500 = (
|
||||
'<title>500 Internal Server Error</title>\n'
|
||||
'<h1>Error: 500 Internal Server Error</h1>')
|
||||
response.write(ERROR_500)
|
||||
response.set_status(500)
|
|
@ -0,0 +1,4 @@
|
|||
cron:
|
||||
- description: retrieve from UMA Cloud Storage data gathered yesterday
|
||||
url: /cron/metrics
|
||||
schedule: every day 05:00
|
|
@ -0,0 +1,17 @@
|
|||
indexes:
|
||||
|
||||
# AUTOGENERATED
|
||||
|
||||
# This index.yaml is automatically updated whenever the dev_appserver
|
||||
# detects that a new type of query is run. If you want to manage the
|
||||
# index.yaml file manually, remove the above marker line (the line
|
||||
# saying "# AUTOGENERATED"). If you want to manage some indexes
|
||||
# manually, move them above the marker line. The index.yaml file is
|
||||
# automatically uploaded to the admin console when you next deploy
|
||||
# your application using appcfg.py.
|
||||
|
||||
- kind: StableInstance
|
||||
properties:
|
||||
- name: date
|
||||
- name: hits
|
||||
direction: desc
|
|
@ -0,0 +1,37 @@
|
|||
import webapp2
|
||||
|
||||
import common
|
||||
import models
|
||||
import settings
|
||||
|
||||
|
||||
class StableInstances(common.JSONHandler):
|
||||
|
||||
def get(self):
|
||||
data = models.StableInstance.all().order('-date').fetch(limit=None)
|
||||
super(StableInstances, self).get(data)
|
||||
|
||||
|
||||
class QueryStackRank(common.JSONHandler):
|
||||
|
||||
def get(self):
|
||||
# Find last date data was fetched by pulling one entry.
|
||||
result = models.StableInstance.all().order('-date').get()
|
||||
|
||||
data = []
|
||||
if result:
|
||||
data_last_added_on = result.date
|
||||
|
||||
query = models.StableInstance.all()
|
||||
query.filter('date =', data_last_added_on)
|
||||
query.order('-hits')
|
||||
|
||||
data = query.fetch(limit=None)
|
||||
|
||||
super(QueryStackRank, self).get(data)
|
||||
|
||||
|
||||
app = webapp2.WSGIApplication([
|
||||
('/data/querystableinstances', StableInstances),
|
||||
('/data/querystackrank', QueryStackRank)
|
||||
], debug=settings.DEBUG)
|
51
models.py
51
models.py
|
@ -1,3 +1,6 @@
|
|||
import datetime
|
||||
import time
|
||||
|
||||
from google.appengine.api import memcache
|
||||
from google.appengine.ext import db
|
||||
#from google.appengine.ext.db import djangoforms
|
||||
|
@ -6,14 +9,60 @@ from google.appengine.ext import db
|
|||
from django import forms
|
||||
|
||||
|
||||
SIMPLE_TYPES = (int, long, float, bool, dict, basestring, list)
|
||||
|
||||
|
||||
class DictModel(db.Model):
|
||||
# def to_dict(self):
|
||||
# return dict([(p, unicode(getattr(self, p))) for p in self.properties()])
|
||||
|
||||
def to_dict(self):
|
||||
return dict([(p, getattr(self, p)) for p in self.properties()])
|
||||
output = {}
|
||||
|
||||
for key, prop in self.properties().iteritems():
|
||||
value = getattr(self, key)
|
||||
|
||||
if value is None or isinstance(value, SIMPLE_TYPES):
|
||||
output[key] = value
|
||||
elif isinstance(value, datetime.date):
|
||||
# Convert date/datetime to ms-since-epoch ("new Date()").
|
||||
#ms = time.mktime(value.utctimetuple())
|
||||
#ms += getattr(value, 'microseconds', 0) / 1000
|
||||
#output[key] = int(ms)
|
||||
output[key] = unicode(value)
|
||||
elif isinstance(value, db.GeoPt):
|
||||
output[key] = {'lat': value.lat, 'lon': value.lon}
|
||||
elif isinstance(value, db.Model):
|
||||
output[key] = to_dict(value)
|
||||
else:
|
||||
raise ValueError('cannot encode ' + repr(prop))
|
||||
|
||||
return output
|
||||
|
||||
|
||||
# UMA metrics.
|
||||
class StableInstance(DictModel):
|
||||
created = db.DateTimeProperty(auto_now_add=True)
|
||||
updated = db.DateTimeProperty(auto_now=True)
|
||||
|
||||
property_name = db.StringProperty(required=True)
|
||||
bucket_id = db.IntegerProperty(required=True)
|
||||
date = db.DateProperty(verbose_name='When the data was fetched',
|
||||
required=True)
|
||||
hits = db.IntegerProperty(required=True)
|
||||
total_pages = db.IntegerProperty()
|
||||
day_percentage = db.FloatProperty()
|
||||
rolling_percentage = db.FloatProperty()
|
||||
|
||||
|
||||
# Feature dashboard.
|
||||
class Feature(DictModel):
|
||||
"""Container for a feature."""
|
||||
|
||||
# Metadata.
|
||||
created = db.DateTimeProperty(auto_now_add=True)
|
||||
updated = db.DateTimeProperty(auto_now=True)
|
||||
|
||||
# General info.
|
||||
category = db.StringProperty(required=True)
|
||||
feature_name = db.StringProperty(required=False)
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
{
|
||||
"installed": {
|
||||
"client_id": "470026626516.apps.googleusercontent.com",
|
||||
"client_secret": "R1uICVrjoYFaOcMI7ISWEZKR",
|
||||
"redirect_uris": ["http://localhost", "urn:ietf:wg:oauth:2.0:oob"],
|
||||
"auth_uri": "https://accounts.google.com/o/oauth2/auth",
|
||||
"token_uri": "https://accounts.google.com/o/oauth2/token"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
#!/bin/bash
|
||||
#
|
||||
# Deploys the app to App Engine.
|
||||
#
|
||||
# Note: This script should be used in place of using appcfg.py update directly
|
||||
# to update the application on App Engine.
|
||||
#
|
||||
# Copyright 2013 Eric Bidelman <ericbidelman@chromium.org>
|
||||
|
||||
|
||||
# The directory in which this script resides.
|
||||
readonly BASEDIR=$(dirname $BASH_SOURCE)
|
||||
|
||||
$BASEDIR/oauthtoken.sh deploy
|
||||
appcfg.py update $BASEDIR/../
|
|
@ -0,0 +1,139 @@
|
|||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright 2013 Google Inc. All Rights Reserved.
|
||||
|
||||
"""OAuth 2.0 Bootstrapper for App Engine dev server development.
|
||||
|
||||
Adapted from:
|
||||
https://code.google.com/p/google-cloud-storage-samples/source/browse/gs-oauth.py
|
||||
"""
|
||||
|
||||
__author__ = ('ericbidelman@chromium.org (Eric Bidelman), '
|
||||
'nherring@google.com (Nathan Herring)')
|
||||
|
||||
|
||||
import datetime
|
||||
import re
|
||||
import os
|
||||
import sys
|
||||
from pkg_resources import parse_version
|
||||
|
||||
|
||||
def downloadUsage(err, downloadUrl=None):
|
||||
"""Emit usage statement with download information."""
|
||||
if downloadUrl is None:
|
||||
downloadString = 'Run'
|
||||
else:
|
||||
downloadString = 'Download available at %s or run' % downloadUrl
|
||||
print '%s\n%s%s' % (
|
||||
err,
|
||||
downloadString,
|
||||
' setup.py on the google-api-python-client:\n' +
|
||||
'https://code.google.com/p/google-api-python-client/downloads')
|
||||
sys.exit(1)
|
||||
|
||||
def importUsage(lib, downloadUrl=None):
|
||||
"""Emit a failed import error with download information."""
|
||||
downloadUsage('Could not load %s module.' % lib, downloadUrl)
|
||||
|
||||
#
|
||||
# Import boilerplate to make it easy to diagnose when the right modules
|
||||
# are not installed and how to remedy it.
|
||||
#
|
||||
|
||||
try:
|
||||
from gflags import gflags
|
||||
print gflags.FLAGS
|
||||
except:
|
||||
importUsage('gflags', 'https://code.google.com/p/python-gflags/downloads/')
|
||||
|
||||
try:
|
||||
import httplib2
|
||||
except:
|
||||
importUsage('httplib2', 'https://code.google.com/p/httplib2/downloads/')
|
||||
|
||||
OAUTH2CLIENT_REQUIRED_VERSION = '1.0b8'
|
||||
try:
|
||||
from oauth2client.file import Storage
|
||||
from oauth2client.client import flow_from_clientsecrets
|
||||
from oauth2client.client import OAuth2WebServerFlow
|
||||
from oauth2client.tools import run
|
||||
import oauth2client
|
||||
except:
|
||||
importUsage('oauth2client')
|
||||
oauth2client_version = None
|
||||
if '__version__' not in oauth2client.__dict__:
|
||||
if '__file__' in oauth2client.__dict__:
|
||||
verMatch = re.search(r'google_api_python_client-([^-]+)',
|
||||
oauth2client.__dict__['__file__'])
|
||||
if verMatch is not None:
|
||||
oauth2client_version = verMatch.group(1)
|
||||
oauth2client_version = re.sub('beta', 'b', oauth2client_version)
|
||||
else:
|
||||
oauth2client_version = oauth2client.__dict__['__version__']
|
||||
if oauth2client_version is None:
|
||||
downloadUsage('Could not determine version of oauth2client module.\n' +
|
||||
'Miminum required version is %s.' % OAUTH2CLIENT_REQUIRED_VERSION)
|
||||
elif (parse_version(oauth2client_version) <
|
||||
parse_version(OAUTH2CLIENT_REQUIRED_VERSION)):
|
||||
downloadUsage(('oauth2client module version %s is too old.\n' +
|
||||
'Miminum required version is %s.') % (oauth2client_version,
|
||||
OAUTH2CLIENT_REQUIRED_VERSION))
|
||||
|
||||
#
|
||||
# End of the import boilerplate
|
||||
#
|
||||
|
||||
FLAGS = gflags.FLAGS
|
||||
|
||||
gflags.DEFINE_multistring(
|
||||
'scope',
|
||||
'https://www.googleapis.com/auth/devstorage.read_only',
|
||||
'API scope to use')
|
||||
|
||||
gflags.DEFINE_string(
|
||||
'client_secrets_file',
|
||||
os.path.join(os.path.dirname(__file__), 'client_secrets.json'),
|
||||
'File name for client_secrets.json')
|
||||
|
||||
gflags.DEFINE_string(
|
||||
'credentials_file',
|
||||
os.path.join(os.path.dirname(__file__), 'oauth2.data2'),
|
||||
'File name for storing OAuth 2.0 credentials.',
|
||||
short_name='f')
|
||||
|
||||
|
||||
def main(argv):
|
||||
try:
|
||||
argv = FLAGS(argv)
|
||||
except gflags.FlagsError, e:
|
||||
print '%s\nUsage: %s ARGS\n%s' % (e, sys.argv[0], FLAGS)
|
||||
sys.exit(1)
|
||||
|
||||
storage = Storage(FLAGS.credentials_file)
|
||||
credentials = storage.get()
|
||||
if credentials is None or credentials.invalid:
|
||||
print 'Acquiring initial credentials...'
|
||||
|
||||
# Need to acquire token using @google.com account.
|
||||
flow = flow_from_clientsecrets(
|
||||
FLAGS.client_secrets_file,
|
||||
scope=' '.join(FLAGS.scope),
|
||||
redirect_uri='urn:ietf:wg:oauth:2.0:oob',
|
||||
message='Error - client_secrets_file not found')
|
||||
|
||||
credentials = run(flow, storage)
|
||||
elif credentials.access_token_expired:
|
||||
print 'Refreshing access token...'
|
||||
credentials.refresh(httplib2.Http())
|
||||
|
||||
print 'Refresh token:', credentials.refresh_token
|
||||
print 'Access token:', credentials.access_token
|
||||
delta = credentials.token_expiry - datetime.datetime.utcnow()
|
||||
print 'Access token expires: %sZ (%dm %ds)' % (credentials.token_expiry,
|
||||
delta.seconds / 60, delta.seconds % 60)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main(sys.argv)
|
|
@ -0,0 +1,2 @@
|
|||
google-gflags@googlegroups.com
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
Copyright (c) 2006, Google Inc.
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are
|
||||
met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above
|
||||
copyright notice, this list of conditions and the following disclaimer
|
||||
in the documentation and/or other materials provided with the
|
||||
distribution.
|
||||
* Neither the name of Google Inc. nor the names of its
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
@ -0,0 +1,62 @@
|
|||
Wed Jan 18 13:57:39 2012 Google Inc. <google-gflags@googlegroups.com>
|
||||
|
||||
* python-gflags: version 2.0
|
||||
* No changes from version 1.8.
|
||||
|
||||
Wed Jan 18 11:54:03 2012 Google Inc. <google-gflags@googlegroups.com>
|
||||
|
||||
* python-gflags: version 1.8
|
||||
* Don't raise DuplicateFlag when re-importing a module (mmcdonald)
|
||||
* Changed the 'official' python-gflags email in setup.py/etc
|
||||
* Changed copyright text to reflect Google's relinquished ownership
|
||||
|
||||
Tue Dec 20 17:10:41 2011 Google Inc. <opensource@google.com>
|
||||
|
||||
* python-gflags: version 1.7
|
||||
* Prepare gflags for python 3.x, keeping 2.4 compatibility (twouters)
|
||||
* If output is a tty, use terminal's width to wrap help-text (wiesmann)
|
||||
* PORTING: Fix ImportError for non-Unix platforms (kdeus)
|
||||
* PORTING: Run correctly when termios isn't available (shines)
|
||||
* Add unicode support to flags (csilvers)
|
||||
|
||||
Fri Jul 29 12:24:08 2011 Google Inc. <opensource@google.com>
|
||||
|
||||
* python-gflags: version 1.6
|
||||
* Document FlagValues.UseGnuGetOpt (garymm)
|
||||
* replace fchmod with chmod to work on python 2.4 (mshields)
|
||||
* Fix bug in flag decl reporting for dup flags (craigcitro)
|
||||
* Add multi_float, and tests for multi_float/int (simonf)
|
||||
* Make flagfiles expand in place, to follow docs (dmlynch)
|
||||
* Raise exception if --flagfile can't be read (tlim)
|
||||
|
||||
Wed Jan 26 13:50:46 2011 Google Inc. <opensource@google.com>
|
||||
|
||||
* python-gflags: version 1.5.1
|
||||
* Fix manifest and setup.py to include new files
|
||||
|
||||
Mon Jan 24 16:58:10 2011 Google Inc. <opensource@google.com>
|
||||
|
||||
* python-gflags: version 1.5
|
||||
* Add support for flag validators (olexiy)
|
||||
* Better reporting on UnrecognizedFlagError (sorenj)
|
||||
* Cache ArgumentParser, to save space (tmarek)
|
||||
|
||||
Wed Oct 13 17:40:12 2010 Google Inc. <opensource@google.com>
|
||||
|
||||
* python-gflags: version 1.4
|
||||
* Unregister per-command flags after running the command (dnr)
|
||||
* Allow key-flags to work with special flags (salcianu)
|
||||
* Allow printing flags of a specific module (mikecurtis)
|
||||
* BUGFIX: Fix an error message for float flags (olexiy)
|
||||
* BUGFIX: Can now import while defining flags (salcianu)
|
||||
* BUGFIX: Fix flagfile parsing in python (chronos)
|
||||
* DOC: Better explain the format of --helpxml output (salcianu)
|
||||
* DOC: Better error message on parse failure (tstromberg)
|
||||
* Better test coverage under python 2.2 (mshields)
|
||||
* Added a Makefile for building the packages.
|
||||
|
||||
Mon Jan 4 18:46:29 2010 Tim 'mithro' Ansell <mithro@mithis.com>
|
||||
|
||||
* python-gflags: version 1.3
|
||||
* Fork from the C++ package (google-gflags 1.3)
|
||||
* Add debian packaging
|
|
@ -0,0 +1,19 @@
|
|||
include AUTHORS
|
||||
include COPYING
|
||||
include ChangeLog
|
||||
include MANIFEST.in
|
||||
include Makefile
|
||||
include NEWS
|
||||
include README
|
||||
include debian/README
|
||||
include debian/changelog
|
||||
include debian/compat
|
||||
include debian/control
|
||||
include debian/copyright
|
||||
include debian/docs
|
||||
include debian/rules
|
||||
include gflags.py
|
||||
include gflags2man.py
|
||||
include gflags_validators.py
|
||||
include setup.py
|
||||
recursive-include tests *.py
|
|
@ -0,0 +1,69 @@
|
|||
|
||||
prep:
|
||||
@echo
|
||||
# Install needed packages
|
||||
sudo apt-get install subversion fakeroot python-setuptools python-subversion
|
||||
#
|
||||
@echo
|
||||
# Check that the person has .pypirc
|
||||
@if [ ! -e ~/.pypirc ]; then \
|
||||
echo "Please create a ~/.pypirc with the following contents:"; \
|
||||
echo "[server-login]"; \
|
||||
echo "username:google_opensource"; \
|
||||
echo "password:<see valentine>"; \
|
||||
fi
|
||||
#
|
||||
@echo
|
||||
# FIXME(tansell): Check that the person has .dputrc for PPA
|
||||
|
||||
clean:
|
||||
# Clean up any build files.
|
||||
python setup.py clean --all
|
||||
#
|
||||
# Clean up the debian stuff
|
||||
fakeroot ./debian/rules clean
|
||||
#
|
||||
# Clean up everything else
|
||||
rm MANIFEST || true
|
||||
rm -rf build-*
|
||||
#
|
||||
# Clean up the egg files
|
||||
rm -rf *egg*
|
||||
#
|
||||
# Remove dist
|
||||
rm -rf dist
|
||||
|
||||
dist:
|
||||
# Generate the tarball based on MANIFEST.in
|
||||
python setup.py sdist
|
||||
#
|
||||
# Build the debian packages
|
||||
fakeroot ./debian/rules binary
|
||||
mv ../python-gflags*.deb ./dist/
|
||||
#
|
||||
# Build the python Egg
|
||||
python setup.py bdist_egg
|
||||
#
|
||||
@echo
|
||||
@echo "Files to upload:"
|
||||
@echo "--------------------------"
|
||||
@ls -l ./dist/
|
||||
|
||||
push:
|
||||
# Send the updates to svn
|
||||
# Upload the source package to code.google.com
|
||||
- /home/build/opensource/tools/googlecode_upload.py \
|
||||
-p python-gflags ./dist/*
|
||||
#
|
||||
# Upload the package to PyPi
|
||||
- python setup.py sdist upload
|
||||
- python setup.py bdist_egg upload
|
||||
#
|
||||
# Upload the package to the ppa
|
||||
# FIXME(tansell): dput should run here
|
||||
|
||||
check:
|
||||
# Run all the tests.
|
||||
for test in tests/*.py; do PYTHONPATH=. python $$test || exit 1; done
|
||||
|
||||
.PHONY: prep dist clean push check
|
|
@ -0,0 +1,78 @@
|
|||
== 18 January 2012 ==
|
||||
|
||||
[Prependum:] I just realized I should have named the new version 2.0,
|
||||
to reflect the new ownership and status as a community run project.
|
||||
Not too late, I guess. I've just released python-gflags 2.0, which is
|
||||
identical to python-gflags 1.8 except for the version number.
|
||||
|
||||
I've just released python-gflags 1.8. This fixes a bug, allowing
|
||||
modules defining flags to be re-imported without raising duplicate
|
||||
flag errors.
|
||||
|
||||
Administrative note: In the coming weeks, I'll be stepping down as
|
||||
maintainer for the python-gflags project, and as part of that Google
|
||||
is relinquishing ownership of the project; it will now be entirely
|
||||
community run. The remaining
|
||||
[http://python-gflags.googlecode.com/svn/tags/python-gflags-1.8/ChangeLog changes]
|
||||
in this release reflect that shift.
|
||||
|
||||
|
||||
=== 20 December 2011 ===
|
||||
|
||||
I've just released python-gflags 1.7. The major change here is
|
||||
improved unicode support, in both flag default values and
|
||||
help-strings. We've also made big steps toward making gflags work
|
||||
with python 3.x (while keeping 2.4 compatibility), and improving
|
||||
--help output in the common case where output is a tty.
|
||||
|
||||
For a full list of changes since last release, see the
|
||||
[http://python-gflags.googlecode.com/svn/tags/python-gflags-1.7/ChangeLog ChangeLog].
|
||||
|
||||
=== 29 July 2011 ===
|
||||
|
||||
I've just released python-gflags 1.6. This release has only minor
|
||||
changes, including support for multi_float flags. The full list of
|
||||
changes is in the
|
||||
[http://python-gflags.googlecode.com/svn/tags/python-gflags-1.6/ChangeLog ChangeLog].
|
||||
|
||||
The major change with this release is procedural: I've changed the
|
||||
internal tools used to integrate Google-supplied patches for gflags
|
||||
into the opensource release. These new tools should result in more
|
||||
frequent updates with better change descriptions. They will also
|
||||
result in future `ChangeLog` entries being much more verbose (for
|
||||
better or for worse).
|
||||
|
||||
=== 26 January 2011 ===
|
||||
|
||||
I've just released python-gflags 1.5.1. I had improperly packaged
|
||||
python-gflags 1.5, so it probably doesn't work. All users who have
|
||||
updated to python-gflags 1.5 are encouraged to update again to 1.5.1.
|
||||
|
||||
=== 24 January 2011 ===
|
||||
|
||||
I've just released python-gflags 1.5. This release adds support for
|
||||
flag verifiers: small functions you can associate with flags, that are
|
||||
called whenever the flag value is set or modified, and can verify that
|
||||
the new value is legal. It also has other, minor changes, described
|
||||
in the
|
||||
[http://python-gflags.googlecode.com/svn/tags/python-gflags-1.5/ChangeLog ChangeLog].
|
||||
|
||||
=== 11 October 2010 ===
|
||||
|
||||
I've just released python-gflags 1.4. This release has only minor
|
||||
changes from 1.3, including support for printing flags of a specific
|
||||
module, allowing key-flags to work with special flags, somewhat better
|
||||
error messaging, and
|
||||
[http://python-gflags.googlecode.com/svn/tags/python-gflags-1.4/ChangeLog so forth].
|
||||
If 1.3 is working well for you, there's no particular reason to upgrade.
|
||||
|
||||
=== 4 January 2010 ===
|
||||
|
||||
I just released python-gflags 1.3. This is the first python-gflags
|
||||
release; it is version 1.3 because this code is forked from the 1.3
|
||||
release of google-gflags.
|
||||
|
||||
I don't have a tarball or .deb file up quite yet, so for now you will
|
||||
have to get the source files by browsing under the 'source'
|
||||
tag. Downloadable files will be available soon.
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
Metadata-Version: 1.0
|
||||
Name: python-gflags
|
||||
Version: 2.0
|
||||
Summary: Google Commandline Flags Module
|
||||
Home-page: http://code.google.com/p/python-gflags
|
||||
Author: Google Inc. and others
|
||||
Author-email: google-gflags@googlegroups.com
|
||||
License: BSD
|
||||
Description: UNKNOWN
|
||||
Platform: UNKNOWN
|
|
@ -0,0 +1,23 @@
|
|||
This repository contains a python implementation of the Google commandline
|
||||
flags module.
|
||||
|
||||
GFlags defines a *distributed* command line system, replacing systems like
|
||||
getopt(), optparse and manual argument processing. Rather than an application
|
||||
having to define all flags in or near main(), each python module defines flags
|
||||
that are useful to it. When one python module imports another, it gains
|
||||
access to the other's flags.
|
||||
|
||||
It includes the ability to define flag types (boolean, float, interger, list),
|
||||
autogeneration of help (in both human and machine readable format) and reading
|
||||
arguments from a file. It also includes the ability to automatically generate
|
||||
man pages from the help flags.
|
||||
|
||||
Documentation for implementation is at the top of gflags.py file.
|
||||
|
||||
To install the python module, run
|
||||
python ./setup.py install
|
||||
|
||||
When you install this library, you also get a helper application,
|
||||
gflags2man.py, installed into /usr/local/bin. You can run gflags2man.py to
|
||||
create an instant man page, with all the commandline flags and their docs, for
|
||||
any C++ or python program you've written using the gflags library.
|
|
@ -0,0 +1,7 @@
|
|||
The list of files here isn't complete. For a step-by-step guide on
|
||||
how to set this package up correctly, check out
|
||||
http://www.debian.org/doc/maint-guide/
|
||||
|
||||
Most of the files that are in this directory are boilerplate.
|
||||
However, you may need to change the list of binary-arch dependencies
|
||||
in 'rules'.
|
|
@ -0,0 +1,54 @@
|
|||
python-gflags (2.0-1) unstable; urgency=low
|
||||
|
||||
* New upstream release.
|
||||
|
||||
-- Google Inc. <google-gflags@googlegroups.com> Wed, 18 Jan 2012 13:57:39 -0800
|
||||
|
||||
python-gflags (1.8-1) unstable; urgency=low
|
||||
|
||||
* New upstream release.
|
||||
|
||||
-- Google Inc. <google-gflags@googlegroups.com> Wed, 18 Jan 2012 11:54:03 -0800
|
||||
|
||||
python-gflags (1.7-1) unstable; urgency=low
|
||||
|
||||
* New upstream release.
|
||||
|
||||
-- Google Inc. <opensource@google.com> Tue, 20 Dec 2011 17:10:41 -0800
|
||||
|
||||
python-gflags (1.6-1) unstable; urgency=low
|
||||
|
||||
* New upstream release.
|
||||
|
||||
-- Google Inc. <opensource@google.com> Fri, 29 Jul 2011 12:24:08 -0700
|
||||
|
||||
python-gflags (1.5.1-1) unstable; urgency=low
|
||||
|
||||
* New upstream release (fixes manifest and setup.py files)
|
||||
|
||||
-- Google Inc. <opensource@google.com> Wed, 26 Jan 2011 13:50:46 -0800
|
||||
|
||||
python-gflags (1.5-1) unstable; urgency=low
|
||||
|
||||
* New upstream release.
|
||||
|
||||
-- Google Inc. <opensource@google.com> Mon, 24 Jan 2011 16:58:10 -0800
|
||||
|
||||
python-gflags (1.4-1) unstable; urgency=low
|
||||
|
||||
* New upstream release.
|
||||
|
||||
-- Google Inc. <opensource@google.com> Wed, 13 Oct 2010 17:40:12 -0700
|
||||
|
||||
python-gflags (1.3-2) unstable; urgency=low
|
||||
|
||||
* Fixed man-page generation.
|
||||
|
||||
-- Tim 'mithro' Ansell <mithro@mithis.com> Mon, 07 Jan 2010 13:46:10 +1100
|
||||
|
||||
python-gflags (1.3-1) unstable; urgency=low
|
||||
|
||||
* Initial release.
|
||||
* Packaging based on gflags 1.3
|
||||
|
||||
-- Tim 'mithro' Ansell <mithro@mithis.com> Mon, 04 Jan 2010 18:46:10 -0800
|
|
@ -0,0 +1 @@
|
|||
5
|
|
@ -0,0 +1,26 @@
|
|||
Source: python-gflags
|
||||
Section: python
|
||||
XS-Python-Version: all
|
||||
Priority: optional
|
||||
Maintainer: Craig Silverstein <google-gflags@googlegroups.com>
|
||||
Build-Depends-Indep: python-central (>= 0.5.6), python-setuptools (>= 0.6b3-1), python-all
|
||||
Build-Depends: debhelper (>= 5.0.38)
|
||||
Standards-Version: 3.7.2
|
||||
|
||||
Package: python-gflags
|
||||
Architecture: all
|
||||
Depends: ${python:Depends}
|
||||
XB-Python-Version: ${python:Versions}
|
||||
Description: A Python implementation of the Google commandline flags module
|
||||
.
|
||||
GFlags defines a *distributed* command line system, replacing systems like
|
||||
getopt(), optparse and manual argument processing. Rather than an application
|
||||
having to define all flags in or near main(), each Python module defines flags
|
||||
that are useful to it. When one Python module imports another, it gains
|
||||
access to the other's flags.
|
||||
.
|
||||
It includes the ability to define flag types (boolean, float, interger, list),
|
||||
autogeneration of help (in both human and machine readable format) and reading
|
||||
arguments from a file. It also includes the ability to automatically generate
|
||||
man pages from the help flags.
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
This package was debianized by Craig Silverstein <google-gflags@googlegroups.com> on
|
||||
Wed, 18 Jan 2012 13:57:39 -0800.
|
||||
|
||||
It was downloaded from http://code.google.com/p/python-gflags/downloads/list
|
||||
|
||||
Upstream Author: Google Inc. and others <google-gflags@googlegroups.com>
|
||||
Copyright: Google Inc. and others <google-gflags@googlegroups.com>
|
||||
|
||||
License:
|
||||
|
||||
Copyright (c) 2006, Google Inc.
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are
|
||||
met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above
|
||||
copyright notice, this list of conditions and the following disclaimer
|
||||
in the documentation and/or other materials provided with the
|
||||
distribution.
|
||||
* Neither the name of Google Inc. nor the names of its
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
The Debian packaging is (C) 2011, Tim 'mithro' Ansell <mithro@mithis.com> and
|
||||
is licensed under the above.
|
|
@ -0,0 +1,2 @@
|
|||
AUTHORS
|
||||
README
|
|
@ -0,0 +1,62 @@
|
|||
#!/usr/bin/make -f
|
||||
# -*- makefile -*-
|
||||
# Sample debian/rules that uses debhelper.
|
||||
# GNU copyright 1997 to 1999 by Joey Hess.
|
||||
|
||||
# Uncomment this to turn on verbose mode.
|
||||
#export DH_VERBOSE=1
|
||||
|
||||
PYTHON := /usr/bin/python
|
||||
#PYVER := $(shell $(PYTHON) -c 'import sys; print sys.version[:3]')
|
||||
PYVERS = $(shell pyversions -vr)
|
||||
|
||||
build: $(PYVERS:%=build-python%)
|
||||
touch $@
|
||||
|
||||
build-python%:
|
||||
dh_testdir
|
||||
python$* setup.py build
|
||||
touch $@
|
||||
|
||||
clean:
|
||||
dh_testdir
|
||||
dh_testroot
|
||||
rm -f build-python*
|
||||
rm -rf build
|
||||
-find . -name '*.py[co]' | xargs rm -f
|
||||
dh_clean
|
||||
|
||||
install: build $(PYVERS:%=install-python%)
|
||||
|
||||
install-python%:
|
||||
dh_testdir
|
||||
dh_testroot
|
||||
dh_clean -k
|
||||
dh_installdirs
|
||||
python$* setup.py install --root=$(CURDIR)/debian/python-gflags --prefix=/usr
|
||||
# Scripts should not have a .py on the end of them
|
||||
mv $(CURDIR)/debian/python-gflags/usr/bin/gflags2man.py $(CURDIR)/debian/python-gflags/usr/bin/gflags2man
|
||||
# Generate a man file for gflags2man
|
||||
mkdir -p $(CURDIR)/debian/python-gflags/usr/share/man/man1
|
||||
PYTHONPATH=$(CURDIR)/debian/.. python$* gflags2man.py --dest_dir $(CURDIR)/debian/python-gflags/usr/share/man/man1 $(CURDIR)/debian/python-gflags/usr/bin/gflags2man
|
||||
|
||||
# Build architecture-independent files here.
|
||||
binary-indep: build install
|
||||
dh_testdir
|
||||
dh_testroot
|
||||
dh_installchangelogs -k ChangeLog
|
||||
dh_installdocs
|
||||
dh_pycentral
|
||||
dh_compress -X.py
|
||||
dh_fixperms
|
||||
dh_installdeb
|
||||
dh_gencontrol
|
||||
dh_md5sums
|
||||
dh_builddeb
|
||||
|
||||
# Build architecture-dependent files here.
|
||||
binary-arch: build install
|
||||
# We have nothing to do by default.
|
||||
|
||||
binary: binary-indep binary-arch
|
||||
.PHONY: build clean binary-indep binary-arch binary install configure
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -0,0 +1,544 @@
|
|||
#!/usr/bin/env python
|
||||
|
||||
# Copyright (c) 2006, Google Inc.
|
||||
# All rights reserved.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without
|
||||
# modification, are permitted provided that the following conditions are
|
||||
# met:
|
||||
#
|
||||
# * Redistributions of source code must retain the above copyright
|
||||
# notice, this list of conditions and the following disclaimer.
|
||||
# * Redistributions in binary form must reproduce the above
|
||||
# copyright notice, this list of conditions and the following disclaimer
|
||||
# in the documentation and/or other materials provided with the
|
||||
# distribution.
|
||||
# * Neither the name of Google Inc. nor the names of its
|
||||
# contributors may be used to endorse or promote products derived from
|
||||
# this software without specific prior written permission.
|
||||
#
|
||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
|
||||
"""gflags2man runs a Google flags base program and generates a man page.
|
||||
|
||||
Run the program, parse the output, and then format that into a man
|
||||
page.
|
||||
|
||||
Usage:
|
||||
gflags2man <program> [program] ...
|
||||
"""
|
||||
|
||||
# TODO(csilvers): work with windows paths (\) as well as unix (/)
|
||||
|
||||
# This may seem a bit of an end run, but it: doesn't bloat flags, can
|
||||
# support python/java/C++, supports older executables, and can be
|
||||
# extended to other document formats.
|
||||
# Inspired by help2man.
|
||||
|
||||
|
||||
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
import stat
|
||||
import time
|
||||
|
||||
import gflags
|
||||
|
||||
_VERSION = '0.1'
|
||||
|
||||
|
||||
def _GetDefaultDestDir():
|
||||
home = os.environ.get('HOME', '')
|
||||
homeman = os.path.join(home, 'man', 'man1')
|
||||
if home and os.path.exists(homeman):
|
||||
return homeman
|
||||
else:
|
||||
return os.environ.get('TMPDIR', '/tmp')
|
||||
|
||||
FLAGS = gflags.FLAGS
|
||||
gflags.DEFINE_string('dest_dir', _GetDefaultDestDir(),
|
||||
'Directory to write resulting manpage to.'
|
||||
' Specify \'-\' for stdout')
|
||||
gflags.DEFINE_string('help_flag', '--help',
|
||||
'Option to pass to target program in to get help')
|
||||
gflags.DEFINE_integer('v', 0, 'verbosity level to use for output')
|
||||
|
||||
|
||||
_MIN_VALID_USAGE_MSG = 9 # if fewer lines than this, help is suspect
|
||||
|
||||
|
||||
class Logging:
|
||||
"""A super-simple logging class"""
|
||||
def error(self, msg): print >>sys.stderr, "ERROR: ", msg
|
||||
def warn(self, msg): print >>sys.stderr, "WARNING: ", msg
|
||||
def info(self, msg): print msg
|
||||
def debug(self, msg): self.vlog(1, msg)
|
||||
def vlog(self, level, msg):
|
||||
if FLAGS.v >= level: print msg
|
||||
logging = Logging()
|
||||
class App:
|
||||
def usage(self, shorthelp=0):
|
||||
print >>sys.stderr, __doc__
|
||||
print >>sys.stderr, "flags:"
|
||||
print >>sys.stderr, str(FLAGS)
|
||||
def run(self):
|
||||
main(sys.argv)
|
||||
app = App()
|
||||
|
||||
|
||||
def GetRealPath(filename):
|
||||
"""Given an executable filename, find in the PATH or find absolute path.
|
||||
Args:
|
||||
filename An executable filename (string)
|
||||
Returns:
|
||||
Absolute version of filename.
|
||||
None if filename could not be found locally, absolutely, or in PATH
|
||||
"""
|
||||
if os.path.isabs(filename): # already absolute
|
||||
return filename
|
||||
|
||||
if filename.startswith('./') or filename.startswith('../'): # relative
|
||||
return os.path.abspath(filename)
|
||||
|
||||
path = os.getenv('PATH', '')
|
||||
for directory in path.split(':'):
|
||||
tryname = os.path.join(directory, filename)
|
||||
if os.path.exists(tryname):
|
||||
if not os.path.isabs(directory): # relative directory
|
||||
return os.path.abspath(tryname)
|
||||
return tryname
|
||||
if os.path.exists(filename):
|
||||
return os.path.abspath(filename)
|
||||
return None # could not determine
|
||||
|
||||
class Flag(object):
|
||||
"""The information about a single flag."""
|
||||
|
||||
def __init__(self, flag_desc, help):
|
||||
"""Create the flag object.
|
||||
Args:
|
||||
flag_desc The command line forms this could take. (string)
|
||||
help The help text (string)
|
||||
"""
|
||||
self.desc = flag_desc # the command line forms
|
||||
self.help = help # the help text
|
||||
self.default = '' # default value
|
||||
self.tips = '' # parsing/syntax tips
|
||||
|
||||
|
||||
class ProgramInfo(object):
|
||||
"""All the information gleaned from running a program with --help."""
|
||||
|
||||
# Match a module block start, for python scripts --help
|
||||
# "goopy.logging:"
|
||||
module_py_re = re.compile(r'(\S.+):$')
|
||||
# match the start of a flag listing
|
||||
# " -v,--verbosity: Logging verbosity"
|
||||
flag_py_re = re.compile(r'\s+(-\S+):\s+(.*)$')
|
||||
# " (default: '0')"
|
||||
flag_default_py_re = re.compile(r'\s+\(default:\s+\'(.*)\'\)$')
|
||||
# " (an integer)"
|
||||
flag_tips_py_re = re.compile(r'\s+\((.*)\)$')
|
||||
|
||||
# Match a module block start, for c++ programs --help
|
||||
# "google/base/commandlineflags":
|
||||
module_c_re = re.compile(r'\s+Flags from (\S.+):$')
|
||||
# match the start of a flag listing
|
||||
# " -v,--verbosity: Logging verbosity"
|
||||
flag_c_re = re.compile(r'\s+(-\S+)\s+(.*)$')
|
||||
|
||||
# Match a module block start, for java programs --help
|
||||
# "com.google.common.flags"
|
||||
module_java_re = re.compile(r'\s+Flags for (\S.+):$')
|
||||
# match the start of a flag listing
|
||||
# " -v,--verbosity: Logging verbosity"
|
||||
flag_java_re = re.compile(r'\s+(-\S+)\s+(.*)$')
|
||||
|
||||
def __init__(self, executable):
|
||||
"""Create object with executable.
|
||||
Args:
|
||||
executable Program to execute (string)
|
||||
"""
|
||||
self.long_name = executable
|
||||
self.name = os.path.basename(executable) # name
|
||||
# Get name without extension (PAR files)
|
||||
(self.short_name, self.ext) = os.path.splitext(self.name)
|
||||
self.executable = GetRealPath(executable) # name of the program
|
||||
self.output = [] # output from the program. List of lines.
|
||||
self.desc = [] # top level description. List of lines
|
||||
self.modules = {} # { section_name(string), [ flags ] }
|
||||
self.module_list = [] # list of module names in their original order
|
||||
self.date = time.localtime(time.time()) # default date info
|
||||
|
||||
def Run(self):
|
||||
"""Run it and collect output.
|
||||
|
||||
Returns:
|
||||
1 (true) If everything went well.
|
||||
0 (false) If there were problems.
|
||||
"""
|
||||
if not self.executable:
|
||||
logging.error('Could not locate "%s"' % self.long_name)
|
||||
return 0
|
||||
|
||||
finfo = os.stat(self.executable)
|
||||
self.date = time.localtime(finfo[stat.ST_MTIME])
|
||||
|
||||
logging.info('Running: %s %s </dev/null 2>&1'
|
||||
% (self.executable, FLAGS.help_flag))
|
||||
# --help output is often routed to stderr, so we combine with stdout.
|
||||
# Re-direct stdin to /dev/null to encourage programs that
|
||||
# don't understand --help to exit.
|
||||
(child_stdin, child_stdout_and_stderr) = os.popen4(
|
||||
[self.executable, FLAGS.help_flag])
|
||||
child_stdin.close() # '</dev/null'
|
||||
self.output = child_stdout_and_stderr.readlines()
|
||||
child_stdout_and_stderr.close()
|
||||
if len(self.output) < _MIN_VALID_USAGE_MSG:
|
||||
logging.error('Error: "%s %s" returned only %d lines: %s'
|
||||
% (self.name, FLAGS.help_flag,
|
||||
len(self.output), self.output))
|
||||
return 0
|
||||
return 1
|
||||
|
||||
def Parse(self):
|
||||
"""Parse program output."""
|
||||
(start_line, lang) = self.ParseDesc()
|
||||
if start_line < 0:
|
||||
return
|
||||
if 'python' == lang:
|
||||
self.ParsePythonFlags(start_line)
|
||||
elif 'c' == lang:
|
||||
self.ParseCFlags(start_line)
|
||||
elif 'java' == lang:
|
||||
self.ParseJavaFlags(start_line)
|
||||
|
||||
def ParseDesc(self, start_line=0):
|
||||
"""Parse the initial description.
|
||||
|
||||
This could be Python or C++.
|
||||
|
||||
Returns:
|
||||
(start_line, lang_type)
|
||||
start_line Line to start parsing flags on (int)
|
||||
lang_type Either 'python' or 'c'
|
||||
(-1, '') if the flags start could not be found
|
||||
"""
|
||||
exec_mod_start = self.executable + ':'
|
||||
|
||||
after_blank = 0
|
||||
start_line = 0 # ignore the passed-in arg for now (?)
|
||||
for start_line in range(start_line, len(self.output)): # collect top description
|
||||
line = self.output[start_line].rstrip()
|
||||
# Python flags start with 'flags:\n'
|
||||
if ('flags:' == line
|
||||
and len(self.output) > start_line+1
|
||||
and '' == self.output[start_line+1].rstrip()):
|
||||
start_line += 2
|
||||
logging.debug('Flags start (python): %s' % line)
|
||||
return (start_line, 'python')
|
||||
# SWIG flags just have the module name followed by colon.
|
||||
if exec_mod_start == line:
|
||||
logging.debug('Flags start (swig): %s' % line)
|
||||
return (start_line, 'python')
|
||||
# C++ flags begin after a blank line and with a constant string
|
||||
if after_blank and line.startswith(' Flags from '):
|
||||
logging.debug('Flags start (c): %s' % line)
|
||||
return (start_line, 'c')
|
||||
# java flags begin with a constant string
|
||||
if line == 'where flags are':
|
||||
logging.debug('Flags start (java): %s' % line)
|
||||
start_line += 2 # skip "Standard flags:"
|
||||
return (start_line, 'java')
|
||||
|
||||
logging.debug('Desc: %s' % line)
|
||||
self.desc.append(line)
|
||||
after_blank = (line == '')
|
||||
else:
|
||||
logging.warn('Never found the start of the flags section for "%s"!'
|
||||
% self.long_name)
|
||||
return (-1, '')
|
||||
|
||||
def ParsePythonFlags(self, start_line=0):
|
||||
"""Parse python/swig style flags."""
|
||||
modname = None # name of current module
|
||||
modlist = []
|
||||
flag = None
|
||||
for line_num in range(start_line, len(self.output)): # collect flags
|
||||
line = self.output[line_num].rstrip()
|
||||
if not line: # blank
|
||||
continue
|
||||
|
||||
mobj = self.module_py_re.match(line)
|
||||
if mobj: # start of a new module
|
||||
modname = mobj.group(1)
|
||||
logging.debug('Module: %s' % line)
|
||||
if flag:
|
||||
modlist.append(flag)
|
||||
self.module_list.append(modname)
|
||||
self.modules.setdefault(modname, [])
|
||||
modlist = self.modules[modname]
|
||||
flag = None
|
||||
continue
|
||||
|
||||
mobj = self.flag_py_re.match(line)
|
||||
if mobj: # start of a new flag
|
||||
if flag:
|
||||
modlist.append(flag)
|
||||
logging.debug('Flag: %s' % line)
|
||||
flag = Flag(mobj.group(1), mobj.group(2))
|
||||
continue
|
||||
|
||||
if not flag: # continuation of a flag
|
||||
logging.error('Flag info, but no current flag "%s"' % line)
|
||||
mobj = self.flag_default_py_re.match(line)
|
||||
if mobj: # (default: '...')
|
||||
flag.default = mobj.group(1)
|
||||
logging.debug('Fdef: %s' % line)
|
||||
continue
|
||||
mobj = self.flag_tips_py_re.match(line)
|
||||
if mobj: # (tips)
|
||||
flag.tips = mobj.group(1)
|
||||
logging.debug('Ftip: %s' % line)
|
||||
continue
|
||||
if flag and flag.help:
|
||||
flag.help += line # multiflags tack on an extra line
|
||||
else:
|
||||
logging.info('Extra: %s' % line)
|
||||
if flag:
|
||||
modlist.append(flag)
|
||||
|
||||
def ParseCFlags(self, start_line=0):
|
||||
"""Parse C style flags."""
|
||||
modname = None # name of current module
|
||||
modlist = []
|
||||
flag = None
|
||||
for line_num in range(start_line, len(self.output)): # collect flags
|
||||
line = self.output[line_num].rstrip()
|
||||
if not line: # blank lines terminate flags
|
||||
if flag: # save last flag
|
||||
modlist.append(flag)
|
||||
flag = None
|
||||
continue
|
||||
|
||||
mobj = self.module_c_re.match(line)
|
||||
if mobj: # start of a new module
|
||||
modname = mobj.group(1)
|
||||
logging.debug('Module: %s' % line)
|
||||
if flag:
|
||||
modlist.append(flag)
|
||||
self.module_list.append(modname)
|
||||
self.modules.setdefault(modname, [])
|
||||
modlist = self.modules[modname]
|
||||
flag = None
|
||||
continue
|
||||
|
||||
mobj = self.flag_c_re.match(line)
|
||||
if mobj: # start of a new flag
|
||||
if flag: # save last flag
|
||||
modlist.append(flag)
|
||||
logging.debug('Flag: %s' % line)
|
||||
flag = Flag(mobj.group(1), mobj.group(2))
|
||||
continue
|
||||
|
||||
# append to flag help. type and default are part of the main text
|
||||
if flag:
|
||||
flag.help += ' ' + line.strip()
|
||||
else:
|
||||
logging.info('Extra: %s' % line)
|
||||
if flag:
|
||||
modlist.append(flag)
|
||||
|
||||
def ParseJavaFlags(self, start_line=0):
|
||||
"""Parse Java style flags (com.google.common.flags)."""
|
||||
# The java flags prints starts with a "Standard flags" "module"
|
||||
# that doesn't follow the standard module syntax.
|
||||
modname = 'Standard flags' # name of current module
|
||||
self.module_list.append(modname)
|
||||
self.modules.setdefault(modname, [])
|
||||
modlist = self.modules[modname]
|
||||
flag = None
|
||||
|
||||
for line_num in range(start_line, len(self.output)): # collect flags
|
||||
line = self.output[line_num].rstrip()
|
||||
logging.vlog(2, 'Line: "%s"' % line)
|
||||
if not line: # blank lines terminate module
|
||||
if flag: # save last flag
|
||||
modlist.append(flag)
|
||||
flag = None
|
||||
continue
|
||||
|
||||
mobj = self.module_java_re.match(line)
|
||||
if mobj: # start of a new module
|
||||
modname = mobj.group(1)
|
||||
logging.debug('Module: %s' % line)
|
||||
if flag:
|
||||
modlist.append(flag)
|
||||
self.module_list.append(modname)
|
||||
self.modules.setdefault(modname, [])
|
||||
modlist = self.modules[modname]
|
||||
flag = None
|
||||
continue
|
||||
|
||||
mobj = self.flag_java_re.match(line)
|
||||
if mobj: # start of a new flag
|
||||
if flag: # save last flag
|
||||
modlist.append(flag)
|
||||
logging.debug('Flag: %s' % line)
|
||||
flag = Flag(mobj.group(1), mobj.group(2))
|
||||
continue
|
||||
|
||||
# append to flag help. type and default are part of the main text
|
||||
if flag:
|
||||
flag.help += ' ' + line.strip()
|
||||
else:
|
||||
logging.info('Extra: %s' % line)
|
||||
if flag:
|
||||
modlist.append(flag)
|
||||
|
||||
def Filter(self):
|
||||
"""Filter parsed data to create derived fields."""
|
||||
if not self.desc:
|
||||
self.short_desc = ''
|
||||
return
|
||||
|
||||
for i in range(len(self.desc)): # replace full path with name
|
||||
if self.desc[i].find(self.executable) >= 0:
|
||||
self.desc[i] = self.desc[i].replace(self.executable, self.name)
|
||||
|
||||
self.short_desc = self.desc[0]
|
||||
word_list = self.short_desc.split(' ')
|
||||
all_names = [ self.name, self.short_name, ]
|
||||
# Since the short_desc is always listed right after the name,
|
||||
# trim it from the short_desc
|
||||
while word_list and (word_list[0] in all_names
|
||||
or word_list[0].lower() in all_names):
|
||||
del word_list[0]
|
||||
self.short_desc = '' # signal need to reconstruct
|
||||
if not self.short_desc and word_list:
|
||||
self.short_desc = ' '.join(word_list)
|
||||
|
||||
|
||||
class GenerateDoc(object):
|
||||
"""Base class to output flags information."""
|
||||
|
||||
def __init__(self, proginfo, directory='.'):
|
||||
"""Create base object.
|
||||
Args:
|
||||
proginfo A ProgramInfo object
|
||||
directory Directory to write output into
|
||||
"""
|
||||
self.info = proginfo
|
||||
self.dirname = directory
|
||||
|
||||
def Output(self):
|
||||
"""Output all sections of the page."""
|
||||
self.Open()
|
||||
self.Header()
|
||||
self.Body()
|
||||
self.Footer()
|
||||
|
||||
def Open(self): raise NotImplementedError # define in subclass
|
||||
def Header(self): raise NotImplementedError # define in subclass
|
||||
def Body(self): raise NotImplementedError # define in subclass
|
||||
def Footer(self): raise NotImplementedError # define in subclass
|
||||
|
||||
|
||||
class GenerateMan(GenerateDoc):
|
||||
"""Output a man page."""
|
||||
|
||||
def __init__(self, proginfo, directory='.'):
|
||||
"""Create base object.
|
||||
Args:
|
||||
proginfo A ProgramInfo object
|
||||
directory Directory to write output into
|
||||
"""
|
||||
GenerateDoc.__init__(self, proginfo, directory)
|
||||
|
||||
def Open(self):
|
||||
if self.dirname == '-':
|
||||
logging.info('Writing to stdout')
|
||||
self.fp = sys.stdout
|
||||
else:
|
||||
self.file_path = '%s.1' % os.path.join(self.dirname, self.info.name)
|
||||
logging.info('Writing: %s' % self.file_path)
|
||||
self.fp = open(self.file_path, 'w')
|
||||
|
||||
def Header(self):
|
||||
self.fp.write(
|
||||
'.\\" DO NOT MODIFY THIS FILE! It was generated by gflags2man %s\n'
|
||||
% _VERSION)
|
||||
self.fp.write(
|
||||
'.TH %s "1" "%s" "%s" "User Commands"\n'
|
||||
% (self.info.name, time.strftime('%x', self.info.date), self.info.name))
|
||||
self.fp.write(
|
||||
'.SH NAME\n%s \\- %s\n' % (self.info.name, self.info.short_desc))
|
||||
self.fp.write(
|
||||
'.SH SYNOPSIS\n.B %s\n[\\fIFLAGS\\fR]...\n' % self.info.name)
|
||||
|
||||
def Body(self):
|
||||
self.fp.write(
|
||||
'.SH DESCRIPTION\n.\\" Add any additional description here\n.PP\n')
|
||||
for ln in self.info.desc:
|
||||
self.fp.write('%s\n' % ln)
|
||||
self.fp.write(
|
||||
'.SH OPTIONS\n')
|
||||
# This shows flags in the original order
|
||||
for modname in self.info.module_list:
|
||||
if modname.find(self.info.executable) >= 0:
|
||||
mod = modname.replace(self.info.executable, self.info.name)
|
||||
else:
|
||||
mod = modname
|
||||
self.fp.write('\n.P\n.I %s\n' % mod)
|
||||
for flag in self.info.modules[modname]:
|
||||
help_string = flag.help
|
||||
if flag.default or flag.tips:
|
||||
help_string += '\n.br\n'
|
||||
if flag.default:
|
||||
help_string += ' (default: \'%s\')' % flag.default
|
||||
if flag.tips:
|
||||
help_string += ' (%s)' % flag.tips
|
||||
self.fp.write(
|
||||
'.TP\n%s\n%s\n' % (flag.desc, help_string))
|
||||
|
||||
def Footer(self):
|
||||
self.fp.write(
|
||||
'.SH COPYRIGHT\nCopyright \(co %s Google.\n'
|
||||
% time.strftime('%Y', self.info.date))
|
||||
self.fp.write('Gflags2man created this page from "%s %s" output.\n'
|
||||
% (self.info.name, FLAGS.help_flag))
|
||||
self.fp.write('\nGflags2man was written by Dan Christian. '
|
||||
' Note that the date on this'
|
||||
' page is the modification date of %s.\n' % self.info.name)
|
||||
|
||||
|
||||
def main(argv):
|
||||
argv = FLAGS(argv) # handles help as well
|
||||
if len(argv) <= 1:
|
||||
app.usage(shorthelp=1)
|
||||
return 1
|
||||
|
||||
for arg in argv[1:]:
|
||||
prog = ProgramInfo(arg)
|
||||
if not prog.Run():
|
||||
continue
|
||||
prog.Parse()
|
||||
prog.Filter()
|
||||
doc = GenerateMan(prog, FLAGS.dest_dir)
|
||||
doc.Output()
|
||||
return 0
|
||||
|
||||
if __name__ == '__main__':
|
||||
app.run()
|
|
@ -0,0 +1,187 @@
|
|||
#!/usr/bin/env python
|
||||
|
||||
# Copyright (c) 2010, Google Inc.
|
||||
# All rights reserved.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without
|
||||
# modification, are permitted provided that the following conditions are
|
||||
# met:
|
||||
#
|
||||
# * Redistributions of source code must retain the above copyright
|
||||
# notice, this list of conditions and the following disclaimer.
|
||||
# * Redistributions in binary form must reproduce the above
|
||||
# copyright notice, this list of conditions and the following disclaimer
|
||||
# in the documentation and/or other materials provided with the
|
||||
# distribution.
|
||||
# * Neither the name of Google Inc. nor the names of its
|
||||
# contributors may be used to endorse or promote products derived from
|
||||
# this software without specific prior written permission.
|
||||
#
|
||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
"""Module to enforce different constraints on flags.
|
||||
|
||||
A validator represents an invariant, enforced over a one or more flags.
|
||||
See 'FLAGS VALIDATORS' in gflags.py's docstring for a usage manual.
|
||||
"""
|
||||
|
||||
__author__ = 'olexiy@google.com (Olexiy Oryeshko)'
|
||||
|
||||
|
||||
class Error(Exception):
|
||||
"""Thrown If validator constraint is not satisfied."""
|
||||
|
||||
|
||||
class Validator(object):
|
||||
"""Base class for flags validators.
|
||||
|
||||
Users should NOT overload these classes, and use gflags.Register...
|
||||
methods instead.
|
||||
"""
|
||||
|
||||
# Used to assign each validator an unique insertion_index
|
||||
validators_count = 0
|
||||
|
||||
def __init__(self, checker, message):
|
||||
"""Constructor to create all validators.
|
||||
|
||||
Args:
|
||||
checker: function to verify the constraint.
|
||||
Input of this method varies, see SimpleValidator and
|
||||
DictionaryValidator for a detailed description.
|
||||
message: string, error message to be shown to the user
|
||||
"""
|
||||
self.checker = checker
|
||||
self.message = message
|
||||
Validator.validators_count += 1
|
||||
# Used to assert validators in the order they were registered (CL/18694236)
|
||||
self.insertion_index = Validator.validators_count
|
||||
|
||||
def Verify(self, flag_values):
|
||||
"""Verify that constraint is satisfied.
|
||||
|
||||
flags library calls this method to verify Validator's constraint.
|
||||
Args:
|
||||
flag_values: gflags.FlagValues, containing all flags
|
||||
Raises:
|
||||
Error: if constraint is not satisfied.
|
||||
"""
|
||||
param = self._GetInputToCheckerFunction(flag_values)
|
||||
if not self.checker(param):
|
||||
raise Error(self.message)
|
||||
|
||||
def GetFlagsNames(self):
|
||||
"""Return the names of the flags checked by this validator.
|
||||
|
||||
Returns:
|
||||
[string], names of the flags
|
||||
"""
|
||||
raise NotImplementedError('This method should be overloaded')
|
||||
|
||||
def PrintFlagsWithValues(self, flag_values):
|
||||
raise NotImplementedError('This method should be overloaded')
|
||||
|
||||
def _GetInputToCheckerFunction(self, flag_values):
|
||||
"""Given flag values, construct the input to be given to checker.
|
||||
|
||||
Args:
|
||||
flag_values: gflags.FlagValues, containing all flags.
|
||||
Returns:
|
||||
Return type depends on the specific validator.
|
||||
"""
|
||||
raise NotImplementedError('This method should be overloaded')
|
||||
|
||||
|
||||
class SimpleValidator(Validator):
|
||||
"""Validator behind RegisterValidator() method.
|
||||
|
||||
Validates that a single flag passes its checker function. The checker function
|
||||
takes the flag value and returns True (if value looks fine) or, if flag value
|
||||
is not valid, either returns False or raises an Exception."""
|
||||
def __init__(self, flag_name, checker, message):
|
||||
"""Constructor.
|
||||
|
||||
Args:
|
||||
flag_name: string, name of the flag.
|
||||
checker: function to verify the validator.
|
||||
input - value of the corresponding flag (string, boolean, etc).
|
||||
output - Boolean. Must return True if validator constraint is satisfied.
|
||||
If constraint is not satisfied, it should either return False or
|
||||
raise Error.
|
||||
message: string, error message to be shown to the user if validator's
|
||||
condition is not satisfied
|
||||
"""
|
||||
super(SimpleValidator, self).__init__(checker, message)
|
||||
self.flag_name = flag_name
|
||||
|
||||
def GetFlagsNames(self):
|
||||
return [self.flag_name]
|
||||
|
||||
def PrintFlagsWithValues(self, flag_values):
|
||||
return 'flag --%s=%s' % (self.flag_name, flag_values[self.flag_name].value)
|
||||
|
||||
def _GetInputToCheckerFunction(self, flag_values):
|
||||
"""Given flag values, construct the input to be given to checker.
|
||||
|
||||
Args:
|
||||
flag_values: gflags.FlagValues
|
||||
Returns:
|
||||
value of the corresponding flag.
|
||||
"""
|
||||
return flag_values[self.flag_name].value
|
||||
|
||||
|
||||
class DictionaryValidator(Validator):
|
||||
"""Validator behind RegisterDictionaryValidator method.
|
||||
|
||||
Validates that flag values pass their common checker function. The checker
|
||||
function takes flag values and returns True (if values look fine) or,
|
||||
if values are not valid, either returns False or raises an Exception.
|
||||
"""
|
||||
def __init__(self, flag_names, checker, message):
|
||||
"""Constructor.
|
||||
|
||||
Args:
|
||||
flag_names: [string], containing names of the flags used by checker.
|
||||
checker: function to verify the validator.
|
||||
input - dictionary, with keys() being flag_names, and value for each
|
||||
key being the value of the corresponding flag (string, boolean, etc).
|
||||
output - Boolean. Must return True if validator constraint is satisfied.
|
||||
If constraint is not satisfied, it should either return False or
|
||||
raise Error.
|
||||
message: string, error message to be shown to the user if validator's
|
||||
condition is not satisfied
|
||||
"""
|
||||
super(DictionaryValidator, self).__init__(checker, message)
|
||||
self.flag_names = flag_names
|
||||
|
||||
def _GetInputToCheckerFunction(self, flag_values):
|
||||
"""Given flag values, construct the input to be given to checker.
|
||||
|
||||
Args:
|
||||
flag_values: gflags.FlagValues
|
||||
Returns:
|
||||
dictionary, with keys() being self.lag_names, and value for each key
|
||||
being the value of the corresponding flag (string, boolean, etc).
|
||||
"""
|
||||
return dict([key, flag_values[key].value] for key in self.flag_names)
|
||||
|
||||
def PrintFlagsWithValues(self, flag_values):
|
||||
prefix = 'flags '
|
||||
flags_with_values = []
|
||||
for key in self.flag_names:
|
||||
flags_with_values.append('%s=%s' % (key, flag_values[key].value))
|
||||
return prefix + ', '.join(flags_with_values)
|
||||
|
||||
def GetFlagsNames(self):
|
||||
return self.flag_names
|
|
@ -0,0 +1,10 @@
|
|||
Metadata-Version: 1.0
|
||||
Name: python-gflags
|
||||
Version: 2.0
|
||||
Summary: Google Commandline Flags Module
|
||||
Home-page: http://code.google.com/p/python-gflags
|
||||
Author: Google Inc. and others
|
||||
Author-email: google-gflags@googlegroups.com
|
||||
License: BSD
|
||||
Description: UNKNOWN
|
||||
Platform: UNKNOWN
|
|
@ -0,0 +1,30 @@
|
|||
AUTHORS
|
||||
COPYING
|
||||
ChangeLog
|
||||
MANIFEST.in
|
||||
Makefile
|
||||
NEWS
|
||||
README
|
||||
gflags.py
|
||||
gflags2man.py
|
||||
gflags_validators.py
|
||||
setup.py
|
||||
debian/README
|
||||
debian/changelog
|
||||
debian/compat
|
||||
debian/control
|
||||
debian/copyright
|
||||
debian/docs
|
||||
debian/rules
|
||||
python_gflags.egg-info/PKG-INFO
|
||||
python_gflags.egg-info/SOURCES.txt
|
||||
python_gflags.egg-info/dependency_links.txt
|
||||
python_gflags.egg-info/top_level.txt
|
||||
tests/gflags_googletest.py
|
||||
tests/gflags_helpxml_test.py
|
||||
tests/gflags_unittest.py
|
||||
tests/gflags_validators_test.py
|
||||
tests/flags_modules_for_testing/__init__.py
|
||||
tests/flags_modules_for_testing/module_bar.py
|
||||
tests/flags_modules_for_testing/module_baz.py
|
||||
tests/flags_modules_for_testing/module_foo.py
|
|
@ -0,0 +1 @@
|
|||
|
|
@ -0,0 +1,2 @@
|
|||
gflags
|
||||
gflags_validators
|
|
@ -0,0 +1,5 @@
|
|||
[egg_info]
|
||||
tag_build =
|
||||
tag_date = 0
|
||||
tag_svn_revision = 0
|
||||
|
|
@ -0,0 +1,44 @@
|
|||
#!/usr/bin/env python
|
||||
|
||||
# Copyright (c) 2007, Google Inc.
|
||||
# All rights reserved.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without
|
||||
# modification, are permitted provided that the following conditions are
|
||||
# met:
|
||||
#
|
||||
# * Redistributions of source code must retain the above copyright
|
||||
# notice, this list of conditions and the following disclaimer.
|
||||
# * Redistributions in binary form must reproduce the above
|
||||
# copyright notice, this list of conditions and the following disclaimer
|
||||
# in the documentation and/or other materials provided with the
|
||||
# distribution.
|
||||
# * Neither the name of Google Inc. nor the names of its
|
||||
# contributors may be used to endorse or promote products derived from
|
||||
# this software without specific prior written permission.
|
||||
#
|
||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
from setuptools import setup
|
||||
|
||||
setup(name='python-gflags',
|
||||
version='2.0',
|
||||
description='Google Commandline Flags Module',
|
||||
license='BSD',
|
||||
author='Google Inc. and others',
|
||||
author_email='google-gflags@googlegroups.com',
|
||||
url='http://code.google.com/p/python-gflags',
|
||||
py_modules=["gflags", "gflags_validators"],
|
||||
data_files=[("bin", ["gflags2man.py"])],
|
||||
include_package_data=True,
|
||||
)
|
|
@ -0,0 +1,135 @@
|
|||
#!/usr/bin/env python
|
||||
|
||||
# Copyright (c) 2009, Google Inc.
|
||||
# All rights reserved.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without
|
||||
# modification, are permitted provided that the following conditions are
|
||||
# met:
|
||||
#
|
||||
# * Redistributions of source code must retain the above copyright
|
||||
# notice, this list of conditions and the following disclaimer.
|
||||
# * Redistributions in binary form must reproduce the above
|
||||
# copyright notice, this list of conditions and the following disclaimer
|
||||
# in the documentation and/or other materials provided with the
|
||||
# distribution.
|
||||
# * Neither the name of Google Inc. nor the names of its
|
||||
# contributors may be used to endorse or promote products derived from
|
||||
# this software without specific prior written permission.
|
||||
#
|
||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
|
||||
"""Auxiliary module for testing gflags.py.
|
||||
|
||||
The purpose of this module is to define a few flags. We want to make
|
||||
sure the unit tests for gflags.py involve more than one module.
|
||||
"""
|
||||
|
||||
__author__ = 'salcianu@google.com (Alex Salcianu)'
|
||||
|
||||
__pychecker__ = 'no-local' # for unittest
|
||||
|
||||
import gflags
|
||||
|
||||
FLAGS = gflags.FLAGS
|
||||
|
||||
|
||||
def DefineFlags(flag_values=FLAGS):
|
||||
"""Defines some flags.
|
||||
|
||||
Args:
|
||||
flag_values: The FlagValues object we want to register the flags
|
||||
with.
|
||||
"""
|
||||
# The 'tmod_bar_' prefix (short for 'test_module_bar') ensures there
|
||||
# is no name clash with the existing flags.
|
||||
gflags.DEFINE_boolean('tmod_bar_x', True, 'Boolean flag.',
|
||||
flag_values=flag_values)
|
||||
gflags.DEFINE_string('tmod_bar_y', 'default', 'String flag.',
|
||||
flag_values=flag_values)
|
||||
gflags.DEFINE_boolean('tmod_bar_z', False,
|
||||
'Another boolean flag from module bar.',
|
||||
flag_values=flag_values)
|
||||
gflags.DEFINE_integer('tmod_bar_t', 4, 'Sample int flag.',
|
||||
flag_values=flag_values)
|
||||
gflags.DEFINE_integer('tmod_bar_u', 5, 'Sample int flag.',
|
||||
flag_values=flag_values)
|
||||
gflags.DEFINE_integer('tmod_bar_v', 6, 'Sample int flag.',
|
||||
flag_values=flag_values)
|
||||
|
||||
|
||||
def RemoveOneFlag(flag_name, flag_values=FLAGS):
|
||||
"""Removes the definition of one flag from gflags.FLAGS.
|
||||
|
||||
Note: if the flag is not defined in gflags.FLAGS, this function does
|
||||
not do anything (in particular, it does not raise any exception).
|
||||
|
||||
Motivation: We use this function for cleanup *after* a test: if
|
||||
there was a failure during a test and not all flags were declared,
|
||||
we do not want the cleanup code to crash.
|
||||
|
||||
Args:
|
||||
flag_name: A string, the name of the flag to delete.
|
||||
flag_values: The FlagValues object we remove the flag from.
|
||||
"""
|
||||
if flag_name in flag_values.FlagDict():
|
||||
flag_values.__delattr__(flag_name)
|
||||
|
||||
|
||||
def NamesOfDefinedFlags():
|
||||
"""Returns: List of names of the flags declared in this module."""
|
||||
return ['tmod_bar_x',
|
||||
'tmod_bar_y',
|
||||
'tmod_bar_z',
|
||||
'tmod_bar_t',
|
||||
'tmod_bar_u',
|
||||
'tmod_bar_v']
|
||||
|
||||
|
||||
def RemoveFlags(flag_values=FLAGS):
|
||||
"""Deletes the flag definitions done by the above DefineFlags().
|
||||
|
||||
Args:
|
||||
flag_values: The FlagValues object we remove the flags from.
|
||||
"""
|
||||
for flag_name in NamesOfDefinedFlags():
|
||||
RemoveOneFlag(flag_name, flag_values=flag_values)
|
||||
|
||||
|
||||
def GetModuleName():
|
||||
"""Uses gflags._GetCallingModule() to return the name of this module.
|
||||
|
||||
For checking that _GetCallingModule works as expected.
|
||||
|
||||
Returns:
|
||||
A string, the name of this module.
|
||||
"""
|
||||
# Calling the protected _GetCallingModule generates a lint warning,
|
||||
# but we do not have any other alternative to test that function.
|
||||
return gflags._GetCallingModule()
|
||||
|
||||
|
||||
def ExecuteCode(code, global_dict):
|
||||
"""Executes some code in a given global environment.
|
||||
|
||||
For testing of _GetCallingModule.
|
||||
|
||||
Args:
|
||||
code: A string, the code to be executed.
|
||||
global_dict: A dictionary, the global environment that code should
|
||||
be executed in.
|
||||
"""
|
||||
# Indeed, using exec generates a lint warning. But some user code
|
||||
# actually uses exec, and we have to test for it ...
|
||||
exec code in global_dict
|
|
@ -0,0 +1,45 @@
|
|||
#!/usr/bin/env python
|
||||
|
||||
# Copyright (c) 2009, Google Inc.
|
||||
# All rights reserved.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without
|
||||
# modification, are permitted provided that the following conditions are
|
||||
# met:
|
||||
#
|
||||
# * Redistributions of source code must retain the above copyright
|
||||
# notice, this list of conditions and the following disclaimer.
|
||||
# * Redistributions in binary form must reproduce the above
|
||||
# copyright notice, this list of conditions and the following disclaimer
|
||||
# in the documentation and/or other materials provided with the
|
||||
# distribution.
|
||||
# * Neither the name of Google Inc. nor the names of its
|
||||
# contributors may be used to endorse or promote products derived from
|
||||
# this software without specific prior written permission.
|
||||
#
|
||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
"""Auxiliary module for testing gflags.py.
|
||||
|
||||
The purpose of this module is to test the behavior of flags that are defined
|
||||
before main() executes.
|
||||
"""
|
||||
|
||||
|
||||
|
||||
|
||||
import gflags
|
||||
|
||||
FLAGS = gflags.FLAGS
|
||||
|
||||
gflags.DEFINE_boolean('tmod_baz_x', True, 'Boolean flag.')
|
|
@ -0,0 +1,141 @@
|
|||
#!/usr/bin/env python
|
||||
#
|
||||
# Copyright (c) 2009, Google Inc.
|
||||
# All rights reserved.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without
|
||||
# modification, are permitted provided that the following conditions are
|
||||
# met:
|
||||
#
|
||||
# * Redistributions of source code must retain the above copyright
|
||||
# notice, this list of conditions and the following disclaimer.
|
||||
# * Redistributions in binary form must reproduce the above
|
||||
# copyright notice, this list of conditions and the following disclaimer
|
||||
# in the documentation and/or other materials provided with the
|
||||
# distribution.
|
||||
# * Neither the name of Google Inc. nor the names of its
|
||||
# contributors may be used to endorse or promote products derived from
|
||||
# this software without specific prior written permission.
|
||||
#
|
||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
"""Auxiliary module for testing gflags.py.
|
||||
|
||||
The purpose of this module is to define a few flags, and declare some
|
||||
other flags as being important. We want to make sure the unit tests
|
||||
for gflags.py involve more than one module.
|
||||
"""
|
||||
|
||||
__author__ = 'salcianu@google.com (Alex Salcianu)'
|
||||
|
||||
__pychecker__ = 'no-local' # for unittest
|
||||
|
||||
import gflags
|
||||
from flags_modules_for_testing import module_bar
|
||||
|
||||
FLAGS = gflags.FLAGS
|
||||
|
||||
|
||||
DECLARED_KEY_FLAGS = ['tmod_bar_x', 'tmod_bar_z', 'tmod_bar_t',
|
||||
# Special (not user-defined) flag:
|
||||
'flagfile']
|
||||
|
||||
|
||||
def DefineFlags(flag_values=FLAGS):
|
||||
"""Defines a few flags."""
|
||||
module_bar.DefineFlags(flag_values=flag_values)
|
||||
# The 'tmod_foo_' prefix (short for 'test_module_foo') ensures that we
|
||||
# have no name clash with existing flags.
|
||||
gflags.DEFINE_boolean('tmod_foo_bool', True, 'Boolean flag from module foo.',
|
||||
flag_values=flag_values)
|
||||
gflags.DEFINE_string('tmod_foo_str', 'default', 'String flag.',
|
||||
flag_values=flag_values)
|
||||
gflags.DEFINE_integer('tmod_foo_int', 3, 'Sample int flag.',
|
||||
flag_values=flag_values)
|
||||
|
||||
|
||||
def DeclareKeyFlags(flag_values=FLAGS):
|
||||
"""Declares a few key flags."""
|
||||
for flag_name in DECLARED_KEY_FLAGS:
|
||||
gflags.DECLARE_key_flag(flag_name, flag_values=flag_values)
|
||||
|
||||
|
||||
def DeclareExtraKeyFlags(flag_values=FLAGS):
|
||||
"""Declares some extra key flags."""
|
||||
gflags.ADOPT_module_key_flags(module_bar, flag_values=flag_values)
|
||||
|
||||
|
||||
def NamesOfDefinedFlags():
|
||||
"""Returns: list of names of flags defined by this module."""
|
||||
return ['tmod_foo_bool', 'tmod_foo_str', 'tmod_foo_int']
|
||||
|
||||
|
||||
def NamesOfDeclaredKeyFlags():
|
||||
"""Returns: list of names of key flags for this module."""
|
||||
return NamesOfDefinedFlags() + DECLARED_KEY_FLAGS
|
||||
|
||||
|
||||
def NamesOfDeclaredExtraKeyFlags():
|
||||
"""Returns the list of names of additional key flags for this module.
|
||||
|
||||
These are the flags that became key for this module only as a result
|
||||
of a call to DeclareExtraKeyFlags() above. I.e., the flags declared
|
||||
by module_bar, that were not already declared as key for this
|
||||
module.
|
||||
|
||||
Returns:
|
||||
The list of names of additional key flags for this module.
|
||||
"""
|
||||
names_of_extra_key_flags = list(module_bar.NamesOfDefinedFlags())
|
||||
for flag_name in NamesOfDeclaredKeyFlags():
|
||||
while flag_name in names_of_extra_key_flags:
|
||||
names_of_extra_key_flags.remove(flag_name)
|
||||
return names_of_extra_key_flags
|
||||
|
||||
|
||||
def RemoveFlags(flag_values=FLAGS):
|
||||
"""Deletes the flag definitions done by the above DefineFlags()."""
|
||||
for flag_name in NamesOfDefinedFlags():
|
||||
module_bar.RemoveOneFlag(flag_name, flag_values=flag_values)
|
||||
module_bar.RemoveFlags(flag_values=flag_values)
|
||||
|
||||
|
||||
def GetModuleName():
|
||||
"""Uses gflags._GetCallingModule() to return the name of this module.
|
||||
|
||||
For checking that _GetCallingModule works as expected.
|
||||
|
||||
Returns:
|
||||
A string, the name of this module.
|
||||
"""
|
||||
# Calling the protected _GetCallingModule generates a lint warning,
|
||||
# but we do not have any other alternative to test that function.
|
||||
return gflags._GetCallingModule()
|
||||
|
||||
|
||||
def DuplicateFlags(flagnames=None):
|
||||
"""Returns a new FlagValues object with the requested flagnames.
|
||||
|
||||
Used to test DuplicateFlagError detection.
|
||||
|
||||
Args:
|
||||
flagnames: str, A list of flag names to create.
|
||||
|
||||
Returns:
|
||||
A FlagValues object with one boolean flag for each name in flagnames.
|
||||
"""
|
||||
flag_values = gflags.FlagValues()
|
||||
for name in flagnames:
|
||||
gflags.DEFINE_boolean(name, False, 'Flag named %s' % (name,),
|
||||
flag_values=flag_values)
|
||||
return flag_values
|
|
@ -0,0 +1,119 @@
|
|||
#!/usr/bin/env python
|
||||
|
||||
# Copyright (c) 2011, Google Inc.
|
||||
# All rights reserved.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without
|
||||
# modification, are permitted provided that the following conditions are
|
||||
# met:
|
||||
#
|
||||
# * Redistributions of source code must retain the above copyright
|
||||
# notice, this list of conditions and the following disclaimer.
|
||||
# * Redistributions in binary form must reproduce the above
|
||||
# copyright notice, this list of conditions and the following disclaimer
|
||||
# in the documentation and/or other materials provided with the
|
||||
# distribution.
|
||||
# * Neither the name of Google Inc. nor the names of its
|
||||
# contributors may be used to endorse or promote products derived from
|
||||
# this software without specific prior written permission.
|
||||
#
|
||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
"""Some simple additions to the unittest framework useful for gflags testing."""
|
||||
|
||||
|
||||
|
||||
import re
|
||||
import unittest
|
||||
|
||||
|
||||
def Sorted(lst):
|
||||
"""Equivalent of sorted(), but not dependent on python version."""
|
||||
sorted_list = lst[:]
|
||||
sorted_list.sort()
|
||||
return sorted_list
|
||||
|
||||
|
||||
def MultiLineEqual(expected, actual):
|
||||
"""Returns True if expected == actual, or returns False and logs."""
|
||||
if actual == expected:
|
||||
return True
|
||||
|
||||
print "Error: FLAGS.MainModuleHelp() didn't return the expected result."
|
||||
print "Got:"
|
||||
print actual
|
||||
print "[End of got]"
|
||||
|
||||
actual_lines = actual.split("\n")
|
||||
expected_lines = expected.split("\n")
|
||||
|
||||
num_actual_lines = len(actual_lines)
|
||||
num_expected_lines = len(expected_lines)
|
||||
|
||||
if num_actual_lines != num_expected_lines:
|
||||
print "Number of actual lines = %d, expected %d" % (
|
||||
num_actual_lines, num_expected_lines)
|
||||
|
||||
num_to_match = min(num_actual_lines, num_expected_lines)
|
||||
|
||||
for i in range(num_to_match):
|
||||
if actual_lines[i] != expected_lines[i]:
|
||||
print "One discrepancy: Got:"
|
||||
print actual_lines[i]
|
||||
print "Expected:"
|
||||
print expected_lines[i]
|
||||
break
|
||||
else:
|
||||
# If we got here, found no discrepancy, print first new line.
|
||||
if num_actual_lines > num_expected_lines:
|
||||
print "New help line:"
|
||||
print actual_lines[num_expected_lines]
|
||||
elif num_expected_lines > num_actual_lines:
|
||||
print "Missing expected help line:"
|
||||
print expected_lines[num_actual_lines]
|
||||
else:
|
||||
print "Bug in this test -- discrepancy detected but not found."
|
||||
|
||||
return False
|
||||
|
||||
|
||||
class TestCase(unittest.TestCase):
|
||||
def assertListEqual(self, list1, list2):
|
||||
"""Asserts that, when sorted, list1 and list2 are identical."""
|
||||
# This exists in python 2.7, but not previous versions. Use the
|
||||
# built-in version if possible.
|
||||
if hasattr(unittest.TestCase, "assertListEqual"):
|
||||
unittest.TestCase.assertListEqual(self, Sorted(list1), Sorted(list2))
|
||||
else:
|
||||
self.assertEqual(Sorted(list1), Sorted(list2))
|
||||
|
||||
def assertMultiLineEqual(self, expected, actual):
|
||||
# This exists in python 2.7, but not previous versions. Use the
|
||||
# built-in version if possible.
|
||||
if hasattr(unittest.TestCase, "assertMultiLineEqual"):
|
||||
unittest.TestCase.assertMultiLineEqual(self, expected, actual)
|
||||
else:
|
||||
self.assertTrue(MultiLineEqual(expected, actual))
|
||||
|
||||
def assertRaisesWithRegexpMatch(self, exception, regexp, fn, *args, **kwargs):
|
||||
try:
|
||||
fn(*args, **kwargs)
|
||||
except exception, why:
|
||||
self.assertTrue(re.search(regexp, str(why)),
|
||||
"'%s' does not match '%s'" % (regexp, why))
|
||||
return
|
||||
self.fail(exception.__name__ + " not raised")
|
||||
|
||||
|
||||
def main():
|
||||
unittest.main()
|
|
@ -0,0 +1,535 @@
|
|||
#!/usr/bin/env python
|
||||
|
||||
# Copyright (c) 2009, Google Inc.
|
||||
# All rights reserved.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without
|
||||
# modification, are permitted provided that the following conditions are
|
||||
# met:
|
||||
#
|
||||
# * Redistributions of source code must retain the above copyright
|
||||
# notice, this list of conditions and the following disclaimer.
|
||||
# * Redistributions in binary form must reproduce the above
|
||||
# copyright notice, this list of conditions and the following disclaimer
|
||||
# in the documentation and/or other materials provided with the
|
||||
# distribution.
|
||||
# * Neither the name of Google Inc. nor the names of its
|
||||
# contributors may be used to endorse or promote products derived from
|
||||
# this software without specific prior written permission.
|
||||
#
|
||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
"""Unit tests for the XML-format help generated by the gflags.py module."""
|
||||
|
||||
__author__ = 'salcianu@google.com (Alex Salcianu)'
|
||||
|
||||
|
||||
import string
|
||||
import StringIO
|
||||
import sys
|
||||
import xml.dom.minidom
|
||||
import xml.sax.saxutils
|
||||
import gflags_googletest as googletest
|
||||
import gflags
|
||||
from flags_modules_for_testing import module_bar
|
||||
|
||||
|
||||
class _MakeXMLSafeTest(googletest.TestCase):
|
||||
|
||||
def _Check(self, s, expected_output):
|
||||
self.assertEqual(gflags._MakeXMLSafe(s), expected_output)
|
||||
|
||||
def testMakeXMLSafe(self):
|
||||
self._Check('plain text', 'plain text')
|
||||
self._Check('(x < y) && (a >= b)',
|
||||
'(x < y) && (a >= b)')
|
||||
# Some characters with ASCII code < 32 are illegal in XML 1.0 and
|
||||
# are removed by us. However, '\n', '\t', and '\r' are legal.
|
||||
self._Check('\x09\x0btext \x02 with\x0dsome \x08 good & bad chars',
|
||||
'\ttext with\rsome good & bad chars')
|
||||
|
||||
|
||||
def _ListSeparatorsInXMLFormat(separators, indent=''):
|
||||
"""Generates XML encoding of a list of list separators.
|
||||
|
||||
Args:
|
||||
separators: A list of list separators. Usually, this should be a
|
||||
string whose characters are the valid list separators, e.g., ','
|
||||
means that both comma (',') and space (' ') are valid list
|
||||
separators.
|
||||
indent: A string that is added at the beginning of each generated
|
||||
XML element.
|
||||
|
||||
Returns:
|
||||
A string.
|
||||
"""
|
||||
result = ''
|
||||
separators = list(separators)
|
||||
separators.sort()
|
||||
for sep_char in separators:
|
||||
result += ('%s<list_separator>%s</list_separator>\n' %
|
||||
(indent, repr(sep_char)))
|
||||
return result
|
||||
|
||||
|
||||
class WriteFlagHelpInXMLFormatTest(googletest.TestCase):
|
||||
"""Test the XML-format help for a single flag at a time.
|
||||
|
||||
There is one test* method for each kind of DEFINE_* declaration.
|
||||
"""
|
||||
|
||||
def setUp(self):
|
||||
# self.fv is a FlagValues object, just like gflags.FLAGS. Each
|
||||
# test registers one flag with this FlagValues.
|
||||
self.fv = gflags.FlagValues()
|
||||
|
||||
def _CheckFlagHelpInXML(self, flag_name, module_name,
|
||||
expected_output, is_key=False):
|
||||
# StringIO.StringIO is a file object that writes into a memory string.
|
||||
sio = StringIO.StringIO()
|
||||
flag_obj = self.fv[flag_name]
|
||||
flag_obj.WriteInfoInXMLFormat(sio, module_name, is_key=is_key, indent=' ')
|
||||
self.assertMultiLineEqual(sio.getvalue(), expected_output)
|
||||
sio.close()
|
||||
|
||||
def testFlagHelpInXML_Int(self):
|
||||
gflags.DEFINE_integer('index', 17, 'An integer flag', flag_values=self.fv)
|
||||
expected_output_pattern = (
|
||||
' <flag>\n'
|
||||
' <file>module.name</file>\n'
|
||||
' <name>index</name>\n'
|
||||
' <meaning>An integer flag</meaning>\n'
|
||||
' <default>17</default>\n'
|
||||
' <current>%d</current>\n'
|
||||
' <type>int</type>\n'
|
||||
' </flag>\n')
|
||||
self._CheckFlagHelpInXML('index', 'module.name',
|
||||
expected_output_pattern % 17)
|
||||
# Check that the output is correct even when the current value of
|
||||
# a flag is different from the default one.
|
||||
self.fv['index'].value = 20
|
||||
self._CheckFlagHelpInXML('index', 'module.name',
|
||||
expected_output_pattern % 20)
|
||||
|
||||
def testFlagHelpInXML_IntWithBounds(self):
|
||||
gflags.DEFINE_integer('nb_iters', 17, 'An integer flag',
|
||||
lower_bound=5, upper_bound=27,
|
||||
flag_values=self.fv)
|
||||
expected_output = (
|
||||
' <flag>\n'
|
||||
' <key>yes</key>\n'
|
||||
' <file>module.name</file>\n'
|
||||
' <name>nb_iters</name>\n'
|
||||
' <meaning>An integer flag</meaning>\n'
|
||||
' <default>17</default>\n'
|
||||
' <current>17</current>\n'
|
||||
' <type>int</type>\n'
|
||||
' <lower_bound>5</lower_bound>\n'
|
||||
' <upper_bound>27</upper_bound>\n'
|
||||
' </flag>\n')
|
||||
self._CheckFlagHelpInXML('nb_iters', 'module.name',
|
||||
expected_output, is_key=True)
|
||||
|
||||
def testFlagHelpInXML_String(self):
|
||||
gflags.DEFINE_string('file_path', '/path/to/my/dir', 'A test string flag.',
|
||||
flag_values=self.fv)
|
||||
expected_output = (
|
||||
' <flag>\n'
|
||||
' <file>simple_module</file>\n'
|
||||
' <name>file_path</name>\n'
|
||||
' <meaning>A test string flag.</meaning>\n'
|
||||
' <default>/path/to/my/dir</default>\n'
|
||||
' <current>/path/to/my/dir</current>\n'
|
||||
' <type>string</type>\n'
|
||||
' </flag>\n')
|
||||
self._CheckFlagHelpInXML('file_path', 'simple_module',
|
||||
expected_output)
|
||||
|
||||
def testFlagHelpInXML_StringWithXMLIllegalChars(self):
|
||||
gflags.DEFINE_string('file_path', '/path/to/\x08my/dir',
|
||||
'A test string flag.', flag_values=self.fv)
|
||||
# '\x08' is not a legal character in XML 1.0 documents. Our
|
||||
# current code purges such characters from the generated XML.
|
||||
expected_output = (
|
||||
' <flag>\n'
|
||||
' <file>simple_module</file>\n'
|
||||
' <name>file_path</name>\n'
|
||||
' <meaning>A test string flag.</meaning>\n'
|
||||
' <default>/path/to/my/dir</default>\n'
|
||||
' <current>/path/to/my/dir</current>\n'
|
||||
' <type>string</type>\n'
|
||||
' </flag>\n')
|
||||
self._CheckFlagHelpInXML('file_path', 'simple_module',
|
||||
expected_output)
|
||||
|
||||
def testFlagHelpInXML_Boolean(self):
|
||||
gflags.DEFINE_boolean('use_hack', False, 'Use performance hack',
|
||||
flag_values=self.fv)
|
||||
expected_output = (
|
||||
' <flag>\n'
|
||||
' <key>yes</key>\n'
|
||||
' <file>a_module</file>\n'
|
||||
' <name>use_hack</name>\n'
|
||||
' <meaning>Use performance hack</meaning>\n'
|
||||
' <default>false</default>\n'
|
||||
' <current>false</current>\n'
|
||||
' <type>bool</type>\n'
|
||||
' </flag>\n')
|
||||
self._CheckFlagHelpInXML('use_hack', 'a_module',
|
||||
expected_output, is_key=True)
|
||||
|
||||
def testFlagHelpInXML_Enum(self):
|
||||
gflags.DEFINE_enum('cc_version', 'stable', ['stable', 'experimental'],
|
||||
'Compiler version to use.', flag_values=self.fv)
|
||||
expected_output = (
|
||||
' <flag>\n'
|
||||
' <file>tool</file>\n'
|
||||
' <name>cc_version</name>\n'
|
||||
' <meaning><stable|experimental>: '
|
||||
'Compiler version to use.</meaning>\n'
|
||||
' <default>stable</default>\n'
|
||||
' <current>stable</current>\n'
|
||||
' <type>string enum</type>\n'
|
||||
' <enum_value>stable</enum_value>\n'
|
||||
' <enum_value>experimental</enum_value>\n'
|
||||
' </flag>\n')
|
||||
self._CheckFlagHelpInXML('cc_version', 'tool', expected_output)
|
||||
|
||||
def testFlagHelpInXML_CommaSeparatedList(self):
|
||||
gflags.DEFINE_list('files', 'a.cc,a.h,archive/old.zip',
|
||||
'Files to process.', flag_values=self.fv)
|
||||
expected_output = (
|
||||
' <flag>\n'
|
||||
' <file>tool</file>\n'
|
||||
' <name>files</name>\n'
|
||||
' <meaning>Files to process.</meaning>\n'
|
||||
' <default>a.cc,a.h,archive/old.zip</default>\n'
|
||||
' <current>[\'a.cc\', \'a.h\', \'archive/old.zip\']</current>\n'
|
||||
' <type>comma separated list of strings</type>\n'
|
||||
' <list_separator>\',\'</list_separator>\n'
|
||||
' </flag>\n')
|
||||
self._CheckFlagHelpInXML('files', 'tool', expected_output)
|
||||
|
||||
def testListAsDefaultArgument_CommaSeparatedList(self):
|
||||
gflags.DEFINE_list('allow_users', ['alice', 'bob'],
|
||||
'Users with access.', flag_values=self.fv)
|
||||
expected_output = (
|
||||
' <flag>\n'
|
||||
' <file>tool</file>\n'
|
||||
' <name>allow_users</name>\n'
|
||||
' <meaning>Users with access.</meaning>\n'
|
||||
' <default>alice,bob</default>\n'
|
||||
' <current>[\'alice\', \'bob\']</current>\n'
|
||||
' <type>comma separated list of strings</type>\n'
|
||||
' <list_separator>\',\'</list_separator>\n'
|
||||
' </flag>\n')
|
||||
self._CheckFlagHelpInXML('allow_users', 'tool', expected_output)
|
||||
|
||||
def testFlagHelpInXML_SpaceSeparatedList(self):
|
||||
gflags.DEFINE_spaceseplist('dirs', 'src libs bin',
|
||||
'Directories to search.', flag_values=self.fv)
|
||||
expected_output = (
|
||||
' <flag>\n'
|
||||
' <file>tool</file>\n'
|
||||
' <name>dirs</name>\n'
|
||||
' <meaning>Directories to search.</meaning>\n'
|
||||
' <default>src libs bin</default>\n'
|
||||
' <current>[\'src\', \'libs\', \'bin\']</current>\n'
|
||||
' <type>whitespace separated list of strings</type>\n'
|
||||
'LIST_SEPARATORS'
|
||||
' </flag>\n').replace('LIST_SEPARATORS',
|
||||
_ListSeparatorsInXMLFormat(string.whitespace,
|
||||
indent=' '))
|
||||
self._CheckFlagHelpInXML('dirs', 'tool', expected_output)
|
||||
|
||||
def testFlagHelpInXML_MultiString(self):
|
||||
gflags.DEFINE_multistring('to_delete', ['a.cc', 'b.h'],
|
||||
'Files to delete', flag_values=self.fv)
|
||||
expected_output = (
|
||||
' <flag>\n'
|
||||
' <file>tool</file>\n'
|
||||
' <name>to_delete</name>\n'
|
||||
' <meaning>Files to delete;\n '
|
||||
'repeat this option to specify a list of values</meaning>\n'
|
||||
' <default>[\'a.cc\', \'b.h\']</default>\n'
|
||||
' <current>[\'a.cc\', \'b.h\']</current>\n'
|
||||
' <type>multi string</type>\n'
|
||||
' </flag>\n')
|
||||
self._CheckFlagHelpInXML('to_delete', 'tool', expected_output)
|
||||
|
||||
def testFlagHelpInXML_MultiInt(self):
|
||||
gflags.DEFINE_multi_int('cols', [5, 7, 23],
|
||||
'Columns to select', flag_values=self.fv)
|
||||
expected_output = (
|
||||
' <flag>\n'
|
||||
' <file>tool</file>\n'
|
||||
' <name>cols</name>\n'
|
||||
' <meaning>Columns to select;\n '
|
||||
'repeat this option to specify a list of values</meaning>\n'
|
||||
' <default>[5, 7, 23]</default>\n'
|
||||
' <current>[5, 7, 23]</current>\n'
|
||||
' <type>multi int</type>\n'
|
||||
' </flag>\n')
|
||||
self._CheckFlagHelpInXML('cols', 'tool', expected_output)
|
||||
|
||||
|
||||
# The next EXPECTED_HELP_XML_* constants are parts of a template for
|
||||
# the expected XML output from WriteHelpInXMLFormatTest below. When
|
||||
# we assemble these parts into a single big string, we'll take into
|
||||
# account the ordering between the name of the main module and the
|
||||
# name of module_bar. Next, we'll fill in the docstring for this
|
||||
# module (%(usage_doc)s), the name of the main module
|
||||
# (%(main_module_name)s) and the name of the module module_bar
|
||||
# (%(module_bar_name)s). See WriteHelpInXMLFormatTest below.
|
||||
#
|
||||
# NOTE: given the current implementation of _GetMainModule(), we
|
||||
# already know the ordering between the main module and module_bar.
|
||||
# However, there is no guarantee that _GetMainModule will never be
|
||||
# changed in the future (especially since it's far from perfect).
|
||||
EXPECTED_HELP_XML_START = """\
|
||||
<?xml version="1.0"?>
|
||||
<AllFlags>
|
||||
<program>gflags_helpxml_test.py</program>
|
||||
<usage>%(usage_doc)s</usage>
|
||||
"""
|
||||
|
||||
EXPECTED_HELP_XML_FOR_FLAGS_FROM_MAIN_MODULE = """\
|
||||
<flag>
|
||||
<key>yes</key>
|
||||
<file>%(main_module_name)s</file>
|
||||
<name>allow_users</name>
|
||||
<meaning>Users with access.</meaning>
|
||||
<default>alice,bob</default>
|
||||
<current>['alice', 'bob']</current>
|
||||
<type>comma separated list of strings</type>
|
||||
<list_separator>','</list_separator>
|
||||
</flag>
|
||||
<flag>
|
||||
<key>yes</key>
|
||||
<file>%(main_module_name)s</file>
|
||||
<name>cc_version</name>
|
||||
<meaning><stable|experimental>: Compiler version to use.</meaning>
|
||||
<default>stable</default>
|
||||
<current>stable</current>
|
||||
<type>string enum</type>
|
||||
<enum_value>stable</enum_value>
|
||||
<enum_value>experimental</enum_value>
|
||||
</flag>
|
||||
<flag>
|
||||
<key>yes</key>
|
||||
<file>%(main_module_name)s</file>
|
||||
<name>cols</name>
|
||||
<meaning>Columns to select;
|
||||
repeat this option to specify a list of values</meaning>
|
||||
<default>[5, 7, 23]</default>
|
||||
<current>[5, 7, 23]</current>
|
||||
<type>multi int</type>
|
||||
</flag>
|
||||
<flag>
|
||||
<key>yes</key>
|
||||
<file>%(main_module_name)s</file>
|
||||
<name>dirs</name>
|
||||
<meaning>Directories to create.</meaning>
|
||||
<default>src libs bins</default>
|
||||
<current>['src', 'libs', 'bins']</current>
|
||||
<type>whitespace separated list of strings</type>
|
||||
%(whitespace_separators)s </flag>
|
||||
<flag>
|
||||
<key>yes</key>
|
||||
<file>%(main_module_name)s</file>
|
||||
<name>file_path</name>
|
||||
<meaning>A test string flag.</meaning>
|
||||
<default>/path/to/my/dir</default>
|
||||
<current>/path/to/my/dir</current>
|
||||
<type>string</type>
|
||||
</flag>
|
||||
<flag>
|
||||
<key>yes</key>
|
||||
<file>%(main_module_name)s</file>
|
||||
<name>files</name>
|
||||
<meaning>Files to process.</meaning>
|
||||
<default>a.cc,a.h,archive/old.zip</default>
|
||||
<current>['a.cc', 'a.h', 'archive/old.zip']</current>
|
||||
<type>comma separated list of strings</type>
|
||||
<list_separator>\',\'</list_separator>
|
||||
</flag>
|
||||
<flag>
|
||||
<key>yes</key>
|
||||
<file>%(main_module_name)s</file>
|
||||
<name>index</name>
|
||||
<meaning>An integer flag</meaning>
|
||||
<default>17</default>
|
||||
<current>17</current>
|
||||
<type>int</type>
|
||||
</flag>
|
||||
<flag>
|
||||
<key>yes</key>
|
||||
<file>%(main_module_name)s</file>
|
||||
<name>nb_iters</name>
|
||||
<meaning>An integer flag</meaning>
|
||||
<default>17</default>
|
||||
<current>17</current>
|
||||
<type>int</type>
|
||||
<lower_bound>5</lower_bound>
|
||||
<upper_bound>27</upper_bound>
|
||||
</flag>
|
||||
<flag>
|
||||
<key>yes</key>
|
||||
<file>%(main_module_name)s</file>
|
||||
<name>to_delete</name>
|
||||
<meaning>Files to delete;
|
||||
repeat this option to specify a list of values</meaning>
|
||||
<default>['a.cc', 'b.h']</default>
|
||||
<current>['a.cc', 'b.h']</current>
|
||||
<type>multi string</type>
|
||||
</flag>
|
||||
<flag>
|
||||
<key>yes</key>
|
||||
<file>%(main_module_name)s</file>
|
||||
<name>use_hack</name>
|
||||
<meaning>Use performance hack</meaning>
|
||||
<default>false</default>
|
||||
<current>false</current>
|
||||
<type>bool</type>
|
||||
</flag>
|
||||
"""
|
||||
|
||||
EXPECTED_HELP_XML_FOR_FLAGS_FROM_MODULE_BAR = """\
|
||||
<flag>
|
||||
<file>%(module_bar_name)s</file>
|
||||
<name>tmod_bar_t</name>
|
||||
<meaning>Sample int flag.</meaning>
|
||||
<default>4</default>
|
||||
<current>4</current>
|
||||
<type>int</type>
|
||||
</flag>
|
||||
<flag>
|
||||
<key>yes</key>
|
||||
<file>%(module_bar_name)s</file>
|
||||
<name>tmod_bar_u</name>
|
||||
<meaning>Sample int flag.</meaning>
|
||||
<default>5</default>
|
||||
<current>5</current>
|
||||
<type>int</type>
|
||||
</flag>
|
||||
<flag>
|
||||
<file>%(module_bar_name)s</file>
|
||||
<name>tmod_bar_v</name>
|
||||
<meaning>Sample int flag.</meaning>
|
||||
<default>6</default>
|
||||
<current>6</current>
|
||||
<type>int</type>
|
||||
</flag>
|
||||
<flag>
|
||||
<file>%(module_bar_name)s</file>
|
||||
<name>tmod_bar_x</name>
|
||||
<meaning>Boolean flag.</meaning>
|
||||
<default>true</default>
|
||||
<current>true</current>
|
||||
<type>bool</type>
|
||||
</flag>
|
||||
<flag>
|
||||
<file>%(module_bar_name)s</file>
|
||||
<name>tmod_bar_y</name>
|
||||
<meaning>String flag.</meaning>
|
||||
<default>default</default>
|
||||
<current>default</current>
|
||||
<type>string</type>
|
||||
</flag>
|
||||
<flag>
|
||||
<key>yes</key>
|
||||
<file>%(module_bar_name)s</file>
|
||||
<name>tmod_bar_z</name>
|
||||
<meaning>Another boolean flag from module bar.</meaning>
|
||||
<default>false</default>
|
||||
<current>false</current>
|
||||
<type>bool</type>
|
||||
</flag>
|
||||
"""
|
||||
|
||||
EXPECTED_HELP_XML_END = """\
|
||||
</AllFlags>
|
||||
"""
|
||||
|
||||
|
||||
class WriteHelpInXMLFormatTest(googletest.TestCase):
|
||||
"""Big test of FlagValues.WriteHelpInXMLFormat, with several flags."""
|
||||
|
||||
def testWriteHelpInXMLFormat(self):
|
||||
fv = gflags.FlagValues()
|
||||
# Since these flags are defined by the top module, they are all key.
|
||||
gflags.DEFINE_integer('index', 17, 'An integer flag', flag_values=fv)
|
||||
gflags.DEFINE_integer('nb_iters', 17, 'An integer flag',
|
||||
lower_bound=5, upper_bound=27, flag_values=fv)
|
||||
gflags.DEFINE_string('file_path', '/path/to/my/dir', 'A test string flag.',
|
||||
flag_values=fv)
|
||||
gflags.DEFINE_boolean('use_hack', False, 'Use performance hack',
|
||||
flag_values=fv)
|
||||
gflags.DEFINE_enum('cc_version', 'stable', ['stable', 'experimental'],
|
||||
'Compiler version to use.', flag_values=fv)
|
||||
gflags.DEFINE_list('files', 'a.cc,a.h,archive/old.zip',
|
||||
'Files to process.', flag_values=fv)
|
||||
gflags.DEFINE_list('allow_users', ['alice', 'bob'],
|
||||
'Users with access.', flag_values=fv)
|
||||
gflags.DEFINE_spaceseplist('dirs', 'src libs bins',
|
||||
'Directories to create.', flag_values=fv)
|
||||
gflags.DEFINE_multistring('to_delete', ['a.cc', 'b.h'],
|
||||
'Files to delete', flag_values=fv)
|
||||
gflags.DEFINE_multi_int('cols', [5, 7, 23],
|
||||
'Columns to select', flag_values=fv)
|
||||
# Define a few flags in a different module.
|
||||
module_bar.DefineFlags(flag_values=fv)
|
||||
# And declare only a few of them to be key. This way, we have
|
||||
# different kinds of flags, defined in different modules, and not
|
||||
# all of them are key flags.
|
||||
gflags.DECLARE_key_flag('tmod_bar_z', flag_values=fv)
|
||||
gflags.DECLARE_key_flag('tmod_bar_u', flag_values=fv)
|
||||
|
||||
# Generate flag help in XML format in the StringIO sio.
|
||||
sio = StringIO.StringIO()
|
||||
fv.WriteHelpInXMLFormat(sio)
|
||||
|
||||
# Check that we got the expected result.
|
||||
expected_output_template = EXPECTED_HELP_XML_START
|
||||
main_module_name = gflags._GetMainModule()
|
||||
module_bar_name = module_bar.__name__
|
||||
|
||||
if main_module_name < module_bar_name:
|
||||
expected_output_template += EXPECTED_HELP_XML_FOR_FLAGS_FROM_MAIN_MODULE
|
||||
expected_output_template += EXPECTED_HELP_XML_FOR_FLAGS_FROM_MODULE_BAR
|
||||
else:
|
||||
expected_output_template += EXPECTED_HELP_XML_FOR_FLAGS_FROM_MODULE_BAR
|
||||
expected_output_template += EXPECTED_HELP_XML_FOR_FLAGS_FROM_MAIN_MODULE
|
||||
|
||||
expected_output_template += EXPECTED_HELP_XML_END
|
||||
|
||||
# XML representation of the whitespace list separators.
|
||||
whitespace_separators = _ListSeparatorsInXMLFormat(string.whitespace,
|
||||
indent=' ')
|
||||
expected_output = (
|
||||
expected_output_template %
|
||||
{'usage_doc': sys.modules['__main__'].__doc__,
|
||||
'main_module_name': main_module_name,
|
||||
'module_bar_name': module_bar_name,
|
||||
'whitespace_separators': whitespace_separators})
|
||||
|
||||
actual_output = sio.getvalue()
|
||||
self.assertMultiLineEqual(actual_output, expected_output)
|
||||
|
||||
# Also check that our result is valid XML. minidom.parseString
|
||||
# throws an xml.parsers.expat.ExpatError in case of an error.
|
||||
xml.dom.minidom.parseString(actual_output)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
googletest.main()
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -0,0 +1,220 @@
|
|||
#!/usr/bin/env python
|
||||
|
||||
# Copyright (c) 2010, Google Inc.
|
||||
# All rights reserved.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without
|
||||
# modification, are permitted provided that the following conditions are
|
||||
# met:
|
||||
#
|
||||
# * Redistributions of source code must retain the above copyright
|
||||
# notice, this list of conditions and the following disclaimer.
|
||||
# * Redistributions in binary form must reproduce the above
|
||||
# copyright notice, this list of conditions and the following disclaimer
|
||||
# in the documentation and/or other materials provided with the
|
||||
# distribution.
|
||||
# * Neither the name of Google Inc. nor the names of its
|
||||
# contributors may be used to endorse or promote products derived from
|
||||
# this software without specific prior written permission.
|
||||
#
|
||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
"""Testing that flags validators framework does work.
|
||||
|
||||
This file tests that each flag validator called when it should be, and that
|
||||
failed validator will throw an exception, etc.
|
||||
"""
|
||||
|
||||
__author__ = 'olexiy@google.com (Olexiy Oryeshko)'
|
||||
|
||||
import gflags_googletest as googletest
|
||||
import gflags
|
||||
import gflags_validators
|
||||
|
||||
|
||||
class SimpleValidatorTest(googletest.TestCase):
|
||||
"""Testing gflags.RegisterValidator() method."""
|
||||
|
||||
def setUp(self):
|
||||
super(SimpleValidatorTest, self).setUp()
|
||||
self.flag_values = gflags.FlagValues()
|
||||
self.call_args = []
|
||||
|
||||
def testSuccess(self):
|
||||
def Checker(x):
|
||||
self.call_args.append(x)
|
||||
return True
|
||||
gflags.DEFINE_integer('test_flag', None, 'Usual integer flag',
|
||||
flag_values=self.flag_values)
|
||||
gflags.RegisterValidator('test_flag',
|
||||
Checker,
|
||||
message='Errors happen',
|
||||
flag_values=self.flag_values)
|
||||
|
||||
argv = ('./program')
|
||||
self.flag_values(argv)
|
||||
self.assertEquals(None, self.flag_values.test_flag)
|
||||
self.flag_values.test_flag = 2
|
||||
self.assertEquals(2, self.flag_values.test_flag)
|
||||
self.assertEquals([None, 2], self.call_args)
|
||||
|
||||
def testDefaultValueNotUsedSuccess(self):
|
||||
def Checker(x):
|
||||
self.call_args.append(x)
|
||||
return True
|
||||
gflags.DEFINE_integer('test_flag', None, 'Usual integer flag',
|
||||
flag_values=self.flag_values)
|
||||
gflags.RegisterValidator('test_flag',
|
||||
Checker,
|
||||
message='Errors happen',
|
||||
flag_values=self.flag_values)
|
||||
|
||||
argv = ('./program', '--test_flag=1')
|
||||
self.flag_values(argv)
|
||||
self.assertEquals(1, self.flag_values.test_flag)
|
||||
self.assertEquals([1], self.call_args)
|
||||
|
||||
def testValidatorNotCalledWhenOtherFlagIsChanged(self):
|
||||
def Checker(x):
|
||||
self.call_args.append(x)
|
||||
return True
|
||||
gflags.DEFINE_integer('test_flag', 1, 'Usual integer flag',
|
||||
flag_values=self.flag_values)
|
||||
gflags.DEFINE_integer('other_flag', 2, 'Other integer flag',
|
||||
flag_values=self.flag_values)
|
||||
gflags.RegisterValidator('test_flag',
|
||||
Checker,
|
||||
message='Errors happen',
|
||||
flag_values=self.flag_values)
|
||||
|
||||
argv = ('./program')
|
||||
self.flag_values(argv)
|
||||
self.assertEquals(1, self.flag_values.test_flag)
|
||||
self.flag_values.other_flag = 3
|
||||
self.assertEquals([1], self.call_args)
|
||||
|
||||
def testExceptionRaisedIfCheckerFails(self):
|
||||
def Checker(x):
|
||||
self.call_args.append(x)
|
||||
return x == 1
|
||||
gflags.DEFINE_integer('test_flag', None, 'Usual integer flag',
|
||||
flag_values=self.flag_values)
|
||||
gflags.RegisterValidator('test_flag',
|
||||
Checker,
|
||||
message='Errors happen',
|
||||
flag_values=self.flag_values)
|
||||
|
||||
argv = ('./program', '--test_flag=1')
|
||||
self.flag_values(argv)
|
||||
try:
|
||||
self.flag_values.test_flag = 2
|
||||
raise AssertionError('gflags.IllegalFlagValue expected')
|
||||
except gflags.IllegalFlagValue, e:
|
||||
self.assertEquals('flag --test_flag=2: Errors happen', str(e))
|
||||
self.assertEquals([1, 2], self.call_args)
|
||||
|
||||
def testExceptionRaisedIfCheckerRaisesException(self):
|
||||
def Checker(x):
|
||||
self.call_args.append(x)
|
||||
if x == 1:
|
||||
return True
|
||||
raise gflags_validators.Error('Specific message')
|
||||
gflags.DEFINE_integer('test_flag', None, 'Usual integer flag',
|
||||
flag_values=self.flag_values)
|
||||
gflags.RegisterValidator('test_flag',
|
||||
Checker,
|
||||
message='Errors happen',
|
||||
flag_values=self.flag_values)
|
||||
|
||||
argv = ('./program', '--test_flag=1')
|
||||
self.flag_values(argv)
|
||||
try:
|
||||
self.flag_values.test_flag = 2
|
||||
raise AssertionError('gflags.IllegalFlagValue expected')
|
||||
except gflags.IllegalFlagValue, e:
|
||||
self.assertEquals('flag --test_flag=2: Specific message', str(e))
|
||||
self.assertEquals([1, 2], self.call_args)
|
||||
|
||||
def testErrorMessageWhenCheckerReturnsFalseOnStart(self):
|
||||
def Checker(x):
|
||||
self.call_args.append(x)
|
||||
return False
|
||||
gflags.DEFINE_integer('test_flag', None, 'Usual integer flag',
|
||||
flag_values=self.flag_values)
|
||||
gflags.RegisterValidator('test_flag',
|
||||
Checker,
|
||||
message='Errors happen',
|
||||
flag_values=self.flag_values)
|
||||
|
||||
argv = ('./program', '--test_flag=1')
|
||||
try:
|
||||
self.flag_values(argv)
|
||||
raise AssertionError('gflags.IllegalFlagValue expected')
|
||||
except gflags.IllegalFlagValue, e:
|
||||
self.assertEquals('flag --test_flag=1: Errors happen', str(e))
|
||||
self.assertEquals([1], self.call_args)
|
||||
|
||||
def testErrorMessageWhenCheckerRaisesExceptionOnStart(self):
|
||||
def Checker(x):
|
||||
self.call_args.append(x)
|
||||
raise gflags_validators.Error('Specific message')
|
||||
gflags.DEFINE_integer('test_flag', None, 'Usual integer flag',
|
||||
flag_values=self.flag_values)
|
||||
gflags.RegisterValidator('test_flag',
|
||||
Checker,
|
||||
message='Errors happen',
|
||||
flag_values=self.flag_values)
|
||||
|
||||
argv = ('./program', '--test_flag=1')
|
||||
try:
|
||||
self.flag_values(argv)
|
||||
raise AssertionError('IllegalFlagValue expected')
|
||||
except gflags.IllegalFlagValue, e:
|
||||
self.assertEquals('flag --test_flag=1: Specific message', str(e))
|
||||
self.assertEquals([1], self.call_args)
|
||||
|
||||
def testValidatorsCheckedInOrder(self):
|
||||
|
||||
def Required(x):
|
||||
self.calls.append('Required')
|
||||
return x is not None
|
||||
|
||||
def Even(x):
|
||||
self.calls.append('Even')
|
||||
return x % 2 == 0
|
||||
|
||||
self.calls = []
|
||||
self._DefineFlagAndValidators(Required, Even)
|
||||
self.assertEquals(['Required', 'Even'], self.calls)
|
||||
|
||||
self.calls = []
|
||||
self._DefineFlagAndValidators(Even, Required)
|
||||
self.assertEquals(['Even', 'Required'], self.calls)
|
||||
|
||||
def _DefineFlagAndValidators(self, first_validator, second_validator):
|
||||
local_flags = gflags.FlagValues()
|
||||
gflags.DEFINE_integer('test_flag', 2, 'test flag', flag_values=local_flags)
|
||||
gflags.RegisterValidator('test_flag',
|
||||
first_validator,
|
||||
message='',
|
||||
flag_values=local_flags)
|
||||
gflags.RegisterValidator('test_flag',
|
||||
second_validator,
|
||||
message='',
|
||||
flag_values=local_flags)
|
||||
argv = ('./program')
|
||||
local_flags(argv)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
googletest.main()
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -0,0 +1,36 @@
|
|||
--- __init__.py
|
||||
+++ __init__.py
|
||||
@@ -1058,15 +1062,26 @@
|
||||
|
||||
# Use a different connection object for Google App Engine
|
||||
try:
|
||||
- from google.appengine.api import apiproxy_stub_map
|
||||
- if apiproxy_stub_map.apiproxy.GetStub('urlfetch') is None:
|
||||
- raise ImportError # Bail out; we're not actually running on App Engine.
|
||||
- from google.appengine.api.urlfetch import fetch
|
||||
- from google.appengine.api.urlfetch import InvalidURLError
|
||||
+ try:
|
||||
+ from google.appengine.api import apiproxy_stub_map
|
||||
+ if apiproxy_stub_map.apiproxy.GetStub('urlfetch') is None:
|
||||
+ raise ImportError # Bail out; we're not actually running on App Engine.
|
||||
+ from google.appengine.api.urlfetch import fetch
|
||||
+ from google.appengine.api.urlfetch import InvalidURLError
|
||||
+ except ImportError:
|
||||
+ from google3.apphosting.api import apiproxy_stub_map
|
||||
+ if apiproxy_stub_map.apiproxy.GetStub('urlfetch') is None:
|
||||
+ raise ImportError # Bail out; we're not actually running on App Engine.
|
||||
+ from google3.apphosting.api.urlfetch import fetch
|
||||
+ from google3.apphosting.api.urlfetch import InvalidURLError
|
||||
|
||||
def _new_fixed_fetch(validate_certificate):
|
||||
- def fixed_fetch(url, payload=None, method="GET", headers={}, allow_truncated=False, follow_redirects=True, deadline=5):
|
||||
- return fetch(url, payload=payload, method=method, headers=header, allow_truncated=allow_truncated, follow_redirects=follow_redirects, deadline=deadline, validate_certificate=validate_certificate)
|
||||
+ def fixed_fetch(url, payload=None, method="GET", headers={},
|
||||
+ allow_truncated=False, follow_redirects=True, deadline=5):
|
||||
+ return fetch(url, payload=payload, method=method, headers=header,
|
||||
+ allow_truncated=allow_truncated,
|
||||
+ follow_redirects=follow_redirects, deadline=deadline,
|
||||
+ validate_certificate=validate_certificate)
|
||||
return fixed_fetch
|
||||
|
||||
class AppEngineHttpConnection(httplib.HTTPConnection):
|
Двоичный файл не отображается.
|
@ -0,0 +1,739 @@
|
|||
# Certifcate Authority certificates for validating SSL connections.
|
||||
#
|
||||
# This file contains PEM format certificates generated from
|
||||
# http://mxr.mozilla.org/seamonkey/source/security/nss/lib/ckfw/builtins/certdata.txt
|
||||
#
|
||||
# ***** BEGIN LICENSE BLOCK *****
|
||||
# Version: MPL 1.1/GPL 2.0/LGPL 2.1
|
||||
#
|
||||
# The contents of this file are subject to the Mozilla Public License Version
|
||||
# 1.1 (the "License"); you may not use this file except in compliance with
|
||||
# the License. You may obtain a copy of the License at
|
||||
# http://www.mozilla.org/MPL/
|
||||
#
|
||||
# Software distributed under the License is distributed on an "AS IS" basis,
|
||||
# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
|
||||
# for the specific language governing rights and limitations under the
|
||||
# License.
|
||||
#
|
||||
# The Original Code is the Netscape security libraries.
|
||||
#
|
||||
# The Initial Developer of the Original Code is
|
||||
# Netscape Communications Corporation.
|
||||
# Portions created by the Initial Developer are Copyright (C) 1994-2000
|
||||
# the Initial Developer. All Rights Reserved.
|
||||
#
|
||||
# Contributor(s):
|
||||
#
|
||||
# Alternatively, the contents of this file may be used under the terms of
|
||||
# either the GNU General Public License Version 2 or later (the "GPL"), or
|
||||
# the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
|
||||
# in which case the provisions of the GPL or the LGPL are applicable instead
|
||||
# of those above. If you wish to allow use of your version of this file only
|
||||
# under the terms of either the GPL or the LGPL, and not to allow others to
|
||||
# use your version of this file under the terms of the MPL, indicate your
|
||||
# decision by deleting the provisions above and replace them with the notice
|
||||
# and other provisions required by the GPL or the LGPL. If you do not delete
|
||||
# the provisions above, a recipient may use your version of this file under
|
||||
# the terms of any one of the MPL, the GPL or the LGPL.
|
||||
#
|
||||
# ***** END LICENSE BLOCK *****
|
||||
|
||||
Verisign/RSA Secure Server CA
|
||||
=============================
|
||||
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIICNDCCAaECEAKtZn5ORf5eV288mBle3cAwDQYJKoZIhvcNAQECBQAwXzELMAkG
|
||||
A1UEBhMCVVMxIDAeBgNVBAoTF1JTQSBEYXRhIFNlY3VyaXR5LCBJbmMuMS4wLAYD
|
||||
VQQLEyVTZWN1cmUgU2VydmVyIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTk0
|
||||
MTEwOTAwMDAwMFoXDTEwMDEwNzIzNTk1OVowXzELMAkGA1UEBhMCVVMxIDAeBgNV
|
||||
BAoTF1JTQSBEYXRhIFNlY3VyaXR5LCBJbmMuMS4wLAYDVQQLEyVTZWN1cmUgU2Vy
|
||||
dmVyIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIGbMA0GCSqGSIb3DQEBAQUAA4GJ
|
||||
ADCBhQJ+AJLOesGugz5aqomDV6wlAXYMra6OLDfO6zV4ZFQD5YRAUcm/jwjiioII
|
||||
0haGN1XpsSECrXZogZoFokvJSyVmIlZsiAeP94FZbYQHZXATcXY+m3dM41CJVphI
|
||||
uR2nKRoTLkoRWZweFdVJVCxzOmmCsZc5nG1wZ0jl3S3WyB57AgMBAAEwDQYJKoZI
|
||||
hvcNAQECBQADfgBl3X7hsuyw4jrg7HFGmhkRuNPHoLQDQCYCPgmc4RKz0Vr2N6W3
|
||||
YQO2WxZpO8ZECAyIUwxrl0nHPjXcbLm7qt9cuzovk2C2qUtN8iD3zV9/ZHuO3ABc
|
||||
1/p3yjkWWW8O6tO1g39NTUJWdrTJXwT4OPjr0l91X817/OWOgHz8UA==
|
||||
-----END CERTIFICATE-----
|
||||
|
||||
Thawte Personal Basic CA
|
||||
========================
|
||||
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIDITCCAoqgAwIBAgIBADANBgkqhkiG9w0BAQQFADCByzELMAkGA1UEBhMCWkEx
|
||||
FTATBgNVBAgTDFdlc3Rlcm4gQ2FwZTESMBAGA1UEBxMJQ2FwZSBUb3duMRowGAYD
|
||||
VQQKExFUaGF3dGUgQ29uc3VsdGluZzEoMCYGA1UECxMfQ2VydGlmaWNhdGlvbiBT
|
||||
ZXJ2aWNlcyBEaXZpc2lvbjEhMB8GA1UEAxMYVGhhd3RlIFBlcnNvbmFsIEJhc2lj
|
||||
IENBMSgwJgYJKoZIhvcNAQkBFhlwZXJzb25hbC1iYXNpY0B0aGF3dGUuY29tMB4X
|
||||
DTk2MDEwMTAwMDAwMFoXDTIwMTIzMTIzNTk1OVowgcsxCzAJBgNVBAYTAlpBMRUw
|
||||
EwYDVQQIEwxXZXN0ZXJuIENhcGUxEjAQBgNVBAcTCUNhcGUgVG93bjEaMBgGA1UE
|
||||
ChMRVGhhd3RlIENvbnN1bHRpbmcxKDAmBgNVBAsTH0NlcnRpZmljYXRpb24gU2Vy
|
||||
dmljZXMgRGl2aXNpb24xITAfBgNVBAMTGFRoYXd0ZSBQZXJzb25hbCBCYXNpYyBD
|
||||
QTEoMCYGCSqGSIb3DQEJARYZcGVyc29uYWwtYmFzaWNAdGhhd3RlLmNvbTCBnzAN
|
||||
BgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAvLyTU23AUE+CFeZIlDWmWr5vQvoPR+53
|
||||
dXLdjUmbllegeNTKP1GzaQuRdhciB5dqxFGTS+CN7zeVoQxN2jSQHReJl+A1OFdK
|
||||
wPQIcOk8RHtQfmGakOMj04gRRif1CwcOu93RfyAKiLlWCy4cgNrx454p7xS9CkT7
|
||||
G1sY0b8jkyECAwEAAaMTMBEwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQQF
|
||||
AAOBgQAt4plrsD16iddZopQBHyvdEktTwq1/qqcAXJFAVyVKOKqEcLnZgA+le1z7
|
||||
c8a914phXAPjLSeoF+CEhULcXpvGt7Jtu3Sv5D/Lp7ew4F2+eIMllNLbgQ95B21P
|
||||
9DkVWlIBe94y1k049hJcBlDfBVu9FEuh3ym6O0GN92NWod8isQ==
|
||||
-----END CERTIFICATE-----
|
||||
|
||||
Thawte Personal Premium CA
|
||||
==========================
|
||||
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIDKTCCApKgAwIBAgIBADANBgkqhkiG9w0BAQQFADCBzzELMAkGA1UEBhMCWkEx
|
||||
FTATBgNVBAgTDFdlc3Rlcm4gQ2FwZTESMBAGA1UEBxMJQ2FwZSBUb3duMRowGAYD
|
||||
VQQKExFUaGF3dGUgQ29uc3VsdGluZzEoMCYGA1UECxMfQ2VydGlmaWNhdGlvbiBT
|
||||
ZXJ2aWNlcyBEaXZpc2lvbjEjMCEGA1UEAxMaVGhhd3RlIFBlcnNvbmFsIFByZW1p
|
||||
dW0gQ0ExKjAoBgkqhkiG9w0BCQEWG3BlcnNvbmFsLXByZW1pdW1AdGhhd3RlLmNv
|
||||
bTAeFw05NjAxMDEwMDAwMDBaFw0yMDEyMzEyMzU5NTlaMIHPMQswCQYDVQQGEwJa
|
||||
QTEVMBMGA1UECBMMV2VzdGVybiBDYXBlMRIwEAYDVQQHEwlDYXBlIFRvd24xGjAY
|
||||
BgNVBAoTEVRoYXd0ZSBDb25zdWx0aW5nMSgwJgYDVQQLEx9DZXJ0aWZpY2F0aW9u
|
||||
IFNlcnZpY2VzIERpdmlzaW9uMSMwIQYDVQQDExpUaGF3dGUgUGVyc29uYWwgUHJl
|
||||
bWl1bSBDQTEqMCgGCSqGSIb3DQEJARYbcGVyc29uYWwtcHJlbWl1bUB0aGF3dGUu
|
||||
Y29tMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDJZtn4B0TPuYwu8KHvE0Vs
|
||||
Bd/eJxZRNkERbGw77f4QfRKe5ZtCmv5gMcNmt3M6SK5O0DI3lIi1DbbZ8/JE2dWI
|
||||
Et12TfIa/G8jHnrx2JhFTgcQ7xZC0EN1bUre4qrJMf8fAHB8Zs8QJQi6+u4A6UYD
|
||||
ZicRFTuqW/KY3TZCstqIdQIDAQABoxMwETAPBgNVHRMBAf8EBTADAQH/MA0GCSqG
|
||||
SIb3DQEBBAUAA4GBAGk2ifc0KjNyL2071CKyuG+axTZmDhs8obF1Wub9NdP4qPIH
|
||||
b4Vnjt4rueIXsDqg8A6iAJrf8xQVbrvIhVqYgPn/vnQdPfP+MCXRNzRn+qVxeTBh
|
||||
KXLA4CxM+1bkOqhv5TJZUtt1KFBZDPgLGeSs2a+WjS9Q2wfD6h+rM+D1KzGJ
|
||||
-----END CERTIFICATE-----
|
||||
|
||||
Thawte Personal Freemail CA
|
||||
===========================
|
||||
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIDLTCCApagAwIBAgIBADANBgkqhkiG9w0BAQQFADCB0TELMAkGA1UEBhMCWkEx
|
||||
FTATBgNVBAgTDFdlc3Rlcm4gQ2FwZTESMBAGA1UEBxMJQ2FwZSBUb3duMRowGAYD
|
||||
VQQKExFUaGF3dGUgQ29uc3VsdGluZzEoMCYGA1UECxMfQ2VydGlmaWNhdGlvbiBT
|
||||
ZXJ2aWNlcyBEaXZpc2lvbjEkMCIGA1UEAxMbVGhhd3RlIFBlcnNvbmFsIEZyZWVt
|
||||
YWlsIENBMSswKQYJKoZIhvcNAQkBFhxwZXJzb25hbC1mcmVlbWFpbEB0aGF3dGUu
|
||||
Y29tMB4XDTk2MDEwMTAwMDAwMFoXDTIwMTIzMTIzNTk1OVowgdExCzAJBgNVBAYT
|
||||
AlpBMRUwEwYDVQQIEwxXZXN0ZXJuIENhcGUxEjAQBgNVBAcTCUNhcGUgVG93bjEa
|
||||
MBgGA1UEChMRVGhhd3RlIENvbnN1bHRpbmcxKDAmBgNVBAsTH0NlcnRpZmljYXRp
|
||||
b24gU2VydmljZXMgRGl2aXNpb24xJDAiBgNVBAMTG1RoYXd0ZSBQZXJzb25hbCBG
|
||||
cmVlbWFpbCBDQTErMCkGCSqGSIb3DQEJARYccGVyc29uYWwtZnJlZW1haWxAdGhh
|
||||
d3RlLmNvbTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA1GnX1LCUZFtx6UfY
|
||||
DFG26nKRsIRefS0Nj3sS34UldSh0OkIsYyeflXtL734Zhx2G6qPduc6WZBrCFG5E
|
||||
rHzmj+hND3EfQDimAKOHePb5lIZererAXnbr2RSjXW56fAylS1V/Bhkpf56aJtVq
|
||||
uzgkCGqYx7Hao5iR/Xnb5VrEHLkCAwEAAaMTMBEwDwYDVR0TAQH/BAUwAwEB/zAN
|
||||
BgkqhkiG9w0BAQQFAAOBgQDH7JJ+Tvj1lqVnYiqk8E0RYNBvjWBYYawmu1I1XAjP
|
||||
MPuoSpaKH2JCI4wXD/S6ZJwXrEcp352YXtJsYHFcoqzceePnbgBHH7UNKOgCneSa
|
||||
/RP0ptl8sfjcXyMmCZGAc9AUG95DqYMl8uacLxXK/qarigd1iwzdUYRr5PjRznei
|
||||
gQ==
|
||||
-----END CERTIFICATE-----
|
||||
|
||||
Thawte Server CA
|
||||
================
|
||||
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIDEzCCAnygAwIBAgIBATANBgkqhkiG9w0BAQQFADCBxDELMAkGA1UEBhMCWkEx
|
||||
FTATBgNVBAgTDFdlc3Rlcm4gQ2FwZTESMBAGA1UEBxMJQ2FwZSBUb3duMR0wGwYD
|
||||
VQQKExRUaGF3dGUgQ29uc3VsdGluZyBjYzEoMCYGA1UECxMfQ2VydGlmaWNhdGlv
|
||||
biBTZXJ2aWNlcyBEaXZpc2lvbjEZMBcGA1UEAxMQVGhhd3RlIFNlcnZlciBDQTEm
|
||||
MCQGCSqGSIb3DQEJARYXc2VydmVyLWNlcnRzQHRoYXd0ZS5jb20wHhcNOTYwODAx
|
||||
MDAwMDAwWhcNMjAxMjMxMjM1OTU5WjCBxDELMAkGA1UEBhMCWkExFTATBgNVBAgT
|
||||
DFdlc3Rlcm4gQ2FwZTESMBAGA1UEBxMJQ2FwZSBUb3duMR0wGwYDVQQKExRUaGF3
|
||||
dGUgQ29uc3VsdGluZyBjYzEoMCYGA1UECxMfQ2VydGlmaWNhdGlvbiBTZXJ2aWNl
|
||||
cyBEaXZpc2lvbjEZMBcGA1UEAxMQVGhhd3RlIFNlcnZlciBDQTEmMCQGCSqGSIb3
|
||||
DQEJARYXc2VydmVyLWNlcnRzQHRoYXd0ZS5jb20wgZ8wDQYJKoZIhvcNAQEBBQAD
|
||||
gY0AMIGJAoGBANOkUG7I/1Zr5s9dtuoMaHVHoqrC2oQl/Kj0R1HahbUgdJSGHg91
|
||||
yekIYfUGbTBuFRkC6VLAYttNmZ7iagxEOM3+vuNkCXDF/rFrKbYvScg71CcEJRCX
|
||||
L+eQbcAoQpnXTEPew/UhbVSfXcNY4cDk2VuwuNy0e982OsK1ZiIS1ocNAgMBAAGj
|
||||
EzARMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEEBQADgYEAB/pMaVz7lcxG
|
||||
7oWDTSEwjsrZqG9JGubaUeNgcGyEYRGhGshIPllDfU+VPaGLtwtimHp1it2ITk6e
|
||||
QNuozDJ0uW8NxuOzRAvZim+aKZuZGCg70eNAKJpaPNW15yAbi8qkq43pUdniTCxZ
|
||||
qdq5snUb9kLy78fyGPmJvKP/iiMucEc=
|
||||
-----END CERTIFICATE-----
|
||||
|
||||
Thawte Premium Server CA
|
||||
========================
|
||||
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIDJzCCApCgAwIBAgIBATANBgkqhkiG9w0BAQQFADCBzjELMAkGA1UEBhMCWkEx
|
||||
FTATBgNVBAgTDFdlc3Rlcm4gQ2FwZTESMBAGA1UEBxMJQ2FwZSBUb3duMR0wGwYD
|
||||
VQQKExRUaGF3dGUgQ29uc3VsdGluZyBjYzEoMCYGA1UECxMfQ2VydGlmaWNhdGlv
|
||||
biBTZXJ2aWNlcyBEaXZpc2lvbjEhMB8GA1UEAxMYVGhhd3RlIFByZW1pdW0gU2Vy
|
||||
dmVyIENBMSgwJgYJKoZIhvcNAQkBFhlwcmVtaXVtLXNlcnZlckB0aGF3dGUuY29t
|
||||
MB4XDTk2MDgwMTAwMDAwMFoXDTIwMTIzMTIzNTk1OVowgc4xCzAJBgNVBAYTAlpB
|
||||
MRUwEwYDVQQIEwxXZXN0ZXJuIENhcGUxEjAQBgNVBAcTCUNhcGUgVG93bjEdMBsG
|
||||
A1UEChMUVGhhd3RlIENvbnN1bHRpbmcgY2MxKDAmBgNVBAsTH0NlcnRpZmljYXRp
|
||||
b24gU2VydmljZXMgRGl2aXNpb24xITAfBgNVBAMTGFRoYXd0ZSBQcmVtaXVtIFNl
|
||||
cnZlciBDQTEoMCYGCSqGSIb3DQEJARYZcHJlbWl1bS1zZXJ2ZXJAdGhhd3RlLmNv
|
||||
bTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA0jY2aovXwlue2oFBYo847kkE
|
||||
VdbQ7xwblRZH7xhINTpS9CtqBo87L+pW46+GjZ4X9560ZXUCTe/LCaIhUdib0GfQ
|
||||
ug2SBhRz1JPLlyoAnFxODLz6FVL88kRu2hFKbgifLy3j+ao6hnO2RlNYyIkFvYMR
|
||||
uHM/qgeN9EJN50CdHDcCAwEAAaMTMBEwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG
|
||||
9w0BAQQFAAOBgQAmSCwWwlj66BZ0DKqqX1Q/8tfJeGBeXm43YyJ3Nn6yF8Q0ufUI
|
||||
hfzJATj/Tb7yFkJD57taRvvBxhEf8UqwKEbJw8RCfbz6q1lu1bdRiBHjpIUZa4JM
|
||||
pAwSremkrj/xw0llmozFyD4lt5SZu5IycQfwhl7tUCemDaYj+bvLpgcUQg==
|
||||
-----END CERTIFICATE-----
|
||||
|
||||
Equifax Secure CA
|
||||
=================
|
||||
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIDIDCCAomgAwIBAgIENd70zzANBgkqhkiG9w0BAQUFADBOMQswCQYDVQQGEwJV
|
||||
UzEQMA4GA1UEChMHRXF1aWZheDEtMCsGA1UECxMkRXF1aWZheCBTZWN1cmUgQ2Vy
|
||||
dGlmaWNhdGUgQXV0aG9yaXR5MB4XDTk4MDgyMjE2NDE1MVoXDTE4MDgyMjE2NDE1
|
||||
MVowTjELMAkGA1UEBhMCVVMxEDAOBgNVBAoTB0VxdWlmYXgxLTArBgNVBAsTJEVx
|
||||
dWlmYXggU2VjdXJlIENlcnRpZmljYXRlIEF1dGhvcml0eTCBnzANBgkqhkiG9w0B
|
||||
AQEFAAOBjQAwgYkCgYEAwV2xWGcIYu6gmi0fCG2RFGiYCh7+2gRvE4RiIcPRfM6f
|
||||
BeC4AfBONOziipUEZKzxa1NfBbPLZ4C/QgKO/t0BCezhABRP/PvwDN1Dulsr4R+A
|
||||
cJkVV5MW8Q+XarfCaCMczE1ZMKxRHjuvK9buY0V7xdlfUNLjUA86iOe/FP3gx7kC
|
||||
AwEAAaOCAQkwggEFMHAGA1UdHwRpMGcwZaBjoGGkXzBdMQswCQYDVQQGEwJVUzEQ
|
||||
MA4GA1UEChMHRXF1aWZheDEtMCsGA1UECxMkRXF1aWZheCBTZWN1cmUgQ2VydGlm
|
||||
aWNhdGUgQXV0aG9yaXR5MQ0wCwYDVQQDEwRDUkwxMBoGA1UdEAQTMBGBDzIwMTgw
|
||||
ODIyMTY0MTUxWjALBgNVHQ8EBAMCAQYwHwYDVR0jBBgwFoAUSOZo+SvSspXXR9gj
|
||||
IBBPM5iQn9QwHQYDVR0OBBYEFEjmaPkr0rKV10fYIyAQTzOYkJ/UMAwGA1UdEwQF
|
||||
MAMBAf8wGgYJKoZIhvZ9B0EABA0wCxsFVjMuMGMDAgbAMA0GCSqGSIb3DQEBBQUA
|
||||
A4GBAFjOKer89961zgK5F7WF0bnj4JXMJTENAKaSbn+2kmOeUJXRmm/kEd5jhW6Y
|
||||
7qj/WsjTVbJmcVfewCHrPSqnI0kBBIZCe/zuf6IWUrVnZ9NA2zsmWLIodz2uFHdh
|
||||
1voqZiegDfqnc1zqcPGUIWVEX/r87yloqaKHee9570+sB3c4
|
||||
-----END CERTIFICATE-----
|
||||
|
||||
Verisign Class 1 Public Primary Certification Authority
|
||||
=======================================================
|
||||
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIICPTCCAaYCEQDNun9W8N/kvFT+IqyzcqpVMA0GCSqGSIb3DQEBAgUAMF8xCzAJ
|
||||
BgNVBAYTAlVTMRcwFQYDVQQKEw5WZXJpU2lnbiwgSW5jLjE3MDUGA1UECxMuQ2xh
|
||||
c3MgMSBQdWJsaWMgUHJpbWFyeSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw05
|
||||
NjAxMjkwMDAwMDBaFw0yODA4MDEyMzU5NTlaMF8xCzAJBgNVBAYTAlVTMRcwFQYD
|
||||
VQQKEw5WZXJpU2lnbiwgSW5jLjE3MDUGA1UECxMuQ2xhc3MgMSBQdWJsaWMgUHJp
|
||||
bWFyeSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTCBnzANBgkqhkiG9w0BAQEFAAOB
|
||||
jQAwgYkCgYEA5Rm/baNWYS2ZSHH2Z965jeu3noaACpEO+jglr0aIguVzqKCbJF0N
|
||||
H8xlbgyw0FaEGIeaBpsQoXPftFg5a27B9hXVqKg/qhIGjTGsf7A01480Z4gJzRQR
|
||||
4k5FVmkfeAKA2txHkSm7NsljXMXg1y2He6G3MrB7MLoqLzGq7qNn2tsCAwEAATAN
|
||||
BgkqhkiG9w0BAQIFAAOBgQBMP7iLxmjf7kMzDl3ppssHhE16M/+SG/Q2rdiVIjZo
|
||||
EWx8QszznC7EBz8UsA9P/5CSdvnivErpj82ggAr3xSnxgiJduLHdgSOjeyUVRjB5
|
||||
FvjqBUuUfx3CHMjjt/QQQDwTw18fU+hI5Ia0e6E1sHslurjTjqs/OJ0ANACY89Fx
|
||||
lA==
|
||||
-----END CERTIFICATE-----
|
||||
|
||||
Verisign Class 2 Public Primary Certification Authority
|
||||
=======================================================
|
||||
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIICPDCCAaUCEC0b/EoXjaOR6+f/9YtFvgswDQYJKoZIhvcNAQECBQAwXzELMAkG
|
||||
A1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMTcwNQYDVQQLEy5DbGFz
|
||||
cyAyIFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTk2
|
||||
MDEyOTAwMDAwMFoXDTI4MDgwMTIzNTk1OVowXzELMAkGA1UEBhMCVVMxFzAVBgNV
|
||||
BAoTDlZlcmlTaWduLCBJbmMuMTcwNQYDVQQLEy5DbGFzcyAyIFB1YmxpYyBQcmlt
|
||||
YXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIGfMA0GCSqGSIb3DQEBAQUAA4GN
|
||||
ADCBiQKBgQC2WoujDWojg4BrzzmH9CETMwZMJaLtVRKXxaeAufqDwSCg+i8VDXyh
|
||||
YGt+eSz6Bg86rvYbb7HS/y8oUl+DfUvEerf4Zh+AVPy3wo5ZShRXRtGak75BkQO7
|
||||
FYCTXOvnzAhsPz6zSvz/S2wj1VCCJkQZjiPDceoZJEcEnnW/yKYAHwIDAQABMA0G
|
||||
CSqGSIb3DQEBAgUAA4GBAIobK/o5wXTXXtgZZKJYSi034DNHD6zt96rbHuSLBlxg
|
||||
J8pFUs4W7z8GZOeUaHxgMxURaa+dYo2jA1Rrpr7l7gUYYAS/QoD90KioHgE796Nc
|
||||
r6Pc5iaAIzy4RHT3Cq5Ji2F4zCS/iIqnDupzGUH9TQPwiNHleI2lKk/2lw0Xd8rY
|
||||
-----END CERTIFICATE-----
|
||||
|
||||
Verisign Class 3 Public Primary Certification Authority
|
||||
=======================================================
|
||||
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIICPDCCAaUCEHC65B0Q2Sk0tjjKewPMur8wDQYJKoZIhvcNAQECBQAwXzELMAkG
|
||||
A1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMTcwNQYDVQQLEy5DbGFz
|
||||
cyAzIFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTk2
|
||||
MDEyOTAwMDAwMFoXDTI4MDgwMTIzNTk1OVowXzELMAkGA1UEBhMCVVMxFzAVBgNV
|
||||
BAoTDlZlcmlTaWduLCBJbmMuMTcwNQYDVQQLEy5DbGFzcyAzIFB1YmxpYyBQcmlt
|
||||
YXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIGfMA0GCSqGSIb3DQEBAQUAA4GN
|
||||
ADCBiQKBgQDJXFme8huKARS0EN8EQNvjV69qRUCPhAwL0TPZ2RHP7gJYHyX3KqhE
|
||||
BarsAx94f56TuZoAqiN91qyFomNFx3InzPRMxnVx0jnvT0Lwdd8KkMaOIG+YD/is
|
||||
I19wKTakyYbnsZogy1Olhec9vn2a/iRFM9x2Fe0PonFkTGUugWhFpwIDAQABMA0G
|
||||
CSqGSIb3DQEBAgUAA4GBALtMEivPLCYATxQT3ab7/AoRhIzzKBxnki98tsX63/Do
|
||||
lbwdj2wsqFHMc9ikwFPwTtYmwHYBV4GSXiHx0bH/59AhWM1pF+NEHJwZRDmJXNyc
|
||||
AA9WjQKZ7aKQRUzkuxCkPfAyAw7xzvjoyVGM5mKf5p/AfbdynMk2OmufTqj/ZA1k
|
||||
-----END CERTIFICATE-----
|
||||
|
||||
Verisign Class 1 Public Primary Certification Authority - G2
|
||||
============================================================
|
||||
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIDAjCCAmsCEEzH6qqYPnHTkxD4PTqJkZIwDQYJKoZIhvcNAQEFBQAwgcExCzAJ
|
||||
BgNVBAYTAlVTMRcwFQYDVQQKEw5WZXJpU2lnbiwgSW5jLjE8MDoGA1UECxMzQ2xh
|
||||
c3MgMSBQdWJsaWMgUHJpbWFyeSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAtIEcy
|
||||
MTowOAYDVQQLEzEoYykgMTk5OCBWZXJpU2lnbiwgSW5jLiAtIEZvciBhdXRob3Jp
|
||||
emVkIHVzZSBvbmx5MR8wHQYDVQQLExZWZXJpU2lnbiBUcnVzdCBOZXR3b3JrMB4X
|
||||
DTk4MDUxODAwMDAwMFoXDTI4MDgwMTIzNTk1OVowgcExCzAJBgNVBAYTAlVTMRcw
|
||||
FQYDVQQKEw5WZXJpU2lnbiwgSW5jLjE8MDoGA1UECxMzQ2xhc3MgMSBQdWJsaWMg
|
||||
UHJpbWFyeSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAtIEcyMTowOAYDVQQLEzEo
|
||||
YykgMTk5OCBWZXJpU2lnbiwgSW5jLiAtIEZvciBhdXRob3JpemVkIHVzZSBvbmx5
|
||||
MR8wHQYDVQQLExZWZXJpU2lnbiBUcnVzdCBOZXR3b3JrMIGfMA0GCSqGSIb3DQEB
|
||||
AQUAA4GNADCBiQKBgQCq0Lq+Fi24g9TK0g+8djHKlNgdk4xWArzZbxpvUjZudVYK
|
||||
VdPfQ4chEWWKfo+9Id5rMj8bhDSVBZ1BNeuS65bdqlk/AVNtmU/t5eIqWpDBucSm
|
||||
Fc/IReumXY6cPvBkJHalzasab7bYe1FhbqZ/h8jit+U03EGI6glAvnOSPWvndQID
|
||||
AQABMA0GCSqGSIb3DQEBBQUAA4GBAKlPww3HZ74sy9mozS11534Vnjty637rXC0J
|
||||
h9ZrbWB85a7FkCMMXErQr7Fd88e2CtvgFZMN3QO8x3aKtd1Pw5sTdbgBwObJW2ul
|
||||
uIncrKTdcu1OofdPvAbT6shkdHvClUGcZXNY8ZCaPGqxmMnEh7zPRW1F4m4iP/68
|
||||
DzFc6PLZ
|
||||
-----END CERTIFICATE-----
|
||||
|
||||
Verisign Class 2 Public Primary Certification Authority - G2
|
||||
============================================================
|
||||
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIDAzCCAmwCEQC5L2DMiJ+hekYJuFtwbIqvMA0GCSqGSIb3DQEBBQUAMIHBMQsw
|
||||
CQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xPDA6BgNVBAsTM0Ns
|
||||
YXNzIDIgUHVibGljIFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgLSBH
|
||||
MjE6MDgGA1UECxMxKGMpIDE5OTggVmVyaVNpZ24sIEluYy4gLSBGb3IgYXV0aG9y
|
||||
aXplZCB1c2Ugb25seTEfMB0GA1UECxMWVmVyaVNpZ24gVHJ1c3QgTmV0d29yazAe
|
||||
Fw05ODA1MTgwMDAwMDBaFw0yODA4MDEyMzU5NTlaMIHBMQswCQYDVQQGEwJVUzEX
|
||||
MBUGA1UEChMOVmVyaVNpZ24sIEluYy4xPDA6BgNVBAsTM0NsYXNzIDIgUHVibGlj
|
||||
IFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgLSBHMjE6MDgGA1UECxMx
|
||||
KGMpIDE5OTggVmVyaVNpZ24sIEluYy4gLSBGb3IgYXV0aG9yaXplZCB1c2Ugb25s
|
||||
eTEfMB0GA1UECxMWVmVyaVNpZ24gVHJ1c3QgTmV0d29yazCBnzANBgkqhkiG9w0B
|
||||
AQEFAAOBjQAwgYkCgYEAp4gBIXQs5xoD8JjhlzwPIQjxnNuX6Zr8wgQGE75fUsjM
|
||||
HiwSViy4AWkszJkfrbCWrnkE8hM5wXuYuggs6MKEEyyqaekJ9MepAqRCwiNPStjw
|
||||
DqL7MWzJ5m+ZJwf15vRMeJ5t60aG+rmGyVTyssSv1EYcWskVMP8NbPUtDm3Of3cC
|
||||
AwEAATANBgkqhkiG9w0BAQUFAAOBgQByLvl/0fFx+8Se9sVeUYpAmLho+Jscg9ji
|
||||
nb3/7aHmZuovCfTK1+qlK5X2JGCGTUQug6XELaDTrnhpb3LabK4I8GOSN+a7xDAX
|
||||
rXfMSTWqz9iP0b63GJZHc2pUIjRkLbYWm1lbtFFZOrMLFPQS32eg9K0yZF6xRnIn
|
||||
jBJ7xUS0rg==
|
||||
-----END CERTIFICATE-----
|
||||
|
||||
Verisign Class 3 Public Primary Certification Authority - G2
|
||||
============================================================
|
||||
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIDAjCCAmsCEH3Z/gfPqB63EHln+6eJNMYwDQYJKoZIhvcNAQEFBQAwgcExCzAJ
|
||||
BgNVBAYTAlVTMRcwFQYDVQQKEw5WZXJpU2lnbiwgSW5jLjE8MDoGA1UECxMzQ2xh
|
||||
c3MgMyBQdWJsaWMgUHJpbWFyeSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAtIEcy
|
||||
MTowOAYDVQQLEzEoYykgMTk5OCBWZXJpU2lnbiwgSW5jLiAtIEZvciBhdXRob3Jp
|
||||
emVkIHVzZSBvbmx5MR8wHQYDVQQLExZWZXJpU2lnbiBUcnVzdCBOZXR3b3JrMB4X
|
||||
DTk4MDUxODAwMDAwMFoXDTI4MDgwMTIzNTk1OVowgcExCzAJBgNVBAYTAlVTMRcw
|
||||
FQYDVQQKEw5WZXJpU2lnbiwgSW5jLjE8MDoGA1UECxMzQ2xhc3MgMyBQdWJsaWMg
|
||||
UHJpbWFyeSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAtIEcyMTowOAYDVQQLEzEo
|
||||
YykgMTk5OCBWZXJpU2lnbiwgSW5jLiAtIEZvciBhdXRob3JpemVkIHVzZSBvbmx5
|
||||
MR8wHQYDVQQLExZWZXJpU2lnbiBUcnVzdCBOZXR3b3JrMIGfMA0GCSqGSIb3DQEB
|
||||
AQUAA4GNADCBiQKBgQDMXtERXVxp0KvTuWpMmR9ZmDCOFoUgRm1HP9SFIIThbbP4
|
||||
pO0M8RcPO/mn+SXXwc+EY/J8Y8+iR/LGWzOOZEAEaMGAuWQcRXfH2G71lSk8UOg0
|
||||
13gfqLptQ5GVj0VXXn7F+8qkBOvqlzdUMG+7AUcyM83cV5tkaWH4mx0ciU9cZwID
|
||||
AQABMA0GCSqGSIb3DQEBBQUAA4GBAFFNzb5cy5gZnBWyATl4Lk0PZ3BwmcYQWpSk
|
||||
U01UbSuvDV1Ai2TT1+7eVmGSX6bEHRBhNtMsJzzoKQm5EWR0zLVznxxIqbxhAe7i
|
||||
F6YM40AIOw7n60RzKprxaZLvcRTDOaxxp5EJb+RxBrO6WVcmeQD2+A2iMzAo1KpY
|
||||
oJ2daZH9
|
||||
-----END CERTIFICATE-----
|
||||
|
||||
Verisign Class 4 Public Primary Certification Authority - G2
|
||||
============================================================
|
||||
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIDAjCCAmsCEDKIjprS9esTR/h/xCA3JfgwDQYJKoZIhvcNAQEFBQAwgcExCzAJ
|
||||
BgNVBAYTAlVTMRcwFQYDVQQKEw5WZXJpU2lnbiwgSW5jLjE8MDoGA1UECxMzQ2xh
|
||||
c3MgNCBQdWJsaWMgUHJpbWFyeSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAtIEcy
|
||||
MTowOAYDVQQLEzEoYykgMTk5OCBWZXJpU2lnbiwgSW5jLiAtIEZvciBhdXRob3Jp
|
||||
emVkIHVzZSBvbmx5MR8wHQYDVQQLExZWZXJpU2lnbiBUcnVzdCBOZXR3b3JrMB4X
|
||||
DTk4MDUxODAwMDAwMFoXDTI4MDgwMTIzNTk1OVowgcExCzAJBgNVBAYTAlVTMRcw
|
||||
FQYDVQQKEw5WZXJpU2lnbiwgSW5jLjE8MDoGA1UECxMzQ2xhc3MgNCBQdWJsaWMg
|
||||
UHJpbWFyeSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAtIEcyMTowOAYDVQQLEzEo
|
||||
YykgMTk5OCBWZXJpU2lnbiwgSW5jLiAtIEZvciBhdXRob3JpemVkIHVzZSBvbmx5
|
||||
MR8wHQYDVQQLExZWZXJpU2lnbiBUcnVzdCBOZXR3b3JrMIGfMA0GCSqGSIb3DQEB
|
||||
AQUAA4GNADCBiQKBgQC68OTP+cSuhVS5B1f5j8V/aBH4xBewRNzjMHPVKmIquNDM
|
||||
HO0oW369atyzkSTKQWI8/AIBvxwWMZQFl3Zuoq29YRdsTjCG8FE3KlDHqGKB3FtK
|
||||
qsGgtG7rL+VXxbErQHDbWk2hjh+9Ax/YA9SPTJlxvOKCzFjomDqG04Y48wApHwID
|
||||
AQABMA0GCSqGSIb3DQEBBQUAA4GBAIWMEsGnuVAVess+rLhDityq3RS6iYF+ATwj
|
||||
cSGIL4LcY/oCRaxFWdcqWERbt5+BO5JoPeI3JPV7bI92NZYJqFmduc4jq3TWg/0y
|
||||
cyfYaT5DdPauxYma51N86Xv2S/PBZYPejYqcPIiNOVn8qj8ijaHBZlCBckztImRP
|
||||
T8qAkbYp
|
||||
-----END CERTIFICATE-----
|
||||
|
||||
Verisign Class 1 Public Primary Certification Authority - G3
|
||||
============================================================
|
||||
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIEGjCCAwICEQCLW3VWhFSFCwDPrzhIzrGkMA0GCSqGSIb3DQEBBQUAMIHKMQsw
|
||||
CQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZl
|
||||
cmlTaWduIFRydXN0IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAxOTk5IFZlcmlTaWdu
|
||||
LCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxRTBDBgNVBAMTPFZlcmlT
|
||||
aWduIENsYXNzIDEgUHVibGljIFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3Jp
|
||||
dHkgLSBHMzAeFw05OTEwMDEwMDAwMDBaFw0zNjA3MTYyMzU5NTlaMIHKMQswCQYD
|
||||
VQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlT
|
||||
aWduIFRydXN0IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAxOTk5IFZlcmlTaWduLCBJ
|
||||
bmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxRTBDBgNVBAMTPFZlcmlTaWdu
|
||||
IENsYXNzIDEgUHVibGljIFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkg
|
||||
LSBHMzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAN2E1Lm0+afY8wR4
|
||||
nN493GwTFtl63SRRZsDHJlkNrAYIwpTRMx/wgzUfbhvI3qpuFU5UJ+/EbRrsC+MO
|
||||
8ESlV8dAWB6jRx9x7GD2bZTIGDnt/kIYVt/kTEkQeE4BdjVjEjbdZrwBBDajVWjV
|
||||
ojYJrKshJlQGrT/KFOCsyq0GHZXi+J3x4GD/wn91K0zM2v6HmSHquv4+VNfSWXjb
|
||||
PG7PoBMAGrgnoeS+Z5bKoMWznN3JdZ7rMJpfo83ZrngZPyPpXNspva1VyBtUjGP2
|
||||
6KbqxzcSXKMpHgLZ2x87tNcPVkeBFQRKr4Mn0cVYiMHd9qqnoxjaaKptEVHhv2Vr
|
||||
n5Z20T0CAwEAATANBgkqhkiG9w0BAQUFAAOCAQEAq2aN17O6x5q25lXQBfGfMY1a
|
||||
qtmqRiYPce2lrVNWYgFHKkTp/j90CxObufRNG7LRX7K20ohcs5/Ny9Sn2WCVhDr4
|
||||
wTcdYcrnsMXlkdpUpqwxga6X3s0IrLjAl4B/bnKk52kTlWUfxJM8/XmPBNQ+T+r3
|
||||
ns7NZ3xPZQL/kYVUc8f/NveGLezQXk//EZ9yBta4GvFMDSZl4kSAHsef493oCtrs
|
||||
pSCAaWihT37ha88HQfqDjrw43bAuEbFrskLMmrz5SCJ5ShkPshw+IHTZasO+8ih4
|
||||
E1Z5T21Q6huwtVexN2ZYI/PcD98Kh8TvhgXVOBRgmaNL3gaWcSzy27YfpO8/7g==
|
||||
-----END CERTIFICATE-----
|
||||
|
||||
Verisign Class 2 Public Primary Certification Authority - G3
|
||||
============================================================
|
||||
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIEGTCCAwECEGFwy0mMX5hFKeewptlQW3owDQYJKoZIhvcNAQEFBQAwgcoxCzAJ
|
||||
BgNVBAYTAlVTMRcwFQYDVQQKEw5WZXJpU2lnbiwgSW5jLjEfMB0GA1UECxMWVmVy
|
||||
aVNpZ24gVHJ1c3QgTmV0d29yazE6MDgGA1UECxMxKGMpIDE5OTkgVmVyaVNpZ24s
|
||||
IEluYy4gLSBGb3IgYXV0aG9yaXplZCB1c2Ugb25seTFFMEMGA1UEAxM8VmVyaVNp
|
||||
Z24gQ2xhc3MgMiBQdWJsaWMgUHJpbWFyeSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0
|
||||
eSAtIEczMB4XDTk5MTAwMTAwMDAwMFoXDTM2MDcxNjIzNTk1OVowgcoxCzAJBgNV
|
||||
BAYTAlVTMRcwFQYDVQQKEw5WZXJpU2lnbiwgSW5jLjEfMB0GA1UECxMWVmVyaVNp
|
||||
Z24gVHJ1c3QgTmV0d29yazE6MDgGA1UECxMxKGMpIDE5OTkgVmVyaVNpZ24sIElu
|
||||
Yy4gLSBGb3IgYXV0aG9yaXplZCB1c2Ugb25seTFFMEMGA1UEAxM8VmVyaVNpZ24g
|
||||
Q2xhc3MgMiBQdWJsaWMgUHJpbWFyeSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAt
|
||||
IEczMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEArwoNwtUs22e5LeWU
|
||||
J92lvuCwTY+zYVY81nzD9M0+hsuiiOLh2KRpxbXiv8GmR1BeRjmL1Za6tW8UvxDO
|
||||
JxOeBUebMXoT2B/Z0wI3i60sR/COgQanDTAM6/c8DyAd3HJG7qUCyFvDyVZpTMUY
|
||||
wZF7C9UTAJu878NIPkZgIIUq1ZC2zYugzDLdt/1AVbJQHFauzI13TccgTacxdu9o
|
||||
koqQHgiBVrKtaaNS0MscxCM9H5n+TOgWY47GCI72MfbS+uV23bUckqNJzc0BzWjN
|
||||
qWm6o+sdDZykIKbBoMXRRkwXbdKsZj+WjOCE1Db/IlnF+RFgqF8EffIa9iVCYQ/E
|
||||
Srg+iQIDAQABMA0GCSqGSIb3DQEBBQUAA4IBAQA0JhU8wI1NQ0kdvekhktdmnLfe
|
||||
xbjQ5F1fdiLAJvmEOjr5jLX77GDx6M4EsMjdpwOPMPOY36TmpDHf0xwLRtxyID+u
|
||||
7gU8pDM/CzmscHhzS5kr3zDCVLCoO1Wh/hYozUK9dG6A2ydEp85EXdQbkJgNHkKU
|
||||
sQAsBNB0owIFImNjzYO1+8FtYmtpdf1dcEG59b98377BMnMiIYtYgXsVkXq642RI
|
||||
sH/7NiXaldDxJBQX3RiAa0YjOVT1jmIJBB2UkKab5iXiQkWquJCtvgiPqQtCGJTP
|
||||
cjnhsUPgKM+351psE2tJs//jGHyJizNdrDPXp/naOlXJWBD5qu9ats9LS98q
|
||||
-----END CERTIFICATE-----
|
||||
|
||||
Verisign Class 3 Public Primary Certification Authority - G3
|
||||
============================================================
|
||||
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIEGjCCAwICEQCbfgZJoz5iudXukEhxKe9XMA0GCSqGSIb3DQEBBQUAMIHKMQsw
|
||||
CQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZl
|
||||
cmlTaWduIFRydXN0IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAxOTk5IFZlcmlTaWdu
|
||||
LCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxRTBDBgNVBAMTPFZlcmlT
|
||||
aWduIENsYXNzIDMgUHVibGljIFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3Jp
|
||||
dHkgLSBHMzAeFw05OTEwMDEwMDAwMDBaFw0zNjA3MTYyMzU5NTlaMIHKMQswCQYD
|
||||
VQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlT
|
||||
aWduIFRydXN0IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAxOTk5IFZlcmlTaWduLCBJ
|
||||
bmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxRTBDBgNVBAMTPFZlcmlTaWdu
|
||||
IENsYXNzIDMgUHVibGljIFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkg
|
||||
LSBHMzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMu6nFL8eB8aHm8b
|
||||
N3O9+MlrlBIwT/A2R/XQkQr1F8ilYcEWQE37imGQ5XYgwREGfassbqb1EUGO+i2t
|
||||
KmFZpGcmTNDovFJbcCAEWNF6yaRpvIMXZK0Fi7zQWM6NjPXr8EJJC52XJ2cybuGu
|
||||
kxUccLwgTS8Y3pKI6GyFVxEa6X7jJhFUokWWVYPKMIno3Nij7SqAP395ZVc+FSBm
|
||||
CC+Vk7+qRy+oRpfwEuL+wgorUeZ25rdGt+INpsyow0xZVYnm6FNcHOqd8GIWC6fJ
|
||||
Xwzw3sJ2zq/3avL6QaaiMxTJ5Xpj055iN9WFZZ4O5lMkdBteHRJTW8cs54NJOxWu
|
||||
imi5V5cCAwEAATANBgkqhkiG9w0BAQUFAAOCAQEAERSWwauSCPc/L8my/uRan2Te
|
||||
2yFPhpk0djZX3dAVL8WtfxUfN2JzPtTnX84XA9s1+ivbrmAJXx5fj267Cz3qWhMe
|
||||
DGBvtcC1IyIuBwvLqXTLR7sdwdela8wv0kL9Sd2nic9TutoAWii/gt/4uhMdUIaC
|
||||
/Y4wjylGsB49Ndo4YhYYSq3mtlFs3q9i6wHQHiT+eo8SGhJouPtmmRQURVyu565p
|
||||
F4ErWjfJXir0xuKhXFSbplQAz/DxwceYMBo7Nhbbo27q/a2ywtrvAkcTisDxszGt
|
||||
TxzhT5yvDwyd93gN2PQ1VoDat20Xj50egWTh/sVFuq1ruQp6Tk9LhO5L8X3dEQ==
|
||||
-----END CERTIFICATE-----
|
||||
|
||||
Verisign Class 4 Public Primary Certification Authority - G3
|
||||
============================================================
|
||||
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIEGjCCAwICEQDsoKeLbnVqAc/EfMwvlF7XMA0GCSqGSIb3DQEBBQUAMIHKMQsw
|
||||
CQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZl
|
||||
cmlTaWduIFRydXN0IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAxOTk5IFZlcmlTaWdu
|
||||
LCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxRTBDBgNVBAMTPFZlcmlT
|
||||
aWduIENsYXNzIDQgUHVibGljIFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3Jp
|
||||
dHkgLSBHMzAeFw05OTEwMDEwMDAwMDBaFw0zNjA3MTYyMzU5NTlaMIHKMQswCQYD
|
||||
VQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlT
|
||||
aWduIFRydXN0IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAxOTk5IFZlcmlTaWduLCBJ
|
||||
bmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxRTBDBgNVBAMTPFZlcmlTaWdu
|
||||
IENsYXNzIDQgUHVibGljIFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkg
|
||||
LSBHMzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAK3LpRFpxlmr8Y+1
|
||||
GQ9Wzsy1HyDkniYlS+BzZYlZ3tCD5PUPtbut8XzoIfzk6AzufEUiGXaStBO3IFsJ
|
||||
+mGuqPKljYXCKtbeZjbSmwL0qJJgfJxptI8kHtCGUvYynEFYHiK9zUVilQhu0Gbd
|
||||
U6LM8BDcVHOLBKFGMzNcF0C5nk3T875Vg+ixiY5afJqWIpA7iCXy0lOIAgwLePLm
|
||||
NxdLMEYH5IBtptiWLugs+BGzOA1mppvqySNb247i8xOOGlktqgLw7KSHZtzBP/XY
|
||||
ufTsgsbSPZUd5cBPhMnZo0QoBmrXRazwa2rvTl/4EYIeOGM0ZlDUPpNz+jDDZq3/
|
||||
ky2X7wMCAwEAATANBgkqhkiG9w0BAQUFAAOCAQEAj/ola09b5KROJ1WrIhVZPMq1
|
||||
CtRK26vdoV9TxaBXOcLORyu+OshWv8LZJxA6sQU8wHcxuzrTBXttmhwwjIDLk5Mq
|
||||
g6sFUYICABFna/OIYUdfA5PVWw3g8dShMjWFsjrbsIKr0csKvE+MW8VLADsfKoKm
|
||||
fjaF3H48ZwC15DtS4KjrXRX5xm3wrR0OhbepmnMUWluPQSjA1egtTaRezarZ7c7c
|
||||
2NU8Qh0XwRJdRTjDOPP8hS6DRkiy1yBfkjaP53kPmF6Z6PDQpLv1U70qzlmwr25/
|
||||
bLvSHgCwIe34QWKCudiyxLtGUPMxxY8BqHTr9Xgn2uf3ZkPznoM+IKrDNWCRzg==
|
||||
-----END CERTIFICATE-----
|
||||
|
||||
Equifax Secure Global eBusiness CA
|
||||
==================================
|
||||
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIICkDCCAfmgAwIBAgIBATANBgkqhkiG9w0BAQQFADBaMQswCQYDVQQGEwJVUzEc
|
||||
MBoGA1UEChMTRXF1aWZheCBTZWN1cmUgSW5jLjEtMCsGA1UEAxMkRXF1aWZheCBT
|
||||
ZWN1cmUgR2xvYmFsIGVCdXNpbmVzcyBDQS0xMB4XDTk5MDYyMTA0MDAwMFoXDTIw
|
||||
MDYyMTA0MDAwMFowWjELMAkGA1UEBhMCVVMxHDAaBgNVBAoTE0VxdWlmYXggU2Vj
|
||||
dXJlIEluYy4xLTArBgNVBAMTJEVxdWlmYXggU2VjdXJlIEdsb2JhbCBlQnVzaW5l
|
||||
c3MgQ0EtMTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAuucXkAJlsTRVPEnC
|
||||
UdXfp9E3j9HngXNBUmCbnaEXJnitx7HoJpQytd4zjTov2/KaelpzmKNc6fuKcxtc
|
||||
58O/gGzNqfTWK8D3+ZmqY6KxRwIP1ORROhI8bIpaVIRw28HFkM9yRcuoWcDNM50/
|
||||
o5brhTMhHD4ePmBudpxnhcXIw2ECAwEAAaNmMGQwEQYJYIZIAYb4QgEBBAQDAgAH
|
||||
MA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAUvqigdHJQa0S3ySPY+6j/s1dr
|
||||
aGwwHQYDVR0OBBYEFL6ooHRyUGtEt8kj2Puo/7NXa2hsMA0GCSqGSIb3DQEBBAUA
|
||||
A4GBADDiAVGqx+pf2rnQZQ8w1j7aDRRJbpGTJxQx78T3LUX47Me/okENI7SS+RkA
|
||||
Z70Br83gcfxaz2TE4JaY0KNA4gGK7ycH8WUBikQtBmV1UsCGECAhX2xrD2yuCRyv
|
||||
8qIYNMR1pHMc8Y3c7635s3a0kr/clRAevsvIO1qEYBlWlKlV
|
||||
-----END CERTIFICATE-----
|
||||
|
||||
Equifax Secure eBusiness CA 1
|
||||
=============================
|
||||
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIICgjCCAeugAwIBAgIBBDANBgkqhkiG9w0BAQQFADBTMQswCQYDVQQGEwJVUzEc
|
||||
MBoGA1UEChMTRXF1aWZheCBTZWN1cmUgSW5jLjEmMCQGA1UEAxMdRXF1aWZheCBT
|
||||
ZWN1cmUgZUJ1c2luZXNzIENBLTEwHhcNOTkwNjIxMDQwMDAwWhcNMjAwNjIxMDQw
|
||||
MDAwWjBTMQswCQYDVQQGEwJVUzEcMBoGA1UEChMTRXF1aWZheCBTZWN1cmUgSW5j
|
||||
LjEmMCQGA1UEAxMdRXF1aWZheCBTZWN1cmUgZUJ1c2luZXNzIENBLTEwgZ8wDQYJ
|
||||
KoZIhvcNAQEBBQADgY0AMIGJAoGBAM4vGbwXt3fek6lfWg0XTzQaDJj0ItlZ1MRo
|
||||
RvC0NcWFAyDGr0WlIVFFQesWWDYyb+JQYmT5/VGcqiTZ9J2DKocKIdMSODRsjQBu
|
||||
WqDZQu4aIZX5UkxVWsUPOE9G+m34LjXWHXzr4vCwdYDIqROsvojvOm6rXyo4YgKw
|
||||
Env+j6YDAgMBAAGjZjBkMBEGCWCGSAGG+EIBAQQEAwIABzAPBgNVHRMBAf8EBTAD
|
||||
AQH/MB8GA1UdIwQYMBaAFEp4MlIR21kWNl7fwRQ2QGpHfEyhMB0GA1UdDgQWBBRK
|
||||
eDJSEdtZFjZe38EUNkBqR3xMoTANBgkqhkiG9w0BAQQFAAOBgQB1W6ibAxHm6VZM
|
||||
zfmpTMANmvPMZWnmJXbMWbfWVMMdzZmsGd20hdXgPfxiIKeES1hl8eL5lSE/9dR+
|
||||
WB5Hh1Q+WKG1tfgq73HnvMP2sUlG4tega+VWeponmHxGYhTnyfxuAxJ5gDgdSIKN
|
||||
/Bf+KpYrtWKmpj29f5JZzVoqgrI3eQ==
|
||||
-----END CERTIFICATE-----
|
||||
|
||||
Equifax Secure eBusiness CA 2
|
||||
=============================
|
||||
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIDIDCCAomgAwIBAgIEN3DPtTANBgkqhkiG9w0BAQUFADBOMQswCQYDVQQGEwJV
|
||||
UzEXMBUGA1UEChMORXF1aWZheCBTZWN1cmUxJjAkBgNVBAsTHUVxdWlmYXggU2Vj
|
||||
dXJlIGVCdXNpbmVzcyBDQS0yMB4XDTk5MDYyMzEyMTQ0NVoXDTE5MDYyMzEyMTQ0
|
||||
NVowTjELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDkVxdWlmYXggU2VjdXJlMSYwJAYD
|
||||
VQQLEx1FcXVpZmF4IFNlY3VyZSBlQnVzaW5lc3MgQ0EtMjCBnzANBgkqhkiG9w0B
|
||||
AQEFAAOBjQAwgYkCgYEA5Dk5kx5SBhsoNviyoynF7Y6yEb3+6+e0dMKP/wXn2Z0G
|
||||
vxLIPw7y1tEkshHe0XMJitSxLJgJDR5QRrKDpkWNYmi7hRsgcDKqQM2mll/EcTc/
|
||||
BPO3QSQ5BxoeLmFYoBIL5aXfxavqN3HMHMg3OrmXUqesxWoklE6ce8/AatbfIb0C
|
||||
AwEAAaOCAQkwggEFMHAGA1UdHwRpMGcwZaBjoGGkXzBdMQswCQYDVQQGEwJVUzEX
|
||||
MBUGA1UEChMORXF1aWZheCBTZWN1cmUxJjAkBgNVBAsTHUVxdWlmYXggU2VjdXJl
|
||||
IGVCdXNpbmVzcyBDQS0yMQ0wCwYDVQQDEwRDUkwxMBoGA1UdEAQTMBGBDzIwMTkw
|
||||
NjIzMTIxNDQ1WjALBgNVHQ8EBAMCAQYwHwYDVR0jBBgwFoAUUJ4L6q9euSBIplBq
|
||||
y/3YIHqngnYwHQYDVR0OBBYEFFCeC+qvXrkgSKZQasv92CB6p4J2MAwGA1UdEwQF
|
||||
MAMBAf8wGgYJKoZIhvZ9B0EABA0wCxsFVjMuMGMDAgbAMA0GCSqGSIb3DQEBBQUA
|
||||
A4GBAAyGgq3oThr1jokn4jVYPSm0B482UJW/bsGe68SQsoWou7dC4A8HOd/7npCy
|
||||
0cE+U58DRLB+S/Rv5Hwf5+Kx5Lia78O9zt4LMjTZ3ijtM2vE1Nc9ElirfQkty3D1
|
||||
E4qUoSek1nDFbZS1yX2doNLGCEnZZpum0/QL3MUmV+GRMOrN
|
||||
-----END CERTIFICATE-----
|
||||
|
||||
Thawte Time Stamping CA
|
||||
=======================
|
||||
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIICoTCCAgqgAwIBAgIBADANBgkqhkiG9w0BAQQFADCBizELMAkGA1UEBhMCWkEx
|
||||
FTATBgNVBAgTDFdlc3Rlcm4gQ2FwZTEUMBIGA1UEBxMLRHVyYmFudmlsbGUxDzAN
|
||||
BgNVBAoTBlRoYXd0ZTEdMBsGA1UECxMUVGhhd3RlIENlcnRpZmljYXRpb24xHzAd
|
||||
BgNVBAMTFlRoYXd0ZSBUaW1lc3RhbXBpbmcgQ0EwHhcNOTcwMTAxMDAwMDAwWhcN
|
||||
MjAxMjMxMjM1OTU5WjCBizELMAkGA1UEBhMCWkExFTATBgNVBAgTDFdlc3Rlcm4g
|
||||
Q2FwZTEUMBIGA1UEBxMLRHVyYmFudmlsbGUxDzANBgNVBAoTBlRoYXd0ZTEdMBsG
|
||||
A1UECxMUVGhhd3RlIENlcnRpZmljYXRpb24xHzAdBgNVBAMTFlRoYXd0ZSBUaW1l
|
||||
c3RhbXBpbmcgQ0EwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBANYrWHhhRYZT
|
||||
6jR7UZztsOYuGA7+4F+oJ9O0yeB8WU4WDnNUYMF/9p8u6TqFJBU820cEY8OexJQa
|
||||
Wt9MevPZQx08EHp5JduQ/vBR5zDWQQD9nyjfeb6Uu522FOMjhdepQeBMpHmwKxqL
|
||||
8vg7ij5FrHGSALSQQZj7X+36ty6K+Ig3AgMBAAGjEzARMA8GA1UdEwEB/wQFMAMB
|
||||
Af8wDQYJKoZIhvcNAQEEBQADgYEAZ9viwuaHPUCDhjc1fR/OmsMMZiCouqoEiYbC
|
||||
9RAIDb/LogWK0E02PvTX72nGXuSwlG9KuefeW4i2e9vjJ+V2w/A1wcu1J5szedyQ
|
||||
pgCed/r8zSeUQhac0xxo7L9c3eWpexAKMnRUEzGLhQOEkbdYATAUOK8oyvyxUBkZ
|
||||
CayJSdM=
|
||||
-----END CERTIFICATE-----
|
||||
|
||||
thawte Primary Root CA
|
||||
======================
|
||||
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIEIDCCAwigAwIBAgIQNE7VVyDV7exJ9C/ON9srbTANBgkqhkiG9w0BAQUFADCB
|
||||
qTELMAkGA1UEBhMCVVMxFTATBgNVBAoTDHRoYXd0ZSwgSW5jLjEoMCYGA1UECxMf
|
||||
Q2VydGlmaWNhdGlvbiBTZXJ2aWNlcyBEaXZpc2lvbjE4MDYGA1UECxMvKGMpIDIw
|
||||
MDYgdGhhd3RlLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxHzAdBgNV
|
||||
BAMTFnRoYXd0ZSBQcmltYXJ5IFJvb3QgQ0EwHhcNMDYxMTE3MDAwMDAwWhcNMzYw
|
||||
NzE2MjM1OTU5WjCBqTELMAkGA1UEBhMCVVMxFTATBgNVBAoTDHRoYXd0ZSwgSW5j
|
||||
LjEoMCYGA1UECxMfQ2VydGlmaWNhdGlvbiBTZXJ2aWNlcyBEaXZpc2lvbjE4MDYG
|
||||
A1UECxMvKGMpIDIwMDYgdGhhd3RlLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNl
|
||||
IG9ubHkxHzAdBgNVBAMTFnRoYXd0ZSBQcmltYXJ5IFJvb3QgQ0EwggEiMA0GCSqG
|
||||
SIb3DQEBAQUAA4IBDwAwggEKAoIBAQCsoPD7gFnUnMekz52hWXMJEEUMDSxuaPFs
|
||||
W0hoSVk3/AszGcJ3f8wQLZU0HObrTQmnHNK4yZc2AreJ1CRfBsDMRJSUjQJib+ta
|
||||
3RGNKJpchJAQeg29dGYvajig4tVUROsdB58Hum/u6f1OCyn1PoSgAfGcq/gcfomk
|
||||
6KHYcWUNo1F77rzSImANuVud37r8UVsLr5iy6S7pBOhih94ryNdOwUxkHt3Ph1i6
|
||||
Sk/KaAcdHJ1KxtUvkcx8cXIcxcBn6zL9yZJclNqFwJu/U30rCfSMnZEfl2pSy94J
|
||||
NqR32HuHUETVPm4pafs5SSYeCaWAe0At6+gnhcn+Yf1+5nyXHdWdAgMBAAGjQjBA
|
||||
MA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBR7W0XP
|
||||
r87Lev0xkhpqtvNG61dIUDANBgkqhkiG9w0BAQUFAAOCAQEAeRHAS7ORtvzw6WfU
|
||||
DW5FvlXok9LOAz/t2iWwHVfLHjp2oEzsUHboZHIMpKnxuIvW1oeEuzLlQRHAd9mz
|
||||
YJ3rG9XRbkREqaYB7FViHXe4XI5ISXycO1cRrK1zN44veFyQaEfZYGDm/Ac9IiAX
|
||||
xPcW6cTYcvnIc3zfFi8VqT79aie2oetaupgf1eNNZAqdE8hhuvU5HIe6uL17In/2
|
||||
/qxAeeWsEG89jxt5dovEN7MhGITlNgDrYyCZuen+MwS7QcjBAvlEYyCegc5C09Y/
|
||||
LHbTY5xZ3Y+m4Q6gLkH3LpVHz7z9M/P2C2F+fpErgUfCJzDupxBdN49cOSvkBPB7
|
||||
jVaMaA==
|
||||
-----END CERTIFICATE-----
|
||||
|
||||
VeriSign Class 3 Public Primary Certification Authority - G5
|
||||
============================================================
|
||||
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIE0zCCA7ugAwIBAgIQGNrRniZ96LtKIVjNzGs7SjANBgkqhkiG9w0BAQUFADCB
|
||||
yjELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQL
|
||||
ExZWZXJpU2lnbiBUcnVzdCBOZXR3b3JrMTowOAYDVQQLEzEoYykgMjAwNiBWZXJp
|
||||
U2lnbiwgSW5jLiAtIEZvciBhdXRob3JpemVkIHVzZSBvbmx5MUUwQwYDVQQDEzxW
|
||||
ZXJpU2lnbiBDbGFzcyAzIFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0
|
||||
aG9yaXR5IC0gRzUwHhcNMDYxMTA4MDAwMDAwWhcNMzYwNzE2MjM1OTU5WjCByjEL
|
||||
MAkGA1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQLExZW
|
||||
ZXJpU2lnbiBUcnVzdCBOZXR3b3JrMTowOAYDVQQLEzEoYykgMjAwNiBWZXJpU2ln
|
||||
biwgSW5jLiAtIEZvciBhdXRob3JpemVkIHVzZSBvbmx5MUUwQwYDVQQDEzxWZXJp
|
||||
U2lnbiBDbGFzcyAzIFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0aG9y
|
||||
aXR5IC0gRzUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCvJAgIKXo1
|
||||
nmAMqudLO07cfLw8RRy7K+D+KQL5VwijZIUVJ/XxrcgxiV0i6CqqpkKzj/i5Vbex
|
||||
t0uz/o9+B1fs70PbZmIVYc9gDaTY3vjgw2IIPVQT60nKWVSFJuUrjxuf6/WhkcIz
|
||||
SdhDY2pSS9KP6HBRTdGJaXvHcPaz3BJ023tdS1bTlr8Vd6Gw9KIl8q8ckmcY5fQG
|
||||
BO+QueQA5N06tRn/Arr0PO7gi+s3i+z016zy9vA9r911kTMZHRxAy3QkGSGT2RT+
|
||||
rCpSx4/VBEnkjWNHiDxpg8v+R70rfk/Fla4OndTRQ8Bnc+MUCH7lP59zuDMKz10/
|
||||
NIeWiu5T6CUVAgMBAAGjgbIwga8wDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8E
|
||||
BAMCAQYwbQYIKwYBBQUHAQwEYTBfoV2gWzBZMFcwVRYJaW1hZ2UvZ2lmMCEwHzAH
|
||||
BgUrDgMCGgQUj+XTGoasjY5rw8+AatRIGCx7GS4wJRYjaHR0cDovL2xvZ28udmVy
|
||||
aXNpZ24uY29tL3ZzbG9nby5naWYwHQYDVR0OBBYEFH/TZafC3ey78DAJ80M5+gKv
|
||||
MzEzMA0GCSqGSIb3DQEBBQUAA4IBAQCTJEowX2LP2BqYLz3q3JktvXf2pXkiOOzE
|
||||
p6B4Eq1iDkVwZMXnl2YtmAl+X6/WzChl8gGqCBpH3vn5fJJaCGkgDdk+bW48DW7Y
|
||||
5gaRQBi5+MHt39tBquCWIMnNZBU4gcmU7qKEKQsTb47bDN0lAtukixlE0kF6BWlK
|
||||
WE9gyn6CagsCqiUXObXbf+eEZSqVir2G3l6BFoMtEMze/aiCKm0oHw0LxOXnGiYZ
|
||||
4fQRbxC1lfznQgUy286dUV4otp6F01vvpX1FQHKOtw5rDgb7MzVIcbidJ4vEZV8N
|
||||
hnacRHr2lVz2XTIIM6RUthg/aFzyQkqFOFSDX9HoLPKsEdao7WNq
|
||||
-----END CERTIFICATE-----
|
||||
|
||||
Entrust.net Secure Server Certification Authority
|
||||
=================================================
|
||||
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIE2DCCBEGgAwIBAgIEN0rSQzANBgkqhkiG9w0BAQUFADCBwzELMAkGA1UEBhMC
|
||||
VVMxFDASBgNVBAoTC0VudHJ1c3QubmV0MTswOQYDVQQLEzJ3d3cuZW50cnVzdC5u
|
||||
ZXQvQ1BTIGluY29ycC4gYnkgcmVmLiAobGltaXRzIGxpYWIuKTElMCMGA1UECxMc
|
||||
KGMpIDE5OTkgRW50cnVzdC5uZXQgTGltaXRlZDE6MDgGA1UEAxMxRW50cnVzdC5u
|
||||
ZXQgU2VjdXJlIFNlcnZlciBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw05OTA1
|
||||
MjUxNjA5NDBaFw0xOTA1MjUxNjM5NDBaMIHDMQswCQYDVQQGEwJVUzEUMBIGA1UE
|
||||
ChMLRW50cnVzdC5uZXQxOzA5BgNVBAsTMnd3dy5lbnRydXN0Lm5ldC9DUFMgaW5j
|
||||
b3JwLiBieSByZWYuIChsaW1pdHMgbGlhYi4pMSUwIwYDVQQLExwoYykgMTk5OSBF
|
||||
bnRydXN0Lm5ldCBMaW1pdGVkMTowOAYDVQQDEzFFbnRydXN0Lm5ldCBTZWN1cmUg
|
||||
U2VydmVyIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIGdMA0GCSqGSIb3DQEBAQUA
|
||||
A4GLADCBhwKBgQDNKIM0VBuJ8w+vN5Ex/68xYMmo6LIQaO2f55M28Qpku0f1BBc/
|
||||
I0dNxScZgSYMVHINiC3ZH5oSn7yzcdOAGT9HZnuMNSjSuQrfJNqc1lB5gXpa0zf3
|
||||
wkrYKZImZNHkmGw6AIr1NJtl+O3jEP/9uElY3KDegjlrgbEWGWG5VLbmQwIBA6OC
|
||||
AdcwggHTMBEGCWCGSAGG+EIBAQQEAwIABzCCARkGA1UdHwSCARAwggEMMIHeoIHb
|
||||
oIHYpIHVMIHSMQswCQYDVQQGEwJVUzEUMBIGA1UEChMLRW50cnVzdC5uZXQxOzA5
|
||||
BgNVBAsTMnd3dy5lbnRydXN0Lm5ldC9DUFMgaW5jb3JwLiBieSByZWYuIChsaW1p
|
||||
dHMgbGlhYi4pMSUwIwYDVQQLExwoYykgMTk5OSBFbnRydXN0Lm5ldCBMaW1pdGVk
|
||||
MTowOAYDVQQDEzFFbnRydXN0Lm5ldCBTZWN1cmUgU2VydmVyIENlcnRpZmljYXRp
|
||||
b24gQXV0aG9yaXR5MQ0wCwYDVQQDEwRDUkwxMCmgJ6AlhiNodHRwOi8vd3d3LmVu
|
||||
dHJ1c3QubmV0L0NSTC9uZXQxLmNybDArBgNVHRAEJDAigA8xOTk5MDUyNTE2MDk0
|
||||
MFqBDzIwMTkwNTI1MTYwOTQwWjALBgNVHQ8EBAMCAQYwHwYDVR0jBBgwFoAU8Bdi
|
||||
E1U9s/8KAGv7UISX8+1i0BowHQYDVR0OBBYEFPAXYhNVPbP/CgBr+1CEl/PtYtAa
|
||||
MAwGA1UdEwQFMAMBAf8wGQYJKoZIhvZ9B0EABAwwChsEVjQuMAMCBJAwDQYJKoZI
|
||||
hvcNAQEFBQADgYEAkNwwAvpkdMKnCqV8IY00F6j7Rw7/JXyNEwr75Ji174z4xRAN
|
||||
95K+8cPV1ZVqBLssziY2ZcgxxufuP+NXdYR6Ee9GTxj005i7qIcyunL2POI9n9cd
|
||||
2cNgQ4xYDiKWL2KjLB+6rQXvqzJ4h6BUcxm1XAX5Uj5tLUUL9wqT6u0G+bI=
|
||||
-----END CERTIFICATE-----
|
||||
|
||||
Go Daddy Certification Authority Root Certificate Bundle
|
||||
========================================================
|
||||
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIE3jCCA8agAwIBAgICAwEwDQYJKoZIhvcNAQEFBQAwYzELMAkGA1UEBhMCVVMx
|
||||
ITAfBgNVBAoTGFRoZSBHbyBEYWRkeSBHcm91cCwgSW5jLjExMC8GA1UECxMoR28g
|
||||
RGFkZHkgQ2xhc3MgMiBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0wNjExMTYw
|
||||
MTU0MzdaFw0yNjExMTYwMTU0MzdaMIHKMQswCQYDVQQGEwJVUzEQMA4GA1UECBMH
|
||||
QXJpem9uYTETMBEGA1UEBxMKU2NvdHRzZGFsZTEaMBgGA1UEChMRR29EYWRkeS5j
|
||||
b20sIEluYy4xMzAxBgNVBAsTKmh0dHA6Ly9jZXJ0aWZpY2F0ZXMuZ29kYWRkeS5j
|
||||
b20vcmVwb3NpdG9yeTEwMC4GA1UEAxMnR28gRGFkZHkgU2VjdXJlIENlcnRpZmlj
|
||||
YXRpb24gQXV0aG9yaXR5MREwDwYDVQQFEwgwNzk2OTI4NzCCASIwDQYJKoZIhvcN
|
||||
AQEBBQADggEPADCCAQoCggEBAMQt1RWMnCZM7DI161+4WQFapmGBWTtwY6vj3D3H
|
||||
KrjJM9N55DrtPDAjhI6zMBS2sofDPZVUBJ7fmd0LJR4h3mUpfjWoqVTr9vcyOdQm
|
||||
VZWt7/v+WIbXnvQAjYwqDL1CBM6nPwT27oDyqu9SoWlm2r4arV3aLGbqGmu75RpR
|
||||
SgAvSMeYddi5Kcju+GZtCpyz8/x4fKL4o/K1w/O5epHBp+YlLpyo7RJlbmr2EkRT
|
||||
cDCVw5wrWCs9CHRK8r5RsL+H0EwnWGu1NcWdrxcx+AuP7q2BNgWJCJjPOq8lh8BJ
|
||||
6qf9Z/dFjpfMFDniNoW1fho3/Rb2cRGadDAW/hOUoz+EDU8CAwEAAaOCATIwggEu
|
||||
MB0GA1UdDgQWBBT9rGEyk2xF1uLuhV+auud2mWjM5zAfBgNVHSMEGDAWgBTSxLDS
|
||||
kdRMEXGzYcs9of7dqGrU4zASBgNVHRMBAf8ECDAGAQH/AgEAMDMGCCsGAQUFBwEB
|
||||
BCcwJTAjBggrBgEFBQcwAYYXaHR0cDovL29jc3AuZ29kYWRkeS5jb20wRgYDVR0f
|
||||
BD8wPTA7oDmgN4Y1aHR0cDovL2NlcnRpZmljYXRlcy5nb2RhZGR5LmNvbS9yZXBv
|
||||
c2l0b3J5L2dkcm9vdC5jcmwwSwYDVR0gBEQwQjBABgRVHSAAMDgwNgYIKwYBBQUH
|
||||
AgEWKmh0dHA6Ly9jZXJ0aWZpY2F0ZXMuZ29kYWRkeS5jb20vcmVwb3NpdG9yeTAO
|
||||
BgNVHQ8BAf8EBAMCAQYwDQYJKoZIhvcNAQEFBQADggEBANKGwOy9+aG2Z+5mC6IG
|
||||
OgRQjhVyrEp0lVPLN8tESe8HkGsz2ZbwlFalEzAFPIUyIXvJxwqoJKSQ3kbTJSMU
|
||||
A2fCENZvD117esyfxVgqwcSeIaha86ykRvOe5GPLL5CkKSkB2XIsKd83ASe8T+5o
|
||||
0yGPwLPk9Qnt0hCqU7S+8MxZC9Y7lhyVJEnfzuz9p0iRFEUOOjZv2kWzRaJBydTX
|
||||
RE4+uXR21aITVSzGh6O1mawGhId/dQb8vxRMDsxuxN89txJx9OjxUUAiKEngHUuH
|
||||
qDTMBqLdElrRhjZkAzVvb3du6/KFUJheqwNTrZEjYx8WnM25sgVjOuH0aBsXBTWV
|
||||
U+4=
|
||||
-----END CERTIFICATE-----
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIE+zCCBGSgAwIBAgICAQ0wDQYJKoZIhvcNAQEFBQAwgbsxJDAiBgNVBAcTG1Zh
|
||||
bGlDZXJ0IFZhbGlkYXRpb24gTmV0d29yazEXMBUGA1UEChMOVmFsaUNlcnQsIElu
|
||||
Yy4xNTAzBgNVBAsTLFZhbGlDZXJ0IENsYXNzIDIgUG9saWN5IFZhbGlkYXRpb24g
|
||||
QXV0aG9yaXR5MSEwHwYDVQQDExhodHRwOi8vd3d3LnZhbGljZXJ0LmNvbS8xIDAe
|
||||
BgkqhkiG9w0BCQEWEWluZm9AdmFsaWNlcnQuY29tMB4XDTA0MDYyOTE3MDYyMFoX
|
||||
DTI0MDYyOTE3MDYyMFowYzELMAkGA1UEBhMCVVMxITAfBgNVBAoTGFRoZSBHbyBE
|
||||
YWRkeSBHcm91cCwgSW5jLjExMC8GA1UECxMoR28gRGFkZHkgQ2xhc3MgMiBDZXJ0
|
||||
aWZpY2F0aW9uIEF1dGhvcml0eTCCASAwDQYJKoZIhvcNAQEBBQADggENADCCAQgC
|
||||
ggEBAN6d1+pXGEmhW+vXX0iG6r7d/+TvZxz0ZWizV3GgXne77ZtJ6XCAPVYYYwhv
|
||||
2vLM0D9/AlQiVBDYsoHUwHU9S3/Hd8M+eKsaA7Ugay9qK7HFiH7Eux6wwdhFJ2+q
|
||||
N1j3hybX2C32qRe3H3I2TqYXP2WYktsqbl2i/ojgC95/5Y0V4evLOtXiEqITLdiO
|
||||
r18SPaAIBQi2XKVlOARFmR6jYGB0xUGlcmIbYsUfb18aQr4CUWWoriMYavx4A6lN
|
||||
f4DD+qta/KFApMoZFv6yyO9ecw3ud72a9nmYvLEHZ6IVDd2gWMZEewo+YihfukEH
|
||||
U1jPEX44dMX4/7VpkI+EdOqXG68CAQOjggHhMIIB3TAdBgNVHQ4EFgQU0sSw0pHU
|
||||
TBFxs2HLPaH+3ahq1OMwgdIGA1UdIwSByjCBx6GBwaSBvjCBuzEkMCIGA1UEBxMb
|
||||
VmFsaUNlcnQgVmFsaWRhdGlvbiBOZXR3b3JrMRcwFQYDVQQKEw5WYWxpQ2VydCwg
|
||||
SW5jLjE1MDMGA1UECxMsVmFsaUNlcnQgQ2xhc3MgMiBQb2xpY3kgVmFsaWRhdGlv
|
||||
biBBdXRob3JpdHkxITAfBgNVBAMTGGh0dHA6Ly93d3cudmFsaWNlcnQuY29tLzEg
|
||||
MB4GCSqGSIb3DQEJARYRaW5mb0B2YWxpY2VydC5jb22CAQEwDwYDVR0TAQH/BAUw
|
||||
AwEB/zAzBggrBgEFBQcBAQQnMCUwIwYIKwYBBQUHMAGGF2h0dHA6Ly9vY3NwLmdv
|
||||
ZGFkZHkuY29tMEQGA1UdHwQ9MDswOaA3oDWGM2h0dHA6Ly9jZXJ0aWZpY2F0ZXMu
|
||||
Z29kYWRkeS5jb20vcmVwb3NpdG9yeS9yb290LmNybDBLBgNVHSAERDBCMEAGBFUd
|
||||
IAAwODA2BggrBgEFBQcCARYqaHR0cDovL2NlcnRpZmljYXRlcy5nb2RhZGR5LmNv
|
||||
bS9yZXBvc2l0b3J5MA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQUFAAOBgQC1
|
||||
QPmnHfbq/qQaQlpE9xXUhUaJwL6e4+PrxeNYiY+Sn1eocSxI0YGyeR+sBjUZsE4O
|
||||
WBsUs5iB0QQeyAfJg594RAoYC5jcdnplDQ1tgMQLARzLrUc+cb53S8wGd9D0Vmsf
|
||||
SxOaFIqII6hR8INMqzW/Rn453HWkrugp++85j09VZw==
|
||||
-----END CERTIFICATE-----
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIC5zCCAlACAQEwDQYJKoZIhvcNAQEFBQAwgbsxJDAiBgNVBAcTG1ZhbGlDZXJ0
|
||||
IFZhbGlkYXRpb24gTmV0d29yazEXMBUGA1UEChMOVmFsaUNlcnQsIEluYy4xNTAz
|
||||
BgNVBAsTLFZhbGlDZXJ0IENsYXNzIDIgUG9saWN5IFZhbGlkYXRpb24gQXV0aG9y
|
||||
aXR5MSEwHwYDVQQDExhodHRwOi8vd3d3LnZhbGljZXJ0LmNvbS8xIDAeBgkqhkiG
|
||||
9w0BCQEWEWluZm9AdmFsaWNlcnQuY29tMB4XDTk5MDYyNjAwMTk1NFoXDTE5MDYy
|
||||
NjAwMTk1NFowgbsxJDAiBgNVBAcTG1ZhbGlDZXJ0IFZhbGlkYXRpb24gTmV0d29y
|
||||
azEXMBUGA1UEChMOVmFsaUNlcnQsIEluYy4xNTAzBgNVBAsTLFZhbGlDZXJ0IENs
|
||||
YXNzIDIgUG9saWN5IFZhbGlkYXRpb24gQXV0aG9yaXR5MSEwHwYDVQQDExhodHRw
|
||||
Oi8vd3d3LnZhbGljZXJ0LmNvbS8xIDAeBgkqhkiG9w0BCQEWEWluZm9AdmFsaWNl
|
||||
cnQuY29tMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDOOnHK5avIWZJV16vY
|
||||
dA757tn2VUdZZUcOBVXc65g2PFxTXdMwzzjsvUGJ7SVCCSRrCl6zfN1SLUzm1NZ9
|
||||
WlmpZdRJEy0kTRxQb7XBhVQ7/nHk01xC+YDgkRoKWzk2Z/M/VXwbP7RfZHM047QS
|
||||
v4dk+NoS/zcnwbNDu+97bi5p9wIDAQABMA0GCSqGSIb3DQEBBQUAA4GBADt/UG9v
|
||||
UJSZSWI4OB9L+KXIPqeCgfYrx+jFzug6EILLGACOTb2oWH+heQC1u+mNr0HZDzTu
|
||||
IYEZoDJJKPTEjlbVUjP9UNV+mWwD5MlM/Mtsq2azSiGM5bUMMj4QssxsodyamEwC
|
||||
W/POuZ6lcg5Ktz885hZo+L7tdEy8W9ViH0Pd
|
||||
-----END CERTIFICATE-----
|
||||
|
||||
GeoTrust Global CA
|
||||
==================
|
||||
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIDfTCCAuagAwIBAgIDErvmMA0GCSqGSIb3DQEBBQUAME4xCzAJBgNVBAYTAlVT
|
||||
MRAwDgYDVQQKEwdFcXVpZmF4MS0wKwYDVQQLEyRFcXVpZmF4IFNlY3VyZSBDZXJ0
|
||||
aWZpY2F0ZSBBdXRob3JpdHkwHhcNMDIwNTIxMDQwMDAwWhcNMTgwODIxMDQwMDAw
|
||||
WjBCMQswCQYDVQQGEwJVUzEWMBQGA1UEChMNR2VvVHJ1c3QgSW5jLjEbMBkGA1UE
|
||||
AxMSR2VvVHJ1c3QgR2xvYmFsIENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB
|
||||
CgKCAQEA2swYYzD99BcjGlZ+W988bDjkcbd4kdS8odhM+KhDtgPpTSEHCIjaWC9m
|
||||
OSm9BXiLnTjoBbdqfnGk5sRgprDvgOSJKA+eJdbtg/OtppHHmMlCGDUUna2YRpIu
|
||||
T8rxh0PBFpVXLVDviS2Aelet8u5fa9IAjbkU+BQVNdnARqN7csiRv8lVK83Qlz6c
|
||||
JmTM386DGXHKTubU1XupGc1V3sjs0l44U+VcT4wt/lAjNvxm5suOpDkZALeVAjmR
|
||||
Cw7+OC7RHQWa9k0+bw8HHa8sHo9gOeL6NlMTOdReJivbPagUvTLrGAMoUgRx5asz
|
||||
PeE4uwc2hGKceeoWMPRfwCvocWvk+QIDAQABo4HwMIHtMB8GA1UdIwQYMBaAFEjm
|
||||
aPkr0rKV10fYIyAQTzOYkJ/UMB0GA1UdDgQWBBTAephojYn7qwVkDBF9qn1luMrM
|
||||
TjAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjA6BgNVHR8EMzAxMC+g
|
||||
LaArhilodHRwOi8vY3JsLmdlb3RydXN0LmNvbS9jcmxzL3NlY3VyZWNhLmNybDBO
|
||||
BgNVHSAERzBFMEMGBFUdIAAwOzA5BggrBgEFBQcCARYtaHR0cHM6Ly93d3cuZ2Vv
|
||||
dHJ1c3QuY29tL3Jlc291cmNlcy9yZXBvc2l0b3J5MA0GCSqGSIb3DQEBBQUAA4GB
|
||||
AHbhEm5OSxYShjAGsoEIz/AIx8dxfmbuwu3UOx//8PDITtZDOLC5MH0Y0FWDomrL
|
||||
NhGc6Ehmo21/uBPUR/6LWlxz/K7ZGzIZOKuXNBSqltLroxwUCEm2u+WR74M26x1W
|
||||
b8ravHNjkOR/ez4iyz0H7V84dJzjA1BOoa+Y7mHyhD8S
|
||||
-----END CERTIFICATE-----
|
||||
|
|
@ -0,0 +1,110 @@
|
|||
"""
|
||||
iri2uri
|
||||
|
||||
Converts an IRI to a URI.
|
||||
|
||||
"""
|
||||
__author__ = "Joe Gregorio (joe@bitworking.org)"
|
||||
__copyright__ = "Copyright 2006, Joe Gregorio"
|
||||
__contributors__ = []
|
||||
__version__ = "1.0.0"
|
||||
__license__ = "MIT"
|
||||
__history__ = """
|
||||
"""
|
||||
|
||||
import urlparse
|
||||
|
||||
|
||||
# Convert an IRI to a URI following the rules in RFC 3987
|
||||
#
|
||||
# The characters we need to enocde and escape are defined in the spec:
|
||||
#
|
||||
# iprivate = %xE000-F8FF / %xF0000-FFFFD / %x100000-10FFFD
|
||||
# ucschar = %xA0-D7FF / %xF900-FDCF / %xFDF0-FFEF
|
||||
# / %x10000-1FFFD / %x20000-2FFFD / %x30000-3FFFD
|
||||
# / %x40000-4FFFD / %x50000-5FFFD / %x60000-6FFFD
|
||||
# / %x70000-7FFFD / %x80000-8FFFD / %x90000-9FFFD
|
||||
# / %xA0000-AFFFD / %xB0000-BFFFD / %xC0000-CFFFD
|
||||
# / %xD0000-DFFFD / %xE1000-EFFFD
|
||||
|
||||
escape_range = [
|
||||
(0xA0, 0xD7FF),
|
||||
(0xE000, 0xF8FF),
|
||||
(0xF900, 0xFDCF),
|
||||
(0xFDF0, 0xFFEF),
|
||||
(0x10000, 0x1FFFD),
|
||||
(0x20000, 0x2FFFD),
|
||||
(0x30000, 0x3FFFD),
|
||||
(0x40000, 0x4FFFD),
|
||||
(0x50000, 0x5FFFD),
|
||||
(0x60000, 0x6FFFD),
|
||||
(0x70000, 0x7FFFD),
|
||||
(0x80000, 0x8FFFD),
|
||||
(0x90000, 0x9FFFD),
|
||||
(0xA0000, 0xAFFFD),
|
||||
(0xB0000, 0xBFFFD),
|
||||
(0xC0000, 0xCFFFD),
|
||||
(0xD0000, 0xDFFFD),
|
||||
(0xE1000, 0xEFFFD),
|
||||
(0xF0000, 0xFFFFD),
|
||||
(0x100000, 0x10FFFD),
|
||||
]
|
||||
|
||||
def encode(c):
|
||||
retval = c
|
||||
i = ord(c)
|
||||
for low, high in escape_range:
|
||||
if i < low:
|
||||
break
|
||||
if i >= low and i <= high:
|
||||
retval = "".join(["%%%2X" % ord(o) for o in c.encode('utf-8')])
|
||||
break
|
||||
return retval
|
||||
|
||||
|
||||
def iri2uri(uri):
|
||||
"""Convert an IRI to a URI. Note that IRIs must be
|
||||
passed in a unicode strings. That is, do not utf-8 encode
|
||||
the IRI before passing it into the function."""
|
||||
if isinstance(uri ,unicode):
|
||||
(scheme, authority, path, query, fragment) = urlparse.urlsplit(uri)
|
||||
authority = authority.encode('idna')
|
||||
# For each character in 'ucschar' or 'iprivate'
|
||||
# 1. encode as utf-8
|
||||
# 2. then %-encode each octet of that utf-8
|
||||
uri = urlparse.urlunsplit((scheme, authority, path, query, fragment))
|
||||
uri = "".join([encode(c) for c in uri])
|
||||
return uri
|
||||
|
||||
if __name__ == "__main__":
|
||||
import unittest
|
||||
|
||||
class Test(unittest.TestCase):
|
||||
|
||||
def test_uris(self):
|
||||
"""Test that URIs are invariant under the transformation."""
|
||||
invariant = [
|
||||
u"ftp://ftp.is.co.za/rfc/rfc1808.txt",
|
||||
u"http://www.ietf.org/rfc/rfc2396.txt",
|
||||
u"ldap://[2001:db8::7]/c=GB?objectClass?one",
|
||||
u"mailto:John.Doe@example.com",
|
||||
u"news:comp.infosystems.www.servers.unix",
|
||||
u"tel:+1-816-555-1212",
|
||||
u"telnet://192.0.2.16:80/",
|
||||
u"urn:oasis:names:specification:docbook:dtd:xml:4.1.2" ]
|
||||
for uri in invariant:
|
||||
self.assertEqual(uri, iri2uri(uri))
|
||||
|
||||
def test_iri(self):
|
||||
""" Test that the right type of escaping is done for each part of the URI."""
|
||||
self.assertEqual("http://xn--o3h.com/%E2%98%84", iri2uri(u"http://\N{COMET}.com/\N{COMET}"))
|
||||
self.assertEqual("http://bitworking.org/?fred=%E2%98%84", iri2uri(u"http://bitworking.org/?fred=\N{COMET}"))
|
||||
self.assertEqual("http://bitworking.org/#%E2%98%84", iri2uri(u"http://bitworking.org/#\N{COMET}"))
|
||||
self.assertEqual("#%E2%98%84", iri2uri(u"#\N{COMET}"))
|
||||
self.assertEqual("/fred?bar=%E2%98%9A#%E2%98%84", iri2uri(u"/fred?bar=\N{BLACK LEFT POINTING INDEX}#\N{COMET}"))
|
||||
self.assertEqual("/fred?bar=%E2%98%9A#%E2%98%84", iri2uri(iri2uri(u"/fred?bar=\N{BLACK LEFT POINTING INDEX}#\N{COMET}")))
|
||||
self.assertNotEqual("/fred?bar=%E2%98%9A#%E2%98%84", iri2uri(u"/fred?bar=\N{BLACK LEFT POINTING INDEX}#\N{COMET}".encode('utf-8')))
|
||||
|
||||
unittest.main()
|
||||
|
||||
|
Двоичный файл не отображается.
|
@ -0,0 +1,438 @@
|
|||
"""SocksiPy - Python SOCKS module.
|
||||
Version 1.00
|
||||
|
||||
Copyright 2006 Dan-Haim. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification,
|
||||
are permitted provided that the following conditions are met:
|
||||
1. Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
3. Neither the name of Dan Haim nor the names of his contributors may be used
|
||||
to endorse or promote products derived from this software without specific
|
||||
prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY DAN HAIM "AS IS" AND ANY EXPRESS OR IMPLIED
|
||||
WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
|
||||
EVENT SHALL DAN HAIM OR HIS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA
|
||||
OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
|
||||
OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMANGE.
|
||||
|
||||
|
||||
This module provides a standard socket-like interface for Python
|
||||
for tunneling connections through SOCKS proxies.
|
||||
|
||||
"""
|
||||
|
||||
"""
|
||||
|
||||
Minor modifications made by Christopher Gilbert (http://motomastyle.com/)
|
||||
for use in PyLoris (http://pyloris.sourceforge.net/)
|
||||
|
||||
Minor modifications made by Mario Vilas (http://breakingcode.wordpress.com/)
|
||||
mainly to merge bug fixes found in Sourceforge
|
||||
|
||||
"""
|
||||
|
||||
import base64
|
||||
import socket
|
||||
import struct
|
||||
import sys
|
||||
|
||||
if getattr(socket, 'socket', None) is None:
|
||||
raise ImportError('socket.socket missing, proxy support unusable')
|
||||
|
||||
PROXY_TYPE_SOCKS4 = 1
|
||||
PROXY_TYPE_SOCKS5 = 2
|
||||
PROXY_TYPE_HTTP = 3
|
||||
PROXY_TYPE_HTTP_NO_TUNNEL = 4
|
||||
|
||||
_defaultproxy = None
|
||||
_orgsocket = socket.socket
|
||||
|
||||
class ProxyError(Exception): pass
|
||||
class GeneralProxyError(ProxyError): pass
|
||||
class Socks5AuthError(ProxyError): pass
|
||||
class Socks5Error(ProxyError): pass
|
||||
class Socks4Error(ProxyError): pass
|
||||
class HTTPError(ProxyError): pass
|
||||
|
||||
_generalerrors = ("success",
|
||||
"invalid data",
|
||||
"not connected",
|
||||
"not available",
|
||||
"bad proxy type",
|
||||
"bad input")
|
||||
|
||||
_socks5errors = ("succeeded",
|
||||
"general SOCKS server failure",
|
||||
"connection not allowed by ruleset",
|
||||
"Network unreachable",
|
||||
"Host unreachable",
|
||||
"Connection refused",
|
||||
"TTL expired",
|
||||
"Command not supported",
|
||||
"Address type not supported",
|
||||
"Unknown error")
|
||||
|
||||
_socks5autherrors = ("succeeded",
|
||||
"authentication is required",
|
||||
"all offered authentication methods were rejected",
|
||||
"unknown username or invalid password",
|
||||
"unknown error")
|
||||
|
||||
_socks4errors = ("request granted",
|
||||
"request rejected or failed",
|
||||
"request rejected because SOCKS server cannot connect to identd on the client",
|
||||
"request rejected because the client program and identd report different user-ids",
|
||||
"unknown error")
|
||||
|
||||
def setdefaultproxy(proxytype=None, addr=None, port=None, rdns=True, username=None, password=None):
|
||||
"""setdefaultproxy(proxytype, addr[, port[, rdns[, username[, password]]]])
|
||||
Sets a default proxy which all further socksocket objects will use,
|
||||
unless explicitly changed.
|
||||
"""
|
||||
global _defaultproxy
|
||||
_defaultproxy = (proxytype, addr, port, rdns, username, password)
|
||||
|
||||
def wrapmodule(module):
|
||||
"""wrapmodule(module)
|
||||
Attempts to replace a module's socket library with a SOCKS socket. Must set
|
||||
a default proxy using setdefaultproxy(...) first.
|
||||
This will only work on modules that import socket directly into the namespace;
|
||||
most of the Python Standard Library falls into this category.
|
||||
"""
|
||||
if _defaultproxy != None:
|
||||
module.socket.socket = socksocket
|
||||
else:
|
||||
raise GeneralProxyError((4, "no proxy specified"))
|
||||
|
||||
class socksocket(socket.socket):
|
||||
"""socksocket([family[, type[, proto]]]) -> socket object
|
||||
Open a SOCKS enabled socket. The parameters are the same as
|
||||
those of the standard socket init. In order for SOCKS to work,
|
||||
you must specify family=AF_INET, type=SOCK_STREAM and proto=0.
|
||||
"""
|
||||
|
||||
def __init__(self, family=socket.AF_INET, type=socket.SOCK_STREAM, proto=0, _sock=None):
|
||||
_orgsocket.__init__(self, family, type, proto, _sock)
|
||||
if _defaultproxy != None:
|
||||
self.__proxy = _defaultproxy
|
||||
else:
|
||||
self.__proxy = (None, None, None, None, None, None)
|
||||
self.__proxysockname = None
|
||||
self.__proxypeername = None
|
||||
self.__httptunnel = True
|
||||
|
||||
def __recvall(self, count):
|
||||
"""__recvall(count) -> data
|
||||
Receive EXACTLY the number of bytes requested from the socket.
|
||||
Blocks until the required number of bytes have been received.
|
||||
"""
|
||||
data = self.recv(count)
|
||||
while len(data) < count:
|
||||
d = self.recv(count-len(data))
|
||||
if not d: raise GeneralProxyError((0, "connection closed unexpectedly"))
|
||||
data = data + d
|
||||
return data
|
||||
|
||||
def sendall(self, content, *args):
|
||||
""" override socket.socket.sendall method to rewrite the header
|
||||
for non-tunneling proxies if needed
|
||||
"""
|
||||
if not self.__httptunnel:
|
||||
content = self.__rewriteproxy(content)
|
||||
return super(socksocket, self).sendall(content, *args)
|
||||
|
||||
def __rewriteproxy(self, header):
|
||||
""" rewrite HTTP request headers to support non-tunneling proxies
|
||||
(i.e. those which do not support the CONNECT method).
|
||||
This only works for HTTP (not HTTPS) since HTTPS requires tunneling.
|
||||
"""
|
||||
host, endpt = None, None
|
||||
hdrs = header.split("\r\n")
|
||||
for hdr in hdrs:
|
||||
if hdr.lower().startswith("host:"):
|
||||
host = hdr
|
||||
elif hdr.lower().startswith("get") or hdr.lower().startswith("post"):
|
||||
endpt = hdr
|
||||
if host and endpt:
|
||||
hdrs.remove(host)
|
||||
hdrs.remove(endpt)
|
||||
host = host.split(" ")[1]
|
||||
endpt = endpt.split(" ")
|
||||
if (self.__proxy[4] != None and self.__proxy[5] != None):
|
||||
hdrs.insert(0, self.__getauthheader())
|
||||
hdrs.insert(0, "Host: %s" % host)
|
||||
hdrs.insert(0, "%s http://%s%s %s" % (endpt[0], host, endpt[1], endpt[2]))
|
||||
return "\r\n".join(hdrs)
|
||||
|
||||
def __getauthheader(self):
|
||||
auth = self.__proxy[4] + ":" + self.__proxy[5]
|
||||
return "Proxy-Authorization: Basic " + base64.b64encode(auth)
|
||||
|
||||
def setproxy(self, proxytype=None, addr=None, port=None, rdns=True, username=None, password=None):
|
||||
"""setproxy(proxytype, addr[, port[, rdns[, username[, password]]]])
|
||||
Sets the proxy to be used.
|
||||
proxytype - The type of the proxy to be used. Three types
|
||||
are supported: PROXY_TYPE_SOCKS4 (including socks4a),
|
||||
PROXY_TYPE_SOCKS5 and PROXY_TYPE_HTTP
|
||||
addr - The address of the server (IP or DNS).
|
||||
port - The port of the server. Defaults to 1080 for SOCKS
|
||||
servers and 8080 for HTTP proxy servers.
|
||||
rdns - Should DNS queries be preformed on the remote side
|
||||
(rather than the local side). The default is True.
|
||||
Note: This has no effect with SOCKS4 servers.
|
||||
username - Username to authenticate with to the server.
|
||||
The default is no authentication.
|
||||
password - Password to authenticate with to the server.
|
||||
Only relevant when username is also provided.
|
||||
"""
|
||||
self.__proxy = (proxytype, addr, port, rdns, username, password)
|
||||
|
||||
def __negotiatesocks5(self, destaddr, destport):
|
||||
"""__negotiatesocks5(self,destaddr,destport)
|
||||
Negotiates a connection through a SOCKS5 server.
|
||||
"""
|
||||
# First we'll send the authentication packages we support.
|
||||
if (self.__proxy[4]!=None) and (self.__proxy[5]!=None):
|
||||
# The username/password details were supplied to the
|
||||
# setproxy method so we support the USERNAME/PASSWORD
|
||||
# authentication (in addition to the standard none).
|
||||
self.sendall(struct.pack('BBBB', 0x05, 0x02, 0x00, 0x02))
|
||||
else:
|
||||
# No username/password were entered, therefore we
|
||||
# only support connections with no authentication.
|
||||
self.sendall(struct.pack('BBB', 0x05, 0x01, 0x00))
|
||||
# We'll receive the server's response to determine which
|
||||
# method was selected
|
||||
chosenauth = self.__recvall(2)
|
||||
if chosenauth[0:1] != chr(0x05).encode():
|
||||
self.close()
|
||||
raise GeneralProxyError((1, _generalerrors[1]))
|
||||
# Check the chosen authentication method
|
||||
if chosenauth[1:2] == chr(0x00).encode():
|
||||
# No authentication is required
|
||||
pass
|
||||
elif chosenauth[1:2] == chr(0x02).encode():
|
||||
# Okay, we need to perform a basic username/password
|
||||
# authentication.
|
||||
self.sendall(chr(0x01).encode() + chr(len(self.__proxy[4])) + self.__proxy[4] + chr(len(self.__proxy[5])) + self.__proxy[5])
|
||||
authstat = self.__recvall(2)
|
||||
if authstat[0:1] != chr(0x01).encode():
|
||||
# Bad response
|
||||
self.close()
|
||||
raise GeneralProxyError((1, _generalerrors[1]))
|
||||
if authstat[1:2] != chr(0x00).encode():
|
||||
# Authentication failed
|
||||
self.close()
|
||||
raise Socks5AuthError((3, _socks5autherrors[3]))
|
||||
# Authentication succeeded
|
||||
else:
|
||||
# Reaching here is always bad
|
||||
self.close()
|
||||
if chosenauth[1] == chr(0xFF).encode():
|
||||
raise Socks5AuthError((2, _socks5autherrors[2]))
|
||||
else:
|
||||
raise GeneralProxyError((1, _generalerrors[1]))
|
||||
# Now we can request the actual connection
|
||||
req = struct.pack('BBB', 0x05, 0x01, 0x00)
|
||||
# If the given destination address is an IP address, we'll
|
||||
# use the IPv4 address request even if remote resolving was specified.
|
||||
try:
|
||||
ipaddr = socket.inet_aton(destaddr)
|
||||
req = req + chr(0x01).encode() + ipaddr
|
||||
except socket.error:
|
||||
# Well it's not an IP number, so it's probably a DNS name.
|
||||
if self.__proxy[3]:
|
||||
# Resolve remotely
|
||||
ipaddr = None
|
||||
req = req + chr(0x03).encode() + chr(len(destaddr)).encode() + destaddr
|
||||
else:
|
||||
# Resolve locally
|
||||
ipaddr = socket.inet_aton(socket.gethostbyname(destaddr))
|
||||
req = req + chr(0x01).encode() + ipaddr
|
||||
req = req + struct.pack(">H", destport)
|
||||
self.sendall(req)
|
||||
# Get the response
|
||||
resp = self.__recvall(4)
|
||||
if resp[0:1] != chr(0x05).encode():
|
||||
self.close()
|
||||
raise GeneralProxyError((1, _generalerrors[1]))
|
||||
elif resp[1:2] != chr(0x00).encode():
|
||||
# Connection failed
|
||||
self.close()
|
||||
if ord(resp[1:2])<=8:
|
||||
raise Socks5Error((ord(resp[1:2]), _socks5errors[ord(resp[1:2])]))
|
||||
else:
|
||||
raise Socks5Error((9, _socks5errors[9]))
|
||||
# Get the bound address/port
|
||||
elif resp[3:4] == chr(0x01).encode():
|
||||
boundaddr = self.__recvall(4)
|
||||
elif resp[3:4] == chr(0x03).encode():
|
||||
resp = resp + self.recv(1)
|
||||
boundaddr = self.__recvall(ord(resp[4:5]))
|
||||
else:
|
||||
self.close()
|
||||
raise GeneralProxyError((1,_generalerrors[1]))
|
||||
boundport = struct.unpack(">H", self.__recvall(2))[0]
|
||||
self.__proxysockname = (boundaddr, boundport)
|
||||
if ipaddr != None:
|
||||
self.__proxypeername = (socket.inet_ntoa(ipaddr), destport)
|
||||
else:
|
||||
self.__proxypeername = (destaddr, destport)
|
||||
|
||||
def getproxysockname(self):
|
||||
"""getsockname() -> address info
|
||||
Returns the bound IP address and port number at the proxy.
|
||||
"""
|
||||
return self.__proxysockname
|
||||
|
||||
def getproxypeername(self):
|
||||
"""getproxypeername() -> address info
|
||||
Returns the IP and port number of the proxy.
|
||||
"""
|
||||
return _orgsocket.getpeername(self)
|
||||
|
||||
def getpeername(self):
|
||||
"""getpeername() -> address info
|
||||
Returns the IP address and port number of the destination
|
||||
machine (note: getproxypeername returns the proxy)
|
||||
"""
|
||||
return self.__proxypeername
|
||||
|
||||
def __negotiatesocks4(self,destaddr,destport):
|
||||
"""__negotiatesocks4(self,destaddr,destport)
|
||||
Negotiates a connection through a SOCKS4 server.
|
||||
"""
|
||||
# Check if the destination address provided is an IP address
|
||||
rmtrslv = False
|
||||
try:
|
||||
ipaddr = socket.inet_aton(destaddr)
|
||||
except socket.error:
|
||||
# It's a DNS name. Check where it should be resolved.
|
||||
if self.__proxy[3]:
|
||||
ipaddr = struct.pack("BBBB", 0x00, 0x00, 0x00, 0x01)
|
||||
rmtrslv = True
|
||||
else:
|
||||
ipaddr = socket.inet_aton(socket.gethostbyname(destaddr))
|
||||
# Construct the request packet
|
||||
req = struct.pack(">BBH", 0x04, 0x01, destport) + ipaddr
|
||||
# The username parameter is considered userid for SOCKS4
|
||||
if self.__proxy[4] != None:
|
||||
req = req + self.__proxy[4]
|
||||
req = req + chr(0x00).encode()
|
||||
# DNS name if remote resolving is required
|
||||
# NOTE: This is actually an extension to the SOCKS4 protocol
|
||||
# called SOCKS4A and may not be supported in all cases.
|
||||
if rmtrslv:
|
||||
req = req + destaddr + chr(0x00).encode()
|
||||
self.sendall(req)
|
||||
# Get the response from the server
|
||||
resp = self.__recvall(8)
|
||||
if resp[0:1] != chr(0x00).encode():
|
||||
# Bad data
|
||||
self.close()
|
||||
raise GeneralProxyError((1,_generalerrors[1]))
|
||||
if resp[1:2] != chr(0x5A).encode():
|
||||
# Server returned an error
|
||||
self.close()
|
||||
if ord(resp[1:2]) in (91, 92, 93):
|
||||
self.close()
|
||||
raise Socks4Error((ord(resp[1:2]), _socks4errors[ord(resp[1:2]) - 90]))
|
||||
else:
|
||||
raise Socks4Error((94, _socks4errors[4]))
|
||||
# Get the bound address/port
|
||||
self.__proxysockname = (socket.inet_ntoa(resp[4:]), struct.unpack(">H", resp[2:4])[0])
|
||||
if rmtrslv != None:
|
||||
self.__proxypeername = (socket.inet_ntoa(ipaddr), destport)
|
||||
else:
|
||||
self.__proxypeername = (destaddr, destport)
|
||||
|
||||
def __negotiatehttp(self, destaddr, destport):
|
||||
"""__negotiatehttp(self,destaddr,destport)
|
||||
Negotiates a connection through an HTTP server.
|
||||
"""
|
||||
# If we need to resolve locally, we do this now
|
||||
if not self.__proxy[3]:
|
||||
addr = socket.gethostbyname(destaddr)
|
||||
else:
|
||||
addr = destaddr
|
||||
headers = ["CONNECT ", addr, ":", str(destport), " HTTP/1.1\r\n"]
|
||||
headers += ["Host: ", destaddr, "\r\n"]
|
||||
if (self.__proxy[4] != None and self.__proxy[5] != None):
|
||||
headers += [self.__getauthheader(), "\r\n"]
|
||||
headers.append("\r\n")
|
||||
self.sendall("".join(headers).encode())
|
||||
# We read the response until we get the string "\r\n\r\n"
|
||||
resp = self.recv(1)
|
||||
while resp.find("\r\n\r\n".encode()) == -1:
|
||||
resp = resp + self.recv(1)
|
||||
# We just need the first line to check if the connection
|
||||
# was successful
|
||||
statusline = resp.splitlines()[0].split(" ".encode(), 2)
|
||||
if statusline[0] not in ("HTTP/1.0".encode(), "HTTP/1.1".encode()):
|
||||
self.close()
|
||||
raise GeneralProxyError((1, _generalerrors[1]))
|
||||
try:
|
||||
statuscode = int(statusline[1])
|
||||
except ValueError:
|
||||
self.close()
|
||||
raise GeneralProxyError((1, _generalerrors[1]))
|
||||
if statuscode != 200:
|
||||
self.close()
|
||||
raise HTTPError((statuscode, statusline[2]))
|
||||
self.__proxysockname = ("0.0.0.0", 0)
|
||||
self.__proxypeername = (addr, destport)
|
||||
|
||||
def connect(self, destpair):
|
||||
"""connect(self, despair)
|
||||
Connects to the specified destination through a proxy.
|
||||
destpar - A tuple of the IP/DNS address and the port number.
|
||||
(identical to socket's connect).
|
||||
To select the proxy server use setproxy().
|
||||
"""
|
||||
# Do a minimal input check first
|
||||
if (not type(destpair) in (list,tuple)) or (len(destpair) < 2) or (not isinstance(destpair[0], basestring)) or (type(destpair[1]) != int):
|
||||
raise GeneralProxyError((5, _generalerrors[5]))
|
||||
if self.__proxy[0] == PROXY_TYPE_SOCKS5:
|
||||
if self.__proxy[2] != None:
|
||||
portnum = self.__proxy[2]
|
||||
else:
|
||||
portnum = 1080
|
||||
_orgsocket.connect(self, (self.__proxy[1], portnum))
|
||||
self.__negotiatesocks5(destpair[0], destpair[1])
|
||||
elif self.__proxy[0] == PROXY_TYPE_SOCKS4:
|
||||
if self.__proxy[2] != None:
|
||||
portnum = self.__proxy[2]
|
||||
else:
|
||||
portnum = 1080
|
||||
_orgsocket.connect(self,(self.__proxy[1], portnum))
|
||||
self.__negotiatesocks4(destpair[0], destpair[1])
|
||||
elif self.__proxy[0] == PROXY_TYPE_HTTP:
|
||||
if self.__proxy[2] != None:
|
||||
portnum = self.__proxy[2]
|
||||
else:
|
||||
portnum = 8080
|
||||
_orgsocket.connect(self,(self.__proxy[1], portnum))
|
||||
self.__negotiatehttp(destpair[0], destpair[1])
|
||||
elif self.__proxy[0] == PROXY_TYPE_HTTP_NO_TUNNEL:
|
||||
if self.__proxy[2] != None:
|
||||
portnum = self.__proxy[2]
|
||||
else:
|
||||
portnum = 8080
|
||||
_orgsocket.connect(self,(self.__proxy[1],portnum))
|
||||
if destpair[1] == 443:
|
||||
self.__negotiatehttp(destpair[0],destpair[1])
|
||||
else:
|
||||
self.__httptunnel = False
|
||||
elif self.__proxy[0] == None:
|
||||
_orgsocket.connect(self, (destpair[0], destpair[1]))
|
||||
else:
|
||||
raise GeneralProxyError((4, _generalerrors[4]))
|
Двоичный файл не отображается.
|
@ -0,0 +1 @@
|
|||
from realsocket import gaierror, error, getaddrinfo, SOCK_STREAM
|
|
@ -0,0 +1,88 @@
|
|||
import unittest
|
||||
import errno
|
||||
import os
|
||||
import signal
|
||||
import subprocess
|
||||
import tempfile
|
||||
|
||||
import nose
|
||||
|
||||
import httplib2
|
||||
from httplib2 import socks
|
||||
from httplib2.test import miniserver
|
||||
|
||||
tinyproxy_cfg = """
|
||||
User "%(user)s"
|
||||
Port %(port)s
|
||||
Listen 127.0.0.1
|
||||
PidFile "%(pidfile)s"
|
||||
LogFile "%(logfile)s"
|
||||
MaxClients 2
|
||||
StartServers 1
|
||||
LogLevel Info
|
||||
"""
|
||||
|
||||
|
||||
class FunctionalProxyHttpTest(unittest.TestCase):
|
||||
def setUp(self):
|
||||
if not socks:
|
||||
raise nose.SkipTest('socks module unavailable')
|
||||
if not subprocess:
|
||||
raise nose.SkipTest('subprocess module unavailable')
|
||||
|
||||
# start a short-lived miniserver so we can get a likely port
|
||||
# for the proxy
|
||||
self.httpd, self.proxyport = miniserver.start_server(
|
||||
miniserver.ThisDirHandler)
|
||||
self.httpd.shutdown()
|
||||
self.httpd, self.port = miniserver.start_server(
|
||||
miniserver.ThisDirHandler)
|
||||
|
||||
self.pidfile = tempfile.mktemp()
|
||||
self.logfile = tempfile.mktemp()
|
||||
fd, self.conffile = tempfile.mkstemp()
|
||||
f = os.fdopen(fd, 'w')
|
||||
our_cfg = tinyproxy_cfg % {'user': os.getlogin(),
|
||||
'pidfile': self.pidfile,
|
||||
'port': self.proxyport,
|
||||
'logfile': self.logfile}
|
||||
f.write(our_cfg)
|
||||
f.close()
|
||||
try:
|
||||
# TODO use subprocess.check_call when 2.4 is dropped
|
||||
ret = subprocess.call(['tinyproxy', '-c', self.conffile])
|
||||
self.assertEqual(0, ret)
|
||||
except OSError, e:
|
||||
if e.errno == errno.ENOENT:
|
||||
raise nose.SkipTest('tinyproxy not available')
|
||||
raise
|
||||
|
||||
def tearDown(self):
|
||||
self.httpd.shutdown()
|
||||
try:
|
||||
pid = int(open(self.pidfile).read())
|
||||
os.kill(pid, signal.SIGTERM)
|
||||
except OSError, e:
|
||||
if e.errno == errno.ESRCH:
|
||||
print '\n\n\nTinyProxy Failed to start, log follows:'
|
||||
print open(self.logfile).read()
|
||||
print 'end tinyproxy log\n\n\n'
|
||||
raise
|
||||
map(os.unlink, (self.pidfile,
|
||||
self.logfile,
|
||||
self.conffile))
|
||||
|
||||
def testSimpleProxy(self):
|
||||
proxy_info = httplib2.ProxyInfo(socks.PROXY_TYPE_HTTP,
|
||||
'localhost', self.proxyport)
|
||||
client = httplib2.Http(proxy_info=proxy_info)
|
||||
src = 'miniserver.py'
|
||||
response, body = client.request('http://localhost:%d/%s' %
|
||||
(self.port, src))
|
||||
self.assertEqual(response.status, 200)
|
||||
self.assertEqual(body, open(os.path.join(miniserver.HERE, src)).read())
|
||||
lf = open(self.logfile).read()
|
||||
expect = ('Established connection to host "127.0.0.1" '
|
||||
'using file descriptor')
|
||||
self.assertTrue(expect in lf,
|
||||
'tinyproxy did not proxy a request for miniserver')
|
|
@ -0,0 +1,100 @@
|
|||
import logging
|
||||
import os
|
||||
import select
|
||||
import SimpleHTTPServer
|
||||
import SocketServer
|
||||
import threading
|
||||
|
||||
HERE = os.path.dirname(__file__)
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class ThisDirHandler(SimpleHTTPServer.SimpleHTTPRequestHandler):
|
||||
def translate_path(self, path):
|
||||
path = path.split('?', 1)[0].split('#', 1)[0]
|
||||
return os.path.join(HERE, *filter(None, path.split('/')))
|
||||
|
||||
def log_message(self, s, *args):
|
||||
# output via logging so nose can catch it
|
||||
logger.info(s, *args)
|
||||
|
||||
|
||||
class ShutdownServer(SocketServer.TCPServer):
|
||||
"""Mixin that allows serve_forever to be shut down.
|
||||
|
||||
The methods in this mixin are backported from SocketServer.py in the Python
|
||||
2.6.4 standard library. The mixin is unnecessary in 2.6 and later, when
|
||||
BaseServer supports the shutdown method directly.
|
||||
"""
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
SocketServer.TCPServer.__init__(self, *args, **kwargs)
|
||||
self.__is_shut_down = threading.Event()
|
||||
self.__serving = False
|
||||
|
||||
def serve_forever(self, poll_interval=0.1):
|
||||
"""Handle one request at a time until shutdown.
|
||||
|
||||
Polls for shutdown every poll_interval seconds. Ignores
|
||||
self.timeout. If you need to do periodic tasks, do them in
|
||||
another thread.
|
||||
"""
|
||||
self.__serving = True
|
||||
self.__is_shut_down.clear()
|
||||
while self.__serving:
|
||||
r, w, e = select.select([self.socket], [], [], poll_interval)
|
||||
if r:
|
||||
self._handle_request_noblock()
|
||||
self.__is_shut_down.set()
|
||||
|
||||
def shutdown(self):
|
||||
"""Stops the serve_forever loop.
|
||||
|
||||
Blocks until the loop has finished. This must be called while
|
||||
serve_forever() is running in another thread, or it will deadlock.
|
||||
"""
|
||||
self.__serving = False
|
||||
self.__is_shut_down.wait()
|
||||
|
||||
def handle_request(self):
|
||||
"""Handle one request, possibly blocking.
|
||||
|
||||
Respects self.timeout.
|
||||
"""
|
||||
# Support people who used socket.settimeout() to escape
|
||||
# handle_request before self.timeout was available.
|
||||
timeout = self.socket.gettimeout()
|
||||
if timeout is None:
|
||||
timeout = self.timeout
|
||||
elif self.timeout is not None:
|
||||
timeout = min(timeout, self.timeout)
|
||||
fd_sets = select.select([self], [], [], timeout)
|
||||
if not fd_sets[0]:
|
||||
self.handle_timeout()
|
||||
return
|
||||
self._handle_request_noblock()
|
||||
|
||||
def _handle_request_noblock(self):
|
||||
"""Handle one request, without blocking.
|
||||
|
||||
I assume that select.select has returned that the socket is
|
||||
readable before this function was called, so there should be
|
||||
no risk of blocking in get_request().
|
||||
"""
|
||||
try:
|
||||
request, client_address = self.get_request()
|
||||
except socket.error:
|
||||
return
|
||||
if self.verify_request(request, client_address):
|
||||
try:
|
||||
self.process_request(request, client_address)
|
||||
except:
|
||||
self.handle_error(request, client_address)
|
||||
self.close_request(request)
|
||||
|
||||
|
||||
def start_server(handler):
|
||||
httpd = ShutdownServer(("", 0), handler)
|
||||
threading.Thread(target=httpd.serve_forever).start()
|
||||
_, port = httpd.socket.getsockname()
|
||||
return httpd, port
|
|
@ -0,0 +1,70 @@
|
|||
# Certifcate Authority certificates for validating SSL connections.
|
||||
#
|
||||
# This file contains PEM format certificates generated from
|
||||
# http://mxr.mozilla.org/seamonkey/source/security/nss/lib/ckfw/builtins/certdata.txt
|
||||
#
|
||||
# ***** BEGIN LICENSE BLOCK *****
|
||||
# Version: MPL 1.1/GPL 2.0/LGPL 2.1
|
||||
#
|
||||
# The contents of this file are subject to the Mozilla Public License Version
|
||||
# 1.1 (the "License"); you may not use this file except in compliance with
|
||||
# the License. You may obtain a copy of the License at
|
||||
# http://www.mozilla.org/MPL/
|
||||
#
|
||||
# Software distributed under the License is distributed on an "AS IS" basis,
|
||||
# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
|
||||
# for the specific language governing rights and limitations under the
|
||||
# License.
|
||||
#
|
||||
# The Original Code is the Netscape security libraries.
|
||||
#
|
||||
# The Initial Developer of the Original Code is
|
||||
# Netscape Communications Corporation.
|
||||
# Portions created by the Initial Developer are Copyright (C) 1994-2000
|
||||
# the Initial Developer. All Rights Reserved.
|
||||
#
|
||||
# Contributor(s):
|
||||
#
|
||||
# Alternatively, the contents of this file may be used under the terms of
|
||||
# either the GNU General Public License Version 2 or later (the "GPL"), or
|
||||
# the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
|
||||
# in which case the provisions of the GPL or the LGPL are applicable instead
|
||||
# of those above. If you wish to allow use of your version of this file only
|
||||
# under the terms of either the GPL or the LGPL, and not to allow others to
|
||||
# use your version of this file under the terms of the MPL, indicate your
|
||||
# decision by deleting the provisions above and replace them with the notice
|
||||
# and other provisions required by the GPL or the LGPL. If you do not delete
|
||||
# the provisions above, a recipient may use your version of this file under
|
||||
# the terms of any one of the MPL, the GPL or the LGPL.
|
||||
#
|
||||
# ***** END LICENSE BLOCK *****
|
||||
|
||||
|
||||
Comodo CA Limited, CN=Trusted Certificate Services
|
||||
==================================================
|
||||
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIEQzCCAyugAwIBAgIBATANBgkqhkiG9w0BAQUFADB/MQswCQYDVQQGEwJHQjEb
|
||||
MBkGA1UECAwSR3JlYXRlciBNYW5jaGVzdGVyMRAwDgYDVQQHDAdTYWxmb3JkMRow
|
||||
GAYDVQQKDBFDb21vZG8gQ0EgTGltaXRlZDElMCMGA1UEAwwcVHJ1c3RlZCBDZXJ0
|
||||
aWZpY2F0ZSBTZXJ2aWNlczAeFw0wNDAxMDEwMDAwMDBaFw0yODEyMzEyMzU5NTla
|
||||
MH8xCzAJBgNVBAYTAkdCMRswGQYDVQQIDBJHcmVhdGVyIE1hbmNoZXN0ZXIxEDAO
|
||||
BgNVBAcMB1NhbGZvcmQxGjAYBgNVBAoMEUNvbW9kbyBDQSBMaW1pdGVkMSUwIwYD
|
||||
VQQDDBxUcnVzdGVkIENlcnRpZmljYXRlIFNlcnZpY2VzMIIBIjANBgkqhkiG9w0B
|
||||
AQEFAAOCAQ8AMIIBCgKCAQEA33FvNlhTWvI2VFeAxHQIIO0Yfyod5jWaHiWsnOWW
|
||||
fnJSoBVC21ndZHoa0Lh73TkVvFVIxO06AOoxEbrycXQaZ7jPM8yoMa+j49d/vzMt
|
||||
TGo87IvDktJTdyR0nAducPy9C1t2ul/y/9c3S0pgePfw+spwtOpZqqPOSC+pw7IL
|
||||
fhdyFgymBwwbOM/JYrc/oJOlh0Hyt3BAd9i+FHzjqMB6juljatEPmsbS9Is6FARW
|
||||
1O24zG71++IsWL1/T2sr92AkWCTOJu80kTrV44HQsvAEAtdbtz6SrGsSivnkBbA7
|
||||
kUlcsutT6vifR4buv5XAwAaf0lteERv0xwQ1KdJVXOTt6wIDAQABo4HJMIHGMB0G
|
||||
A1UdDgQWBBTFe1i97doladL3WRaoszLAeydb9DAOBgNVHQ8BAf8EBAMCAQYwDwYD
|
||||
VR0TAQH/BAUwAwEB/zCBgwYDVR0fBHwwejA8oDqgOIY2aHR0cDovL2NybC5jb21v
|
||||
ZG9jYS5jb20vVHJ1c3RlZENlcnRpZmljYXRlU2VydmljZXMuY3JsMDqgOKA2hjRo
|
||||
dHRwOi8vY3JsLmNvbW9kby5uZXQvVHJ1c3RlZENlcnRpZmljYXRlU2VydmljZXMu
|
||||
Y3JsMA0GCSqGSIb3DQEBBQUAA4IBAQDIk4E7ibSvuIQSTI3S8NtwuleGFTQQuS9/
|
||||
HrCoiWChisJ3DFBKmwCL2Iv0QeLQg4pKHBQGsKNoBXAxMKdTmw7pSqBYaWcOrp32
|
||||
pSxBvzwGa+RZzG0Q8ZZvH9/0BAKkn0U+yNj6NkZEUD+Cl5EfKNsYEYwq5GWDVxIS
|
||||
jBc/lDb+XbDABHcTuPQV1T84zJQ6VdCsmPW6AF/ghhmBeC8owH7TzEIK9a5QoNE+
|
||||
xqFx7D+gIIxmOom0jtTYsU0lR+4viMi14QVFwL4Ucd56/Y57fU0IlqUSc/Atyjcn
|
||||
dBInTMu2l+nZrghtWjlA3QVHdWpaIbOjGM9O9y5Xt5hwXsjEeLBi
|
||||
-----END CERTIFICATE-----
|
|
@ -0,0 +1,23 @@
|
|||
import os
|
||||
import unittest
|
||||
|
||||
import httplib2
|
||||
|
||||
from httplib2.test import miniserver
|
||||
|
||||
|
||||
class HttpSmokeTest(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.httpd, self.port = miniserver.start_server(
|
||||
miniserver.ThisDirHandler)
|
||||
|
||||
def tearDown(self):
|
||||
self.httpd.shutdown()
|
||||
|
||||
def testGetFile(self):
|
||||
client = httplib2.Http()
|
||||
src = 'miniserver.py'
|
||||
response, body = client.request('http://localhost:%d/%s' %
|
||||
(self.port, src))
|
||||
self.assertEqual(response.status, 200)
|
||||
self.assertEqual(body, open(os.path.join(miniserver.HERE, src)).read())
|
|
@ -0,0 +1,24 @@
|
|||
"""Tests for httplib2 when the socket module is missing.
|
||||
|
||||
This helps ensure compatibility with environments such as AppEngine.
|
||||
"""
|
||||
import os
|
||||
import sys
|
||||
import unittest
|
||||
|
||||
import httplib2
|
||||
|
||||
class MissingSocketTest(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self._oldsocks = httplib2.socks
|
||||
httplib2.socks = None
|
||||
|
||||
def tearDown(self):
|
||||
httplib2.socks = self._oldsocks
|
||||
|
||||
def testProxyDisabled(self):
|
||||
proxy_info = httplib2.ProxyInfo('blah',
|
||||
'localhost', 0)
|
||||
client = httplib2.Http(proxy_info=proxy_info)
|
||||
self.assertRaises(httplib2.ProxiesUnavailableError,
|
||||
client.request, 'http://localhost:-1/')
|
|
@ -0,0 +1,5 @@
|
|||
__version__ = "1.1"
|
||||
|
||||
GOOGLE_AUTH_URI = 'https://accounts.google.com/o/oauth2/auth'
|
||||
GOOGLE_REVOKE_URI = 'https://accounts.google.com/o/oauth2/revoke'
|
||||
GOOGLE_TOKEN_URI = 'https://accounts.google.com/o/oauth2/token'
|
|
@ -0,0 +1,32 @@
|
|||
# Copyright (C) 2010 Google Inc.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
"""Utility module to import a JSON module
|
||||
|
||||
Hides all the messy details of exactly where
|
||||
we get a simplejson module from.
|
||||
"""
|
||||
|
||||
__author__ = 'jcgregorio@google.com (Joe Gregorio)'
|
||||
|
||||
|
||||
try: # pragma: no cover
|
||||
# Should work for Python2.6 and higher.
|
||||
import json as simplejson
|
||||
except ImportError: # pragma: no cover
|
||||
try:
|
||||
import simplejson
|
||||
except ImportError:
|
||||
# Try to import from django, should work on App Engine
|
||||
from django.utils import simplejson
|
|
@ -0,0 +1,896 @@
|
|||
# Copyright (C) 2010 Google Inc.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
"""Utilities for Google App Engine
|
||||
|
||||
Utilities for making it easier to use OAuth 2.0 on Google App Engine.
|
||||
"""
|
||||
|
||||
__author__ = 'jcgregorio@google.com (Joe Gregorio)'
|
||||
|
||||
import base64
|
||||
import cgi
|
||||
import httplib2
|
||||
import logging
|
||||
import os
|
||||
import pickle
|
||||
import time
|
||||
|
||||
from google.appengine.api import app_identity
|
||||
from google.appengine.api import memcache
|
||||
from google.appengine.api import users
|
||||
from google.appengine.ext import db
|
||||
from google.appengine.ext import webapp
|
||||
from google.appengine.ext.webapp.util import login_required
|
||||
from google.appengine.ext.webapp.util import run_wsgi_app
|
||||
from oauth2client import GOOGLE_AUTH_URI
|
||||
from oauth2client import GOOGLE_REVOKE_URI
|
||||
from oauth2client import GOOGLE_TOKEN_URI
|
||||
from oauth2client import clientsecrets
|
||||
from oauth2client import util
|
||||
from oauth2client import xsrfutil
|
||||
from oauth2client.anyjson import simplejson
|
||||
from oauth2client.client import AccessTokenRefreshError
|
||||
from oauth2client.client import AssertionCredentials
|
||||
from oauth2client.client import Credentials
|
||||
from oauth2client.client import Flow
|
||||
from oauth2client.client import OAuth2WebServerFlow
|
||||
from oauth2client.client import Storage
|
||||
|
||||
# TODO(dhermes): Resolve import issue.
|
||||
# This is a temporary fix for a Google internal issue.
|
||||
try:
|
||||
from google.appengine.ext import ndb
|
||||
except ImportError:
|
||||
ndb = None
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
OAUTH2CLIENT_NAMESPACE = 'oauth2client#ns'
|
||||
|
||||
XSRF_MEMCACHE_ID = 'xsrf_secret_key'
|
||||
|
||||
|
||||
def _safe_html(s):
|
||||
"""Escape text to make it safe to display.
|
||||
|
||||
Args:
|
||||
s: string, The text to escape.
|
||||
|
||||
Returns:
|
||||
The escaped text as a string.
|
||||
"""
|
||||
return cgi.escape(s, quote=1).replace("'", ''')
|
||||
|
||||
|
||||
class InvalidClientSecretsError(Exception):
|
||||
"""The client_secrets.json file is malformed or missing required fields."""
|
||||
|
||||
|
||||
class InvalidXsrfTokenError(Exception):
|
||||
"""The XSRF token is invalid or expired."""
|
||||
|
||||
|
||||
class SiteXsrfSecretKey(db.Model):
|
||||
"""Storage for the sites XSRF secret key.
|
||||
|
||||
There will only be one instance stored of this model, the one used for the
|
||||
site.
|
||||
"""
|
||||
secret = db.StringProperty()
|
||||
|
||||
if ndb is not None:
|
||||
class SiteXsrfSecretKeyNDB(ndb.Model):
|
||||
"""NDB Model for storage for the sites XSRF secret key.
|
||||
|
||||
Since this model uses the same kind as SiteXsrfSecretKey, it can be used
|
||||
interchangeably. This simply provides an NDB model for interacting with the
|
||||
same data the DB model interacts with.
|
||||
|
||||
There should only be one instance stored of this model, the one used for the
|
||||
site.
|
||||
"""
|
||||
secret = ndb.StringProperty()
|
||||
|
||||
@classmethod
|
||||
def _get_kind(cls):
|
||||
"""Return the kind name for this class."""
|
||||
return 'SiteXsrfSecretKey'
|
||||
|
||||
|
||||
def _generate_new_xsrf_secret_key():
|
||||
"""Returns a random XSRF secret key.
|
||||
"""
|
||||
return os.urandom(16).encode("hex")
|
||||
|
||||
|
||||
def xsrf_secret_key():
|
||||
"""Return the secret key for use for XSRF protection.
|
||||
|
||||
If the Site entity does not have a secret key, this method will also create
|
||||
one and persist it.
|
||||
|
||||
Returns:
|
||||
The secret key.
|
||||
"""
|
||||
secret = memcache.get(XSRF_MEMCACHE_ID, namespace=OAUTH2CLIENT_NAMESPACE)
|
||||
if not secret:
|
||||
# Load the one and only instance of SiteXsrfSecretKey.
|
||||
model = SiteXsrfSecretKey.get_or_insert(key_name='site')
|
||||
if not model.secret:
|
||||
model.secret = _generate_new_xsrf_secret_key()
|
||||
model.put()
|
||||
secret = model.secret
|
||||
memcache.add(XSRF_MEMCACHE_ID, secret, namespace=OAUTH2CLIENT_NAMESPACE)
|
||||
|
||||
return str(secret)
|
||||
|
||||
|
||||
class AppAssertionCredentials(AssertionCredentials):
|
||||
"""Credentials object for App Engine Assertion Grants
|
||||
|
||||
This object will allow an App Engine application to identify itself to Google
|
||||
and other OAuth 2.0 servers that can verify assertions. It can be used for the
|
||||
purpose of accessing data stored under an account assigned to the App Engine
|
||||
application itself.
|
||||
|
||||
This credential does not require a flow to instantiate because it represents
|
||||
a two legged flow, and therefore has all of the required information to
|
||||
generate and refresh its own access tokens.
|
||||
"""
|
||||
|
||||
@util.positional(2)
|
||||
def __init__(self, scope, **kwargs):
|
||||
"""Constructor for AppAssertionCredentials
|
||||
|
||||
Args:
|
||||
scope: string or iterable of strings, scope(s) of the credentials being
|
||||
requested.
|
||||
"""
|
||||
self.scope = util.scopes_to_string(scope)
|
||||
|
||||
# Assertion type is no longer used, but still in the parent class signature.
|
||||
super(AppAssertionCredentials, self).__init__(None)
|
||||
|
||||
@classmethod
|
||||
def from_json(cls, json):
|
||||
data = simplejson.loads(json)
|
||||
return AppAssertionCredentials(data['scope'])
|
||||
|
||||
def _refresh(self, http_request):
|
||||
"""Refreshes the access_token.
|
||||
|
||||
Since the underlying App Engine app_identity implementation does its own
|
||||
caching we can skip all the storage hoops and just to a refresh using the
|
||||
API.
|
||||
|
||||
Args:
|
||||
http_request: callable, a callable that matches the method signature of
|
||||
httplib2.Http.request, used to make the refresh request.
|
||||
|
||||
Raises:
|
||||
AccessTokenRefreshError: When the refresh fails.
|
||||
"""
|
||||
try:
|
||||
scopes = self.scope.split()
|
||||
(token, _) = app_identity.get_access_token(scopes)
|
||||
except app_identity.Error, e:
|
||||
raise AccessTokenRefreshError(str(e))
|
||||
self.access_token = token
|
||||
|
||||
|
||||
class FlowProperty(db.Property):
|
||||
"""App Engine datastore Property for Flow.
|
||||
|
||||
Utility property that allows easy storage and retrieval of an
|
||||
oauth2client.Flow"""
|
||||
|
||||
# Tell what the user type is.
|
||||
data_type = Flow
|
||||
|
||||
# For writing to datastore.
|
||||
def get_value_for_datastore(self, model_instance):
|
||||
flow = super(FlowProperty,
|
||||
self).get_value_for_datastore(model_instance)
|
||||
return db.Blob(pickle.dumps(flow))
|
||||
|
||||
# For reading from datastore.
|
||||
def make_value_from_datastore(self, value):
|
||||
if value is None:
|
||||
return None
|
||||
return pickle.loads(value)
|
||||
|
||||
def validate(self, value):
|
||||
if value is not None and not isinstance(value, Flow):
|
||||
raise db.BadValueError('Property %s must be convertible '
|
||||
'to a FlowThreeLegged instance (%s)' %
|
||||
(self.name, value))
|
||||
return super(FlowProperty, self).validate(value)
|
||||
|
||||
def empty(self, value):
|
||||
return not value
|
||||
|
||||
|
||||
if ndb is not None:
|
||||
class FlowNDBProperty(ndb.PickleProperty):
|
||||
"""App Engine NDB datastore Property for Flow.
|
||||
|
||||
Serves the same purpose as the DB FlowProperty, but for NDB models. Since
|
||||
PickleProperty inherits from BlobProperty, the underlying representation of
|
||||
the data in the datastore will be the same as in the DB case.
|
||||
|
||||
Utility property that allows easy storage and retrieval of an
|
||||
oauth2client.Flow
|
||||
"""
|
||||
|
||||
def _validate(self, value):
|
||||
"""Validates a value as a proper Flow object.
|
||||
|
||||
Args:
|
||||
value: A value to be set on the property.
|
||||
|
||||
Raises:
|
||||
TypeError if the value is not an instance of Flow.
|
||||
"""
|
||||
logger.info('validate: Got type %s', type(value))
|
||||
if value is not None and not isinstance(value, Flow):
|
||||
raise TypeError('Property %s must be convertible to a flow '
|
||||
'instance; received: %s.' % (self._name, value))
|
||||
|
||||
|
||||
class CredentialsProperty(db.Property):
|
||||
"""App Engine datastore Property for Credentials.
|
||||
|
||||
Utility property that allows easy storage and retrieval of
|
||||
oath2client.Credentials
|
||||
"""
|
||||
|
||||
# Tell what the user type is.
|
||||
data_type = Credentials
|
||||
|
||||
# For writing to datastore.
|
||||
def get_value_for_datastore(self, model_instance):
|
||||
logger.info("get: Got type " + str(type(model_instance)))
|
||||
cred = super(CredentialsProperty,
|
||||
self).get_value_for_datastore(model_instance)
|
||||
if cred is None:
|
||||
cred = ''
|
||||
else:
|
||||
cred = cred.to_json()
|
||||
return db.Blob(cred)
|
||||
|
||||
# For reading from datastore.
|
||||
def make_value_from_datastore(self, value):
|
||||
logger.info("make: Got type " + str(type(value)))
|
||||
if value is None:
|
||||
return None
|
||||
if len(value) == 0:
|
||||
return None
|
||||
try:
|
||||
credentials = Credentials.new_from_json(value)
|
||||
except ValueError:
|
||||
credentials = None
|
||||
return credentials
|
||||
|
||||
def validate(self, value):
|
||||
value = super(CredentialsProperty, self).validate(value)
|
||||
logger.info("validate: Got type " + str(type(value)))
|
||||
if value is not None and not isinstance(value, Credentials):
|
||||
raise db.BadValueError('Property %s must be convertible '
|
||||
'to a Credentials instance (%s)' %
|
||||
(self.name, value))
|
||||
#if value is not None and not isinstance(value, Credentials):
|
||||
# return None
|
||||
return value
|
||||
|
||||
|
||||
if ndb is not None:
|
||||
# TODO(dhermes): Turn this into a JsonProperty and overhaul the Credentials
|
||||
# and subclass mechanics to use new_from_dict, to_dict,
|
||||
# from_dict, etc.
|
||||
class CredentialsNDBProperty(ndb.BlobProperty):
|
||||
"""App Engine NDB datastore Property for Credentials.
|
||||
|
||||
Serves the same purpose as the DB CredentialsProperty, but for NDB models.
|
||||
Since CredentialsProperty stores data as a blob and this inherits from
|
||||
BlobProperty, the data in the datastore will be the same as in the DB case.
|
||||
|
||||
Utility property that allows easy storage and retrieval of Credentials and
|
||||
subclasses.
|
||||
"""
|
||||
def _validate(self, value):
|
||||
"""Validates a value as a proper credentials object.
|
||||
|
||||
Args:
|
||||
value: A value to be set on the property.
|
||||
|
||||
Raises:
|
||||
TypeError if the value is not an instance of Credentials.
|
||||
"""
|
||||
logger.info('validate: Got type %s', type(value))
|
||||
if value is not None and not isinstance(value, Credentials):
|
||||
raise TypeError('Property %s must be convertible to a credentials '
|
||||
'instance; received: %s.' % (self._name, value))
|
||||
|
||||
def _to_base_type(self, value):
|
||||
"""Converts our validated value to a JSON serialized string.
|
||||
|
||||
Args:
|
||||
value: A value to be set in the datastore.
|
||||
|
||||
Returns:
|
||||
A JSON serialized version of the credential, else '' if value is None.
|
||||
"""
|
||||
if value is None:
|
||||
return ''
|
||||
else:
|
||||
return value.to_json()
|
||||
|
||||
def _from_base_type(self, value):
|
||||
"""Converts our stored JSON string back to the desired type.
|
||||
|
||||
Args:
|
||||
value: A value from the datastore to be converted to the desired type.
|
||||
|
||||
Returns:
|
||||
A deserialized Credentials (or subclass) object, else None if the
|
||||
value can't be parsed.
|
||||
"""
|
||||
if not value:
|
||||
return None
|
||||
try:
|
||||
# Uses the from_json method of the implied class of value
|
||||
credentials = Credentials.new_from_json(value)
|
||||
except ValueError:
|
||||
credentials = None
|
||||
return credentials
|
||||
|
||||
|
||||
class StorageByKeyName(Storage):
|
||||
"""Store and retrieve a credential to and from the App Engine datastore.
|
||||
|
||||
This Storage helper presumes the Credentials have been stored as a
|
||||
CredentialsProperty or CredentialsNDBProperty on a datastore model class, and
|
||||
that entities are stored by key_name.
|
||||
"""
|
||||
|
||||
@util.positional(4)
|
||||
def __init__(self, model, key_name, property_name, cache=None):
|
||||
"""Constructor for Storage.
|
||||
|
||||
Args:
|
||||
model: db.Model or ndb.Model, model class
|
||||
key_name: string, key name for the entity that has the credentials
|
||||
property_name: string, name of the property that is a CredentialsProperty
|
||||
or CredentialsNDBProperty.
|
||||
cache: memcache, a write-through cache to put in front of the datastore.
|
||||
If the model you are using is an NDB model, using a cache will be
|
||||
redundant since the model uses an instance cache and memcache for you.
|
||||
"""
|
||||
self._model = model
|
||||
self._key_name = key_name
|
||||
self._property_name = property_name
|
||||
self._cache = cache
|
||||
|
||||
def _is_ndb(self):
|
||||
"""Determine whether the model of the instance is an NDB model.
|
||||
|
||||
Returns:
|
||||
Boolean indicating whether or not the model is an NDB or DB model.
|
||||
"""
|
||||
# issubclass will fail if one of the arguments is not a class, only need
|
||||
# worry about new-style classes since ndb and db models are new-style
|
||||
if isinstance(self._model, type):
|
||||
if ndb is not None and issubclass(self._model, ndb.Model):
|
||||
return True
|
||||
elif issubclass(self._model, db.Model):
|
||||
return False
|
||||
|
||||
raise TypeError('Model class not an NDB or DB model: %s.' % (self._model,))
|
||||
|
||||
def _get_entity(self):
|
||||
"""Retrieve entity from datastore.
|
||||
|
||||
Uses a different model method for db or ndb models.
|
||||
|
||||
Returns:
|
||||
Instance of the model corresponding to the current storage object
|
||||
and stored using the key name of the storage object.
|
||||
"""
|
||||
if self._is_ndb():
|
||||
return self._model.get_by_id(self._key_name)
|
||||
else:
|
||||
return self._model.get_by_key_name(self._key_name)
|
||||
|
||||
def _delete_entity(self):
|
||||
"""Delete entity from datastore.
|
||||
|
||||
Attempts to delete using the key_name stored on the object, whether or not
|
||||
the given key is in the datastore.
|
||||
"""
|
||||
if self._is_ndb():
|
||||
ndb.Key(self._model, self._key_name).delete()
|
||||
else:
|
||||
entity_key = db.Key.from_path(self._model.kind(), self._key_name)
|
||||
db.delete(entity_key)
|
||||
|
||||
def locked_get(self):
|
||||
"""Retrieve Credential from datastore.
|
||||
|
||||
Returns:
|
||||
oauth2client.Credentials
|
||||
"""
|
||||
if self._cache:
|
||||
json = self._cache.get(self._key_name)
|
||||
if json:
|
||||
return Credentials.new_from_json(json)
|
||||
|
||||
credentials = None
|
||||
entity = self._get_entity()
|
||||
if entity is not None:
|
||||
credentials = getattr(entity, self._property_name)
|
||||
if credentials and hasattr(credentials, 'set_store'):
|
||||
credentials.set_store(self)
|
||||
if self._cache:
|
||||
self._cache.set(self._key_name, credentials.to_json())
|
||||
|
||||
return credentials
|
||||
|
||||
def locked_put(self, credentials):
|
||||
"""Write a Credentials to the datastore.
|
||||
|
||||
Args:
|
||||
credentials: Credentials, the credentials to store.
|
||||
"""
|
||||
entity = self._model.get_or_insert(self._key_name)
|
||||
setattr(entity, self._property_name, credentials)
|
||||
entity.put()
|
||||
if self._cache:
|
||||
self._cache.set(self._key_name, credentials.to_json())
|
||||
|
||||
def locked_delete(self):
|
||||
"""Delete Credential from datastore."""
|
||||
|
||||
if self._cache:
|
||||
self._cache.delete(self._key_name)
|
||||
|
||||
self._delete_entity()
|
||||
|
||||
|
||||
class CredentialsModel(db.Model):
|
||||
"""Storage for OAuth 2.0 Credentials
|
||||
|
||||
Storage of the model is keyed by the user.user_id().
|
||||
"""
|
||||
credentials = CredentialsProperty()
|
||||
|
||||
|
||||
if ndb is not None:
|
||||
class CredentialsNDBModel(ndb.Model):
|
||||
"""NDB Model for storage of OAuth 2.0 Credentials
|
||||
|
||||
Since this model uses the same kind as CredentialsModel and has a property
|
||||
which can serialize and deserialize Credentials correctly, it can be used
|
||||
interchangeably with a CredentialsModel to access, insert and delete the
|
||||
same entities. This simply provides an NDB model for interacting with the
|
||||
same data the DB model interacts with.
|
||||
|
||||
Storage of the model is keyed by the user.user_id().
|
||||
"""
|
||||
credentials = CredentialsNDBProperty()
|
||||
|
||||
@classmethod
|
||||
def _get_kind(cls):
|
||||
"""Return the kind name for this class."""
|
||||
return 'CredentialsModel'
|
||||
|
||||
|
||||
def _build_state_value(request_handler, user):
|
||||
"""Composes the value for the 'state' parameter.
|
||||
|
||||
Packs the current request URI and an XSRF token into an opaque string that
|
||||
can be passed to the authentication server via the 'state' parameter.
|
||||
|
||||
Args:
|
||||
request_handler: webapp.RequestHandler, The request.
|
||||
user: google.appengine.api.users.User, The current user.
|
||||
|
||||
Returns:
|
||||
The state value as a string.
|
||||
"""
|
||||
uri = request_handler.request.url
|
||||
token = xsrfutil.generate_token(xsrf_secret_key(), user.user_id(),
|
||||
action_id=str(uri))
|
||||
return uri + ':' + token
|
||||
|
||||
|
||||
def _parse_state_value(state, user):
|
||||
"""Parse the value of the 'state' parameter.
|
||||
|
||||
Parses the value and validates the XSRF token in the state parameter.
|
||||
|
||||
Args:
|
||||
state: string, The value of the state parameter.
|
||||
user: google.appengine.api.users.User, The current user.
|
||||
|
||||
Raises:
|
||||
InvalidXsrfTokenError: if the XSRF token is invalid.
|
||||
|
||||
Returns:
|
||||
The redirect URI.
|
||||
"""
|
||||
uri, token = state.rsplit(':', 1)
|
||||
if not xsrfutil.validate_token(xsrf_secret_key(), token, user.user_id(),
|
||||
action_id=uri):
|
||||
raise InvalidXsrfTokenError()
|
||||
|
||||
return uri
|
||||
|
||||
|
||||
class OAuth2Decorator(object):
|
||||
"""Utility for making OAuth 2.0 easier.
|
||||
|
||||
Instantiate and then use with oauth_required or oauth_aware
|
||||
as decorators on webapp.RequestHandler methods.
|
||||
|
||||
Example:
|
||||
|
||||
decorator = OAuth2Decorator(
|
||||
client_id='837...ent.com',
|
||||
client_secret='Qh...wwI',
|
||||
scope='https://www.googleapis.com/auth/plus')
|
||||
|
||||
|
||||
class MainHandler(webapp.RequestHandler):
|
||||
|
||||
@decorator.oauth_required
|
||||
def get(self):
|
||||
http = decorator.http()
|
||||
# http is authorized with the user's Credentials and can be used
|
||||
# in API calls
|
||||
|
||||
"""
|
||||
|
||||
@util.positional(4)
|
||||
def __init__(self, client_id, client_secret, scope,
|
||||
auth_uri=GOOGLE_AUTH_URI,
|
||||
token_uri=GOOGLE_TOKEN_URI,
|
||||
revoke_uri=GOOGLE_REVOKE_URI,
|
||||
user_agent=None,
|
||||
message=None,
|
||||
callback_path='/oauth2callback',
|
||||
token_response_param=None,
|
||||
**kwargs):
|
||||
|
||||
"""Constructor for OAuth2Decorator
|
||||
|
||||
Args:
|
||||
client_id: string, client identifier.
|
||||
client_secret: string client secret.
|
||||
scope: string or iterable of strings, scope(s) of the credentials being
|
||||
requested.
|
||||
auth_uri: string, URI for authorization endpoint. For convenience
|
||||
defaults to Google's endpoints but any OAuth 2.0 provider can be used.
|
||||
token_uri: string, URI for token endpoint. For convenience
|
||||
defaults to Google's endpoints but any OAuth 2.0 provider can be used.
|
||||
revoke_uri: string, URI for revoke endpoint. For convenience
|
||||
defaults to Google's endpoints but any OAuth 2.0 provider can be used.
|
||||
user_agent: string, User agent of your application, default to None.
|
||||
message: Message to display if there are problems with the OAuth 2.0
|
||||
configuration. The message may contain HTML and will be presented on the
|
||||
web interface for any method that uses the decorator.
|
||||
callback_path: string, The absolute path to use as the callback URI. Note
|
||||
that this must match up with the URI given when registering the
|
||||
application in the APIs Console.
|
||||
token_response_param: string. If provided, the full JSON response
|
||||
to the access token request will be encoded and included in this query
|
||||
parameter in the callback URI. This is useful with providers (e.g.
|
||||
wordpress.com) that include extra fields that the client may want.
|
||||
**kwargs: dict, Keyword arguments are be passed along as kwargs to the
|
||||
OAuth2WebServerFlow constructor.
|
||||
"""
|
||||
self.flow = None
|
||||
self.credentials = None
|
||||
self._client_id = client_id
|
||||
self._client_secret = client_secret
|
||||
self._scope = util.scopes_to_string(scope)
|
||||
self._auth_uri = auth_uri
|
||||
self._token_uri = token_uri
|
||||
self._revoke_uri = revoke_uri
|
||||
self._user_agent = user_agent
|
||||
self._kwargs = kwargs
|
||||
self._message = message
|
||||
self._in_error = False
|
||||
self._callback_path = callback_path
|
||||
self._token_response_param = token_response_param
|
||||
|
||||
def _display_error_message(self, request_handler):
|
||||
request_handler.response.out.write('<html><body>')
|
||||
request_handler.response.out.write(_safe_html(self._message))
|
||||
request_handler.response.out.write('</body></html>')
|
||||
|
||||
def oauth_required(self, method):
|
||||
"""Decorator that starts the OAuth 2.0 dance.
|
||||
|
||||
Starts the OAuth dance for the logged in user if they haven't already
|
||||
granted access for this application.
|
||||
|
||||
Args:
|
||||
method: callable, to be decorated method of a webapp.RequestHandler
|
||||
instance.
|
||||
"""
|
||||
|
||||
def check_oauth(request_handler, *args, **kwargs):
|
||||
if self._in_error:
|
||||
self._display_error_message(request_handler)
|
||||
return
|
||||
|
||||
user = users.get_current_user()
|
||||
# Don't use @login_decorator as this could be used in a POST request.
|
||||
if not user:
|
||||
request_handler.redirect(users.create_login_url(
|
||||
request_handler.request.uri))
|
||||
return
|
||||
|
||||
self._create_flow(request_handler)
|
||||
|
||||
# Store the request URI in 'state' so we can use it later
|
||||
self.flow.params['state'] = _build_state_value(request_handler, user)
|
||||
self.credentials = StorageByKeyName(
|
||||
CredentialsModel, user.user_id(), 'credentials').get()
|
||||
|
||||
if not self.has_credentials():
|
||||
return request_handler.redirect(self.authorize_url())
|
||||
try:
|
||||
return method(request_handler, *args, **kwargs)
|
||||
except AccessTokenRefreshError:
|
||||
return request_handler.redirect(self.authorize_url())
|
||||
|
||||
return check_oauth
|
||||
|
||||
def _create_flow(self, request_handler):
|
||||
"""Create the Flow object.
|
||||
|
||||
The Flow is calculated lazily since we don't know where this app is
|
||||
running until it receives a request, at which point redirect_uri can be
|
||||
calculated and then the Flow object can be constructed.
|
||||
|
||||
Args:
|
||||
request_handler: webapp.RequestHandler, the request handler.
|
||||
"""
|
||||
if self.flow is None:
|
||||
redirect_uri = request_handler.request.relative_url(
|
||||
self._callback_path) # Usually /oauth2callback
|
||||
self.flow = OAuth2WebServerFlow(self._client_id, self._client_secret,
|
||||
self._scope, redirect_uri=redirect_uri,
|
||||
user_agent=self._user_agent,
|
||||
auth_uri=self._auth_uri,
|
||||
token_uri=self._token_uri,
|
||||
revoke_uri=self._revoke_uri,
|
||||
**self._kwargs)
|
||||
|
||||
def oauth_aware(self, method):
|
||||
"""Decorator that sets up for OAuth 2.0 dance, but doesn't do it.
|
||||
|
||||
Does all the setup for the OAuth dance, but doesn't initiate it.
|
||||
This decorator is useful if you want to create a page that knows
|
||||
whether or not the user has granted access to this application.
|
||||
From within a method decorated with @oauth_aware the has_credentials()
|
||||
and authorize_url() methods can be called.
|
||||
|
||||
Args:
|
||||
method: callable, to be decorated method of a webapp.RequestHandler
|
||||
instance.
|
||||
"""
|
||||
|
||||
def setup_oauth(request_handler, *args, **kwargs):
|
||||
if self._in_error:
|
||||
self._display_error_message(request_handler)
|
||||
return
|
||||
|
||||
user = users.get_current_user()
|
||||
# Don't use @login_decorator as this could be used in a POST request.
|
||||
if not user:
|
||||
request_handler.redirect(users.create_login_url(
|
||||
request_handler.request.uri))
|
||||
return
|
||||
|
||||
self._create_flow(request_handler)
|
||||
|
||||
self.flow.params['state'] = _build_state_value(request_handler, user)
|
||||
self.credentials = StorageByKeyName(
|
||||
CredentialsModel, user.user_id(), 'credentials').get()
|
||||
return method(request_handler, *args, **kwargs)
|
||||
return setup_oauth
|
||||
|
||||
def has_credentials(self):
|
||||
"""True if for the logged in user there are valid access Credentials.
|
||||
|
||||
Must only be called from with a webapp.RequestHandler subclassed method
|
||||
that had been decorated with either @oauth_required or @oauth_aware.
|
||||
"""
|
||||
return self.credentials is not None and not self.credentials.invalid
|
||||
|
||||
def authorize_url(self):
|
||||
"""Returns the URL to start the OAuth dance.
|
||||
|
||||
Must only be called from with a webapp.RequestHandler subclassed method
|
||||
that had been decorated with either @oauth_required or @oauth_aware.
|
||||
"""
|
||||
url = self.flow.step1_get_authorize_url()
|
||||
return str(url)
|
||||
|
||||
def http(self):
|
||||
"""Returns an authorized http instance.
|
||||
|
||||
Must only be called from within an @oauth_required decorated method, or
|
||||
from within an @oauth_aware decorated method where has_credentials()
|
||||
returns True.
|
||||
"""
|
||||
return self.credentials.authorize(httplib2.Http())
|
||||
|
||||
@property
|
||||
def callback_path(self):
|
||||
"""The absolute path where the callback will occur.
|
||||
|
||||
Note this is the absolute path, not the absolute URI, that will be
|
||||
calculated by the decorator at runtime. See callback_handler() for how this
|
||||
should be used.
|
||||
|
||||
Returns:
|
||||
The callback path as a string.
|
||||
"""
|
||||
return self._callback_path
|
||||
|
||||
|
||||
def callback_handler(self):
|
||||
"""RequestHandler for the OAuth 2.0 redirect callback.
|
||||
|
||||
Usage:
|
||||
app = webapp.WSGIApplication([
|
||||
('/index', MyIndexHandler),
|
||||
...,
|
||||
(decorator.callback_path, decorator.callback_handler())
|
||||
])
|
||||
|
||||
Returns:
|
||||
A webapp.RequestHandler that handles the redirect back from the
|
||||
server during the OAuth 2.0 dance.
|
||||
"""
|
||||
decorator = self
|
||||
|
||||
class OAuth2Handler(webapp.RequestHandler):
|
||||
"""Handler for the redirect_uri of the OAuth 2.0 dance."""
|
||||
|
||||
@login_required
|
||||
def get(self):
|
||||
error = self.request.get('error')
|
||||
if error:
|
||||
errormsg = self.request.get('error_description', error)
|
||||
self.response.out.write(
|
||||
'The authorization request failed: %s' % _safe_html(errormsg))
|
||||
else:
|
||||
user = users.get_current_user()
|
||||
decorator._create_flow(self)
|
||||
credentials = decorator.flow.step2_exchange(self.request.params)
|
||||
StorageByKeyName(
|
||||
CredentialsModel, user.user_id(), 'credentials').put(credentials)
|
||||
redirect_uri = _parse_state_value(str(self.request.get('state')),
|
||||
user)
|
||||
|
||||
if decorator._token_response_param and credentials.token_response:
|
||||
resp_json = simplejson.dumps(credentials.token_response)
|
||||
redirect_uri = util._add_query_parameter(
|
||||
redirect_uri, decorator._token_response_param, resp_json)
|
||||
|
||||
self.redirect(redirect_uri)
|
||||
|
||||
return OAuth2Handler
|
||||
|
||||
def callback_application(self):
|
||||
"""WSGI application for handling the OAuth 2.0 redirect callback.
|
||||
|
||||
If you need finer grained control use `callback_handler` which returns just
|
||||
the webapp.RequestHandler.
|
||||
|
||||
Returns:
|
||||
A webapp.WSGIApplication that handles the redirect back from the
|
||||
server during the OAuth 2.0 dance.
|
||||
"""
|
||||
return webapp.WSGIApplication([
|
||||
(self.callback_path, self.callback_handler())
|
||||
])
|
||||
|
||||
|
||||
class OAuth2DecoratorFromClientSecrets(OAuth2Decorator):
|
||||
"""An OAuth2Decorator that builds from a clientsecrets file.
|
||||
|
||||
Uses a clientsecrets file as the source for all the information when
|
||||
constructing an OAuth2Decorator.
|
||||
|
||||
Example:
|
||||
|
||||
decorator = OAuth2DecoratorFromClientSecrets(
|
||||
os.path.join(os.path.dirname(__file__), 'client_secrets.json')
|
||||
scope='https://www.googleapis.com/auth/plus')
|
||||
|
||||
|
||||
class MainHandler(webapp.RequestHandler):
|
||||
|
||||
@decorator.oauth_required
|
||||
def get(self):
|
||||
http = decorator.http()
|
||||
# http is authorized with the user's Credentials and can be used
|
||||
# in API calls
|
||||
"""
|
||||
|
||||
@util.positional(3)
|
||||
def __init__(self, filename, scope, message=None, cache=None):
|
||||
"""Constructor
|
||||
|
||||
Args:
|
||||
filename: string, File name of client secrets.
|
||||
scope: string or iterable of strings, scope(s) of the credentials being
|
||||
requested.
|
||||
message: string, A friendly string to display to the user if the
|
||||
clientsecrets file is missing or invalid. The message may contain HTML
|
||||
and will be presented on the web interface for any method that uses the
|
||||
decorator.
|
||||
cache: An optional cache service client that implements get() and set()
|
||||
methods. See clientsecrets.loadfile() for details.
|
||||
"""
|
||||
client_type, client_info = clientsecrets.loadfile(filename, cache=cache)
|
||||
if client_type not in [
|
||||
clientsecrets.TYPE_WEB, clientsecrets.TYPE_INSTALLED]:
|
||||
raise InvalidClientSecretsError(
|
||||
'OAuth2Decorator doesn\'t support this OAuth 2.0 flow.')
|
||||
constructor_kwargs = {
|
||||
'auth_uri': client_info['auth_uri'],
|
||||
'token_uri': client_info['token_uri'],
|
||||
'message': message,
|
||||
}
|
||||
revoke_uri = client_info.get('revoke_uri')
|
||||
if revoke_uri is not None:
|
||||
constructor_kwargs['revoke_uri'] = revoke_uri
|
||||
super(OAuth2DecoratorFromClientSecrets, self).__init__(
|
||||
client_info['client_id'], client_info['client_secret'],
|
||||
scope, **constructor_kwargs)
|
||||
if message is not None:
|
||||
self._message = message
|
||||
else:
|
||||
self._message = 'Please configure your application for OAuth 2.0.'
|
||||
|
||||
|
||||
@util.positional(2)
|
||||
def oauth2decorator_from_clientsecrets(filename, scope,
|
||||
message=None, cache=None):
|
||||
"""Creates an OAuth2Decorator populated from a clientsecrets file.
|
||||
|
||||
Args:
|
||||
filename: string, File name of client secrets.
|
||||
scope: string or list of strings, scope(s) of the credentials being
|
||||
requested.
|
||||
message: string, A friendly string to display to the user if the
|
||||
clientsecrets file is missing or invalid. The message may contain HTML and
|
||||
will be presented on the web interface for any method that uses the
|
||||
decorator.
|
||||
cache: An optional cache service client that implements get() and set()
|
||||
methods. See clientsecrets.loadfile() for details.
|
||||
|
||||
Returns: An OAuth2Decorator
|
||||
|
||||
"""
|
||||
return OAuth2DecoratorFromClientSecrets(filename, scope,
|
||||
message=message, cache=cache)
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -0,0 +1,153 @@
|
|||
# Copyright (C) 2011 Google Inc.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
"""Utilities for reading OAuth 2.0 client secret files.
|
||||
|
||||
A client_secrets.json file contains all the information needed to interact with
|
||||
an OAuth 2.0 protected service.
|
||||
"""
|
||||
|
||||
__author__ = 'jcgregorio@google.com (Joe Gregorio)'
|
||||
|
||||
|
||||
from anyjson import simplejson
|
||||
|
||||
# Properties that make a client_secrets.json file valid.
|
||||
TYPE_WEB = 'web'
|
||||
TYPE_INSTALLED = 'installed'
|
||||
|
||||
VALID_CLIENT = {
|
||||
TYPE_WEB: {
|
||||
'required': [
|
||||
'client_id',
|
||||
'client_secret',
|
||||
'redirect_uris',
|
||||
'auth_uri',
|
||||
'token_uri',
|
||||
],
|
||||
'string': [
|
||||
'client_id',
|
||||
'client_secret',
|
||||
],
|
||||
},
|
||||
TYPE_INSTALLED: {
|
||||
'required': [
|
||||
'client_id',
|
||||
'client_secret',
|
||||
'redirect_uris',
|
||||
'auth_uri',
|
||||
'token_uri',
|
||||
],
|
||||
'string': [
|
||||
'client_id',
|
||||
'client_secret',
|
||||
],
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
class Error(Exception):
|
||||
"""Base error for this module."""
|
||||
pass
|
||||
|
||||
|
||||
class InvalidClientSecretsError(Error):
|
||||
"""Format of ClientSecrets file is invalid."""
|
||||
pass
|
||||
|
||||
|
||||
def _validate_clientsecrets(obj):
|
||||
if obj is None or len(obj) != 1:
|
||||
raise InvalidClientSecretsError('Invalid file format.')
|
||||
client_type = obj.keys()[0]
|
||||
if client_type not in VALID_CLIENT.keys():
|
||||
raise InvalidClientSecretsError('Unknown client type: %s.' % client_type)
|
||||
client_info = obj[client_type]
|
||||
for prop_name in VALID_CLIENT[client_type]['required']:
|
||||
if prop_name not in client_info:
|
||||
raise InvalidClientSecretsError(
|
||||
'Missing property "%s" in a client type of "%s".' % (prop_name,
|
||||
client_type))
|
||||
for prop_name in VALID_CLIENT[client_type]['string']:
|
||||
if client_info[prop_name].startswith('[['):
|
||||
raise InvalidClientSecretsError(
|
||||
'Property "%s" is not configured.' % prop_name)
|
||||
return client_type, client_info
|
||||
|
||||
|
||||
def load(fp):
|
||||
obj = simplejson.load(fp)
|
||||
return _validate_clientsecrets(obj)
|
||||
|
||||
|
||||
def loads(s):
|
||||
obj = simplejson.loads(s)
|
||||
return _validate_clientsecrets(obj)
|
||||
|
||||
|
||||
def _loadfile(filename):
|
||||
try:
|
||||
fp = file(filename, 'r')
|
||||
try:
|
||||
obj = simplejson.load(fp)
|
||||
finally:
|
||||
fp.close()
|
||||
except IOError:
|
||||
raise InvalidClientSecretsError('File not found: "%s"' % filename)
|
||||
return _validate_clientsecrets(obj)
|
||||
|
||||
|
||||
def loadfile(filename, cache=None):
|
||||
"""Loading of client_secrets JSON file, optionally backed by a cache.
|
||||
|
||||
Typical cache storage would be App Engine memcache service,
|
||||
but you can pass in any other cache client that implements
|
||||
these methods:
|
||||
- get(key, namespace=ns)
|
||||
- set(key, value, namespace=ns)
|
||||
|
||||
Usage:
|
||||
# without caching
|
||||
client_type, client_info = loadfile('secrets.json')
|
||||
# using App Engine memcache service
|
||||
from google.appengine.api import memcache
|
||||
client_type, client_info = loadfile('secrets.json', cache=memcache)
|
||||
|
||||
Args:
|
||||
filename: string, Path to a client_secrets.json file on a filesystem.
|
||||
cache: An optional cache service client that implements get() and set()
|
||||
methods. If not specified, the file is always being loaded from
|
||||
a filesystem.
|
||||
|
||||
Raises:
|
||||
InvalidClientSecretsError: In case of a validation error or some
|
||||
I/O failure. Can happen only on cache miss.
|
||||
|
||||
Returns:
|
||||
(client_type, client_info) tuple, as _loadfile() normally would.
|
||||
JSON contents is validated only during first load. Cache hits are not
|
||||
validated.
|
||||
"""
|
||||
_SECRET_NAMESPACE = 'oauth2client:secrets#ns'
|
||||
|
||||
if not cache:
|
||||
return _loadfile(filename)
|
||||
|
||||
obj = cache.get(filename, namespace=_SECRET_NAMESPACE)
|
||||
if obj is None:
|
||||
client_type, client_info = _loadfile(filename)
|
||||
obj = {client_type: client_info}
|
||||
cache.set(filename, obj, namespace=_SECRET_NAMESPACE)
|
||||
|
||||
return obj.iteritems().next()
|
|
@ -0,0 +1,377 @@
|
|||
#!/usr/bin/python2.4
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (C) 2011 Google Inc.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import base64
|
||||
import hashlib
|
||||
import logging
|
||||
import time
|
||||
|
||||
from anyjson import simplejson
|
||||
|
||||
|
||||
CLOCK_SKEW_SECS = 300 # 5 minutes in seconds
|
||||
AUTH_TOKEN_LIFETIME_SECS = 300 # 5 minutes in seconds
|
||||
MAX_TOKEN_LIFETIME_SECS = 86400 # 1 day in seconds
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class AppIdentityError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
try:
|
||||
from OpenSSL import crypto
|
||||
|
||||
|
||||
class OpenSSLVerifier(object):
|
||||
"""Verifies the signature on a message."""
|
||||
|
||||
def __init__(self, pubkey):
|
||||
"""Constructor.
|
||||
|
||||
Args:
|
||||
pubkey, OpenSSL.crypto.PKey, The public key to verify with.
|
||||
"""
|
||||
self._pubkey = pubkey
|
||||
|
||||
def verify(self, message, signature):
|
||||
"""Verifies a message against a signature.
|
||||
|
||||
Args:
|
||||
message: string, The message to verify.
|
||||
signature: string, The signature on the message.
|
||||
|
||||
Returns:
|
||||
True if message was signed by the private key associated with the public
|
||||
key that this object was constructed with.
|
||||
"""
|
||||
try:
|
||||
crypto.verify(self._pubkey, signature, message, 'sha256')
|
||||
return True
|
||||
except:
|
||||
return False
|
||||
|
||||
@staticmethod
|
||||
def from_string(key_pem, is_x509_cert):
|
||||
"""Construct a Verified instance from a string.
|
||||
|
||||
Args:
|
||||
key_pem: string, public key in PEM format.
|
||||
is_x509_cert: bool, True if key_pem is an X509 cert, otherwise it is
|
||||
expected to be an RSA key in PEM format.
|
||||
|
||||
Returns:
|
||||
Verifier instance.
|
||||
|
||||
Raises:
|
||||
OpenSSL.crypto.Error if the key_pem can't be parsed.
|
||||
"""
|
||||
if is_x509_cert:
|
||||
pubkey = crypto.load_certificate(crypto.FILETYPE_PEM, key_pem)
|
||||
else:
|
||||
pubkey = crypto.load_privatekey(crypto.FILETYPE_PEM, key_pem)
|
||||
return OpenSSLVerifier(pubkey)
|
||||
|
||||
|
||||
class OpenSSLSigner(object):
|
||||
"""Signs messages with a private key."""
|
||||
|
||||
def __init__(self, pkey):
|
||||
"""Constructor.
|
||||
|
||||
Args:
|
||||
pkey, OpenSSL.crypto.PKey (or equiv), The private key to sign with.
|
||||
"""
|
||||
self._key = pkey
|
||||
|
||||
def sign(self, message):
|
||||
"""Signs a message.
|
||||
|
||||
Args:
|
||||
message: string, Message to be signed.
|
||||
|
||||
Returns:
|
||||
string, The signature of the message for the given key.
|
||||
"""
|
||||
return crypto.sign(self._key, message, 'sha256')
|
||||
|
||||
@staticmethod
|
||||
def from_string(key, password='notasecret'):
|
||||
"""Construct a Signer instance from a string.
|
||||
|
||||
Args:
|
||||
key: string, private key in PKCS12 or PEM format.
|
||||
password: string, password for the private key file.
|
||||
|
||||
Returns:
|
||||
Signer instance.
|
||||
|
||||
Raises:
|
||||
OpenSSL.crypto.Error if the key can't be parsed.
|
||||
"""
|
||||
if key.startswith('-----BEGIN '):
|
||||
pkey = crypto.load_privatekey(crypto.FILETYPE_PEM, key)
|
||||
else:
|
||||
pkey = crypto.load_pkcs12(key, password).get_privatekey()
|
||||
return OpenSSLSigner(pkey)
|
||||
|
||||
except ImportError:
|
||||
OpenSSLVerifier = None
|
||||
OpenSSLSigner = None
|
||||
|
||||
|
||||
try:
|
||||
from Crypto.PublicKey import RSA
|
||||
from Crypto.Hash import SHA256
|
||||
from Crypto.Signature import PKCS1_v1_5
|
||||
|
||||
|
||||
class PyCryptoVerifier(object):
|
||||
"""Verifies the signature on a message."""
|
||||
|
||||
def __init__(self, pubkey):
|
||||
"""Constructor.
|
||||
|
||||
Args:
|
||||
pubkey, OpenSSL.crypto.PKey (or equiv), The public key to verify with.
|
||||
"""
|
||||
self._pubkey = pubkey
|
||||
|
||||
def verify(self, message, signature):
|
||||
"""Verifies a message against a signature.
|
||||
|
||||
Args:
|
||||
message: string, The message to verify.
|
||||
signature: string, The signature on the message.
|
||||
|
||||
Returns:
|
||||
True if message was signed by the private key associated with the public
|
||||
key that this object was constructed with.
|
||||
"""
|
||||
try:
|
||||
return PKCS1_v1_5.new(self._pubkey).verify(
|
||||
SHA256.new(message), signature)
|
||||
except:
|
||||
return False
|
||||
|
||||
@staticmethod
|
||||
def from_string(key_pem, is_x509_cert):
|
||||
"""Construct a Verified instance from a string.
|
||||
|
||||
Args:
|
||||
key_pem: string, public key in PEM format.
|
||||
is_x509_cert: bool, True if key_pem is an X509 cert, otherwise it is
|
||||
expected to be an RSA key in PEM format.
|
||||
|
||||
Returns:
|
||||
Verifier instance.
|
||||
|
||||
Raises:
|
||||
NotImplementedError if is_x509_cert is true.
|
||||
"""
|
||||
if is_x509_cert:
|
||||
raise NotImplementedError(
|
||||
'X509 certs are not supported by the PyCrypto library. '
|
||||
'Try using PyOpenSSL if native code is an option.')
|
||||
else:
|
||||
pubkey = RSA.importKey(key_pem)
|
||||
return PyCryptoVerifier(pubkey)
|
||||
|
||||
|
||||
class PyCryptoSigner(object):
|
||||
"""Signs messages with a private key."""
|
||||
|
||||
def __init__(self, pkey):
|
||||
"""Constructor.
|
||||
|
||||
Args:
|
||||
pkey, OpenSSL.crypto.PKey (or equiv), The private key to sign with.
|
||||
"""
|
||||
self._key = pkey
|
||||
|
||||
def sign(self, message):
|
||||
"""Signs a message.
|
||||
|
||||
Args:
|
||||
message: string, Message to be signed.
|
||||
|
||||
Returns:
|
||||
string, The signature of the message for the given key.
|
||||
"""
|
||||
return PKCS1_v1_5.new(self._key).sign(SHA256.new(message))
|
||||
|
||||
@staticmethod
|
||||
def from_string(key, password='notasecret'):
|
||||
"""Construct a Signer instance from a string.
|
||||
|
||||
Args:
|
||||
key: string, private key in PEM format.
|
||||
password: string, password for private key file. Unused for PEM files.
|
||||
|
||||
Returns:
|
||||
Signer instance.
|
||||
|
||||
Raises:
|
||||
NotImplementedError if they key isn't in PEM format.
|
||||
"""
|
||||
if key.startswith('-----BEGIN '):
|
||||
pkey = RSA.importKey(key)
|
||||
else:
|
||||
raise NotImplementedError(
|
||||
'PKCS12 format is not supported by the PyCrpto library. '
|
||||
'Try converting to a "PEM" '
|
||||
'(openssl pkcs12 -in xxxxx.p12 -nodes -nocerts > privatekey.pem) '
|
||||
'or using PyOpenSSL if native code is an option.')
|
||||
return PyCryptoSigner(pkey)
|
||||
|
||||
except ImportError:
|
||||
PyCryptoVerifier = None
|
||||
PyCryptoSigner = None
|
||||
|
||||
|
||||
if OpenSSLSigner:
|
||||
Signer = OpenSSLSigner
|
||||
Verifier = OpenSSLVerifier
|
||||
elif PyCryptoSigner:
|
||||
Signer = PyCryptoSigner
|
||||
Verifier = PyCryptoVerifier
|
||||
else:
|
||||
raise ImportError('No encryption library found. Please install either '
|
||||
'PyOpenSSL, or PyCrypto 2.6 or later')
|
||||
|
||||
|
||||
def _urlsafe_b64encode(raw_bytes):
|
||||
return base64.urlsafe_b64encode(raw_bytes).rstrip('=')
|
||||
|
||||
|
||||
def _urlsafe_b64decode(b64string):
|
||||
# Guard against unicode strings, which base64 can't handle.
|
||||
b64string = b64string.encode('ascii')
|
||||
padded = b64string + '=' * (4 - len(b64string) % 4)
|
||||
return base64.urlsafe_b64decode(padded)
|
||||
|
||||
|
||||
def _json_encode(data):
|
||||
return simplejson.dumps(data, separators = (',', ':'))
|
||||
|
||||
|
||||
def make_signed_jwt(signer, payload):
|
||||
"""Make a signed JWT.
|
||||
|
||||
See http://self-issued.info/docs/draft-jones-json-web-token.html.
|
||||
|
||||
Args:
|
||||
signer: crypt.Signer, Cryptographic signer.
|
||||
payload: dict, Dictionary of data to convert to JSON and then sign.
|
||||
|
||||
Returns:
|
||||
string, The JWT for the payload.
|
||||
"""
|
||||
header = {'typ': 'JWT', 'alg': 'RS256'}
|
||||
|
||||
segments = [
|
||||
_urlsafe_b64encode(_json_encode(header)),
|
||||
_urlsafe_b64encode(_json_encode(payload)),
|
||||
]
|
||||
signing_input = '.'.join(segments)
|
||||
|
||||
signature = signer.sign(signing_input)
|
||||
segments.append(_urlsafe_b64encode(signature))
|
||||
|
||||
logger.debug(str(segments))
|
||||
|
||||
return '.'.join(segments)
|
||||
|
||||
|
||||
def verify_signed_jwt_with_certs(jwt, certs, audience):
|
||||
"""Verify a JWT against public certs.
|
||||
|
||||
See http://self-issued.info/docs/draft-jones-json-web-token.html.
|
||||
|
||||
Args:
|
||||
jwt: string, A JWT.
|
||||
certs: dict, Dictionary where values of public keys in PEM format.
|
||||
audience: string, The audience, 'aud', that this JWT should contain. If
|
||||
None then the JWT's 'aud' parameter is not verified.
|
||||
|
||||
Returns:
|
||||
dict, The deserialized JSON payload in the JWT.
|
||||
|
||||
Raises:
|
||||
AppIdentityError if any checks are failed.
|
||||
"""
|
||||
segments = jwt.split('.')
|
||||
|
||||
if (len(segments) != 3):
|
||||
raise AppIdentityError(
|
||||
'Wrong number of segments in token: %s' % jwt)
|
||||
signed = '%s.%s' % (segments[0], segments[1])
|
||||
|
||||
signature = _urlsafe_b64decode(segments[2])
|
||||
|
||||
# Parse token.
|
||||
json_body = _urlsafe_b64decode(segments[1])
|
||||
try:
|
||||
parsed = simplejson.loads(json_body)
|
||||
except:
|
||||
raise AppIdentityError('Can\'t parse token: %s' % json_body)
|
||||
|
||||
# Check signature.
|
||||
verified = False
|
||||
for (keyname, pem) in certs.items():
|
||||
verifier = Verifier.from_string(pem, True)
|
||||
if (verifier.verify(signed, signature)):
|
||||
verified = True
|
||||
break
|
||||
if not verified:
|
||||
raise AppIdentityError('Invalid token signature: %s' % jwt)
|
||||
|
||||
# Check creation timestamp.
|
||||
iat = parsed.get('iat')
|
||||
if iat is None:
|
||||
raise AppIdentityError('No iat field in token: %s' % json_body)
|
||||
earliest = iat - CLOCK_SKEW_SECS
|
||||
|
||||
# Check expiration timestamp.
|
||||
now = long(time.time())
|
||||
exp = parsed.get('exp')
|
||||
if exp is None:
|
||||
raise AppIdentityError('No exp field in token: %s' % json_body)
|
||||
if exp >= now + MAX_TOKEN_LIFETIME_SECS:
|
||||
raise AppIdentityError(
|
||||
'exp field too far in future: %s' % json_body)
|
||||
latest = exp + CLOCK_SKEW_SECS
|
||||
|
||||
if now < earliest:
|
||||
raise AppIdentityError('Token used too early, %d < %d: %s' %
|
||||
(now, earliest, json_body))
|
||||
if now > latest:
|
||||
raise AppIdentityError('Token used too late, %d > %d: %s' %
|
||||
(now, latest, json_body))
|
||||
|
||||
# Check audience.
|
||||
if audience is not None:
|
||||
aud = parsed.get('aud')
|
||||
if aud is None:
|
||||
raise AppIdentityError('No aud field in token: %s' % json_body)
|
||||
if aud != audience:
|
||||
raise AppIdentityError('Wrong recipient, %s != %s: %s' %
|
||||
(aud, audience, json_body))
|
||||
|
||||
return parsed
|
|
@ -0,0 +1,134 @@
|
|||
# Copyright (C) 2010 Google Inc.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
"""OAuth 2.0 utilities for Django.
|
||||
|
||||
Utilities for using OAuth 2.0 in conjunction with
|
||||
the Django datastore.
|
||||
"""
|
||||
|
||||
__author__ = 'jcgregorio@google.com (Joe Gregorio)'
|
||||
|
||||
import oauth2client
|
||||
import base64
|
||||
import pickle
|
||||
|
||||
from django.db import models
|
||||
from oauth2client.client import Storage as BaseStorage
|
||||
|
||||
class CredentialsField(models.Field):
|
||||
|
||||
__metaclass__ = models.SubfieldBase
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
if 'null' not in kwargs:
|
||||
kwargs['null'] = True
|
||||
super(CredentialsField, self).__init__(*args, **kwargs)
|
||||
|
||||
def get_internal_type(self):
|
||||
return "TextField"
|
||||
|
||||
def to_python(self, value):
|
||||
if value is None:
|
||||
return None
|
||||
if isinstance(value, oauth2client.client.Credentials):
|
||||
return value
|
||||
return pickle.loads(base64.b64decode(value))
|
||||
|
||||
def get_db_prep_value(self, value, connection, prepared=False):
|
||||
if value is None:
|
||||
return None
|
||||
return base64.b64encode(pickle.dumps(value))
|
||||
|
||||
|
||||
class FlowField(models.Field):
|
||||
|
||||
__metaclass__ = models.SubfieldBase
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
if 'null' not in kwargs:
|
||||
kwargs['null'] = True
|
||||
super(FlowField, self).__init__(*args, **kwargs)
|
||||
|
||||
def get_internal_type(self):
|
||||
return "TextField"
|
||||
|
||||
def to_python(self, value):
|
||||
if value is None:
|
||||
return None
|
||||
if isinstance(value, oauth2client.client.Flow):
|
||||
return value
|
||||
return pickle.loads(base64.b64decode(value))
|
||||
|
||||
def get_db_prep_value(self, value, connection, prepared=False):
|
||||
if value is None:
|
||||
return None
|
||||
return base64.b64encode(pickle.dumps(value))
|
||||
|
||||
|
||||
class Storage(BaseStorage):
|
||||
"""Store and retrieve a single credential to and from
|
||||
the datastore.
|
||||
|
||||
This Storage helper presumes the Credentials
|
||||
have been stored as a CredenialsField
|
||||
on a db model class.
|
||||
"""
|
||||
|
||||
def __init__(self, model_class, key_name, key_value, property_name):
|
||||
"""Constructor for Storage.
|
||||
|
||||
Args:
|
||||
model: db.Model, model class
|
||||
key_name: string, key name for the entity that has the credentials
|
||||
key_value: string, key value for the entity that has the credentials
|
||||
property_name: string, name of the property that is an CredentialsProperty
|
||||
"""
|
||||
self.model_class = model_class
|
||||
self.key_name = key_name
|
||||
self.key_value = key_value
|
||||
self.property_name = property_name
|
||||
|
||||
def locked_get(self):
|
||||
"""Retrieve Credential from datastore.
|
||||
|
||||
Returns:
|
||||
oauth2client.Credentials
|
||||
"""
|
||||
credential = None
|
||||
|
||||
query = {self.key_name: self.key_value}
|
||||
entities = self.model_class.objects.filter(**query)
|
||||
if len(entities) > 0:
|
||||
credential = getattr(entities[0], self.property_name)
|
||||
if credential and hasattr(credential, 'set_store'):
|
||||
credential.set_store(self)
|
||||
return credential
|
||||
|
||||
def locked_put(self, credentials):
|
||||
"""Write a Credentials to the datastore.
|
||||
|
||||
Args:
|
||||
credentials: Credentials, the credentials to store.
|
||||
"""
|
||||
args = {self.key_name: self.key_value}
|
||||
entity = self.model_class(**args)
|
||||
setattr(entity, self.property_name, credentials)
|
||||
entity.save()
|
||||
|
||||
def locked_delete(self):
|
||||
"""Delete Credentials from the datastore."""
|
||||
|
||||
query = {self.key_name: self.key_value}
|
||||
entities = self.model_class.objects.filter(**query).delete()
|
|
@ -0,0 +1,124 @@
|
|||
# Copyright (C) 2010 Google Inc.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
"""Utilities for OAuth.
|
||||
|
||||
Utilities for making it easier to work with OAuth 2.0
|
||||
credentials.
|
||||
"""
|
||||
|
||||
__author__ = 'jcgregorio@google.com (Joe Gregorio)'
|
||||
|
||||
import os
|
||||
import stat
|
||||
import threading
|
||||
|
||||
from anyjson import simplejson
|
||||
from client import Storage as BaseStorage
|
||||
from client import Credentials
|
||||
|
||||
|
||||
class CredentialsFileSymbolicLinkError(Exception):
|
||||
"""Credentials files must not be symbolic links."""
|
||||
|
||||
|
||||
class Storage(BaseStorage):
|
||||
"""Store and retrieve a single credential to and from a file."""
|
||||
|
||||
def __init__(self, filename):
|
||||
self._filename = filename
|
||||
self._lock = threading.Lock()
|
||||
|
||||
def _validate_file(self):
|
||||
if os.path.islink(self._filename):
|
||||
raise CredentialsFileSymbolicLinkError(
|
||||
'File: %s is a symbolic link.' % self._filename)
|
||||
|
||||
def acquire_lock(self):
|
||||
"""Acquires any lock necessary to access this Storage.
|
||||
|
||||
This lock is not reentrant."""
|
||||
self._lock.acquire()
|
||||
|
||||
def release_lock(self):
|
||||
"""Release the Storage lock.
|
||||
|
||||
Trying to release a lock that isn't held will result in a
|
||||
RuntimeError.
|
||||
"""
|
||||
self._lock.release()
|
||||
|
||||
def locked_get(self):
|
||||
"""Retrieve Credential from file.
|
||||
|
||||
Returns:
|
||||
oauth2client.client.Credentials
|
||||
|
||||
Raises:
|
||||
CredentialsFileSymbolicLinkError if the file is a symbolic link.
|
||||
"""
|
||||
credentials = None
|
||||
self._validate_file()
|
||||
try:
|
||||
f = open(self._filename, 'rb')
|
||||
content = f.read()
|
||||
f.close()
|
||||
except IOError:
|
||||
return credentials
|
||||
|
||||
try:
|
||||
credentials = Credentials.new_from_json(content)
|
||||
credentials.set_store(self)
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
return credentials
|
||||
|
||||
def _create_file_if_needed(self):
|
||||
"""Create an empty file if necessary.
|
||||
|
||||
This method will not initialize the file. Instead it implements a
|
||||
simple version of "touch" to ensure the file has been created.
|
||||
"""
|
||||
if not os.path.exists(self._filename):
|
||||
old_umask = os.umask(0177)
|
||||
try:
|
||||
open(self._filename, 'a+b').close()
|
||||
finally:
|
||||
os.umask(old_umask)
|
||||
|
||||
def locked_put(self, credentials):
|
||||
"""Write Credentials to file.
|
||||
|
||||
Args:
|
||||
credentials: Credentials, the credentials to store.
|
||||
|
||||
Raises:
|
||||
CredentialsFileSymbolicLinkError if the file is a symbolic link.
|
||||
"""
|
||||
|
||||
self._create_file_if_needed()
|
||||
self._validate_file()
|
||||
f = open(self._filename, 'wb')
|
||||
f.write(credentials.to_json())
|
||||
f.close()
|
||||
|
||||
def locked_delete(self):
|
||||
"""Delete Credentials file.
|
||||
|
||||
Args:
|
||||
credentials: Credentials, the credentials to store.
|
||||
"""
|
||||
|
||||
os.unlink(self._filename)
|
|
@ -0,0 +1,90 @@
|
|||
# Copyright (C) 2012 Google Inc.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
"""Utilities for Google Compute Engine
|
||||
|
||||
Utilities for making it easier to use OAuth 2.0 on Google Compute Engine.
|
||||
"""
|
||||
|
||||
__author__ = 'jcgregorio@google.com (Joe Gregorio)'
|
||||
|
||||
import httplib2
|
||||
import logging
|
||||
import uritemplate
|
||||
|
||||
from oauth2client import util
|
||||
from oauth2client.anyjson import simplejson
|
||||
from oauth2client.client import AccessTokenRefreshError
|
||||
from oauth2client.client import AssertionCredentials
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# URI Template for the endpoint that returns access_tokens.
|
||||
META = ('http://metadata.google.internal/0.1/meta-data/service-accounts/'
|
||||
'default/acquire{?scope}')
|
||||
|
||||
|
||||
class AppAssertionCredentials(AssertionCredentials):
|
||||
"""Credentials object for Compute Engine Assertion Grants
|
||||
|
||||
This object will allow a Compute Engine instance to identify itself to
|
||||
Google and other OAuth 2.0 servers that can verify assertions. It can be used
|
||||
for the purpose of accessing data stored under an account assigned to the
|
||||
Compute Engine instance itself.
|
||||
|
||||
This credential does not require a flow to instantiate because it represents
|
||||
a two legged flow, and therefore has all of the required information to
|
||||
generate and refresh its own access tokens.
|
||||
"""
|
||||
|
||||
@util.positional(2)
|
||||
def __init__(self, scope, **kwargs):
|
||||
"""Constructor for AppAssertionCredentials
|
||||
|
||||
Args:
|
||||
scope: string or iterable of strings, scope(s) of the credentials being
|
||||
requested.
|
||||
"""
|
||||
self.scope = util.scopes_to_string(scope)
|
||||
|
||||
# Assertion type is no longer used, but still in the parent class signature.
|
||||
super(AppAssertionCredentials, self).__init__(None)
|
||||
|
||||
@classmethod
|
||||
def from_json(cls, json):
|
||||
data = simplejson.loads(json)
|
||||
return AppAssertionCredentials(data['scope'])
|
||||
|
||||
def _refresh(self, http_request):
|
||||
"""Refreshes the access_token.
|
||||
|
||||
Skip all the storage hoops and just refresh using the API.
|
||||
|
||||
Args:
|
||||
http_request: callable, a callable that matches the method signature of
|
||||
httplib2.Http.request, used to make the refresh request.
|
||||
|
||||
Raises:
|
||||
AccessTokenRefreshError: When the refresh fails.
|
||||
"""
|
||||
uri = uritemplate.expand(META, {'scope': self.scope})
|
||||
response, content = http_request(uri)
|
||||
if response.status == 200:
|
||||
try:
|
||||
d = simplejson.loads(content)
|
||||
except StandardError, e:
|
||||
raise AccessTokenRefreshError(str(e))
|
||||
self.access_token = d['accessToken']
|
||||
else:
|
||||
raise AccessTokenRefreshError(content)
|
|
@ -0,0 +1,109 @@
|
|||
# Copyright (C) 2012 Google Inc.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
"""A keyring based Storage.
|
||||
|
||||
A Storage for Credentials that uses the keyring module.
|
||||
"""
|
||||
|
||||
__author__ = 'jcgregorio@google.com (Joe Gregorio)'
|
||||
|
||||
import keyring
|
||||
import threading
|
||||
|
||||
from client import Storage as BaseStorage
|
||||
from client import Credentials
|
||||
|
||||
|
||||
class Storage(BaseStorage):
|
||||
"""Store and retrieve a single credential to and from the keyring.
|
||||
|
||||
To use this module you must have the keyring module installed. See
|
||||
<http://pypi.python.org/pypi/keyring/>. This is an optional module and is not
|
||||
installed with oauth2client by default because it does not work on all the
|
||||
platforms that oauth2client supports, such as Google App Engine.
|
||||
|
||||
The keyring module <http://pypi.python.org/pypi/keyring/> is a cross-platform
|
||||
library for access the keyring capabilities of the local system. The user will
|
||||
be prompted for their keyring password when this module is used, and the
|
||||
manner in which the user is prompted will vary per platform.
|
||||
|
||||
Usage:
|
||||
from oauth2client.keyring_storage import Storage
|
||||
|
||||
s = Storage('name_of_application', 'user1')
|
||||
credentials = s.get()
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, service_name, user_name):
|
||||
"""Constructor.
|
||||
|
||||
Args:
|
||||
service_name: string, The name of the service under which the credentials
|
||||
are stored.
|
||||
user_name: string, The name of the user to store credentials for.
|
||||
"""
|
||||
self._service_name = service_name
|
||||
self._user_name = user_name
|
||||
self._lock = threading.Lock()
|
||||
|
||||
def acquire_lock(self):
|
||||
"""Acquires any lock necessary to access this Storage.
|
||||
|
||||
This lock is not reentrant."""
|
||||
self._lock.acquire()
|
||||
|
||||
def release_lock(self):
|
||||
"""Release the Storage lock.
|
||||
|
||||
Trying to release a lock that isn't held will result in a
|
||||
RuntimeError.
|
||||
"""
|
||||
self._lock.release()
|
||||
|
||||
def locked_get(self):
|
||||
"""Retrieve Credential from file.
|
||||
|
||||
Returns:
|
||||
oauth2client.client.Credentials
|
||||
"""
|
||||
credentials = None
|
||||
content = keyring.get_password(self._service_name, self._user_name)
|
||||
|
||||
if content is not None:
|
||||
try:
|
||||
credentials = Credentials.new_from_json(content)
|
||||
credentials.set_store(self)
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
return credentials
|
||||
|
||||
def locked_put(self, credentials):
|
||||
"""Write Credentials to file.
|
||||
|
||||
Args:
|
||||
credentials: Credentials, the credentials to store.
|
||||
"""
|
||||
keyring.set_password(self._service_name, self._user_name,
|
||||
credentials.to_json())
|
||||
|
||||
def locked_delete(self):
|
||||
"""Delete Credentials file.
|
||||
|
||||
Args:
|
||||
credentials: Credentials, the credentials to store.
|
||||
"""
|
||||
keyring.set_password(self._service_name, self._user_name, '')
|
|
@ -0,0 +1,361 @@
|
|||
# Copyright 2011 Google Inc. All Rights Reserved.
|
||||
|
||||
"""Locked file interface that should work on Unix and Windows pythons.
|
||||
|
||||
This module first tries to use fcntl locking to ensure serialized access
|
||||
to a file, then falls back on a lock file if that is unavialable.
|
||||
|
||||
Usage:
|
||||
f = LockedFile('filename', 'r+b', 'rb')
|
||||
f.open_and_lock()
|
||||
if f.is_locked():
|
||||
print 'Acquired filename with r+b mode'
|
||||
f.file_handle().write('locked data')
|
||||
else:
|
||||
print 'Aquired filename with rb mode'
|
||||
f.unlock_and_close()
|
||||
"""
|
||||
|
||||
__author__ = 'cache@google.com (David T McWherter)'
|
||||
|
||||
import errno
|
||||
import logging
|
||||
import os
|
||||
import time
|
||||
|
||||
from oauth2client import util
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class CredentialsFileSymbolicLinkError(Exception):
|
||||
"""Credentials files must not be symbolic links."""
|
||||
|
||||
|
||||
class AlreadyLockedException(Exception):
|
||||
"""Trying to lock a file that has already been locked by the LockedFile."""
|
||||
pass
|
||||
|
||||
|
||||
def validate_file(filename):
|
||||
if os.path.islink(filename):
|
||||
raise CredentialsFileSymbolicLinkError(
|
||||
'File: %s is a symbolic link.' % filename)
|
||||
|
||||
class _Opener(object):
|
||||
"""Base class for different locking primitives."""
|
||||
|
||||
def __init__(self, filename, mode, fallback_mode):
|
||||
"""Create an Opener.
|
||||
|
||||
Args:
|
||||
filename: string, The pathname of the file.
|
||||
mode: string, The preferred mode to access the file with.
|
||||
fallback_mode: string, The mode to use if locking fails.
|
||||
"""
|
||||
self._locked = False
|
||||
self._filename = filename
|
||||
self._mode = mode
|
||||
self._fallback_mode = fallback_mode
|
||||
self._fh = None
|
||||
|
||||
def is_locked(self):
|
||||
"""Was the file locked."""
|
||||
return self._locked
|
||||
|
||||
def file_handle(self):
|
||||
"""The file handle to the file. Valid only after opened."""
|
||||
return self._fh
|
||||
|
||||
def filename(self):
|
||||
"""The filename that is being locked."""
|
||||
return self._filename
|
||||
|
||||
def open_and_lock(self, timeout, delay):
|
||||
"""Open the file and lock it.
|
||||
|
||||
Args:
|
||||
timeout: float, How long to try to lock for.
|
||||
delay: float, How long to wait between retries.
|
||||
"""
|
||||
pass
|
||||
|
||||
def unlock_and_close(self):
|
||||
"""Unlock and close the file."""
|
||||
pass
|
||||
|
||||
|
||||
class _PosixOpener(_Opener):
|
||||
"""Lock files using Posix advisory lock files."""
|
||||
|
||||
def open_and_lock(self, timeout, delay):
|
||||
"""Open the file and lock it.
|
||||
|
||||
Tries to create a .lock file next to the file we're trying to open.
|
||||
|
||||
Args:
|
||||
timeout: float, How long to try to lock for.
|
||||
delay: float, How long to wait between retries.
|
||||
|
||||
Raises:
|
||||
AlreadyLockedException: if the lock is already acquired.
|
||||
IOError: if the open fails.
|
||||
CredentialsFileSymbolicLinkError if the file is a symbolic link.
|
||||
"""
|
||||
if self._locked:
|
||||
raise AlreadyLockedException('File %s is already locked' %
|
||||
self._filename)
|
||||
self._locked = False
|
||||
|
||||
validate_file(self._filename)
|
||||
try:
|
||||
self._fh = open(self._filename, self._mode)
|
||||
except IOError, e:
|
||||
# If we can't access with _mode, try _fallback_mode and don't lock.
|
||||
if e.errno == errno.EACCES:
|
||||
self._fh = open(self._filename, self._fallback_mode)
|
||||
return
|
||||
|
||||
lock_filename = self._posix_lockfile(self._filename)
|
||||
start_time = time.time()
|
||||
while True:
|
||||
try:
|
||||
self._lock_fd = os.open(lock_filename,
|
||||
os.O_CREAT|os.O_EXCL|os.O_RDWR)
|
||||
self._locked = True
|
||||
break
|
||||
|
||||
except OSError, e:
|
||||
if e.errno != errno.EEXIST:
|
||||
raise
|
||||
if (time.time() - start_time) >= timeout:
|
||||
logger.warn('Could not acquire lock %s in %s seconds' % (
|
||||
lock_filename, timeout))
|
||||
# Close the file and open in fallback_mode.
|
||||
if self._fh:
|
||||
self._fh.close()
|
||||
self._fh = open(self._filename, self._fallback_mode)
|
||||
return
|
||||
time.sleep(delay)
|
||||
|
||||
def unlock_and_close(self):
|
||||
"""Unlock a file by removing the .lock file, and close the handle."""
|
||||
if self._locked:
|
||||
lock_filename = self._posix_lockfile(self._filename)
|
||||
os.close(self._lock_fd)
|
||||
os.unlink(lock_filename)
|
||||
self._locked = False
|
||||
self._lock_fd = None
|
||||
if self._fh:
|
||||
self._fh.close()
|
||||
|
||||
def _posix_lockfile(self, filename):
|
||||
"""The name of the lock file to use for posix locking."""
|
||||
return '%s.lock' % filename
|
||||
|
||||
|
||||
try:
|
||||
import fcntl
|
||||
|
||||
class _FcntlOpener(_Opener):
|
||||
"""Open, lock, and unlock a file using fcntl.lockf."""
|
||||
|
||||
def open_and_lock(self, timeout, delay):
|
||||
"""Open the file and lock it.
|
||||
|
||||
Args:
|
||||
timeout: float, How long to try to lock for.
|
||||
delay: float, How long to wait between retries
|
||||
|
||||
Raises:
|
||||
AlreadyLockedException: if the lock is already acquired.
|
||||
IOError: if the open fails.
|
||||
CredentialsFileSymbolicLinkError if the file is a symbolic link.
|
||||
"""
|
||||
if self._locked:
|
||||
raise AlreadyLockedException('File %s is already locked' %
|
||||
self._filename)
|
||||
start_time = time.time()
|
||||
|
||||
validate_file(self._filename)
|
||||
try:
|
||||
self._fh = open(self._filename, self._mode)
|
||||
except IOError, e:
|
||||
# If we can't access with _mode, try _fallback_mode and don't lock.
|
||||
if e.errno == errno.EACCES:
|
||||
self._fh = open(self._filename, self._fallback_mode)
|
||||
return
|
||||
|
||||
# We opened in _mode, try to lock the file.
|
||||
while True:
|
||||
try:
|
||||
fcntl.lockf(self._fh.fileno(), fcntl.LOCK_EX)
|
||||
self._locked = True
|
||||
return
|
||||
except IOError, e:
|
||||
# If not retrying, then just pass on the error.
|
||||
if timeout == 0:
|
||||
raise e
|
||||
if e.errno != errno.EACCES:
|
||||
raise e
|
||||
# We could not acquire the lock. Try again.
|
||||
if (time.time() - start_time) >= timeout:
|
||||
logger.warn('Could not lock %s in %s seconds' % (
|
||||
self._filename, timeout))
|
||||
if self._fh:
|
||||
self._fh.close()
|
||||
self._fh = open(self._filename, self._fallback_mode)
|
||||
return
|
||||
time.sleep(delay)
|
||||
|
||||
def unlock_and_close(self):
|
||||
"""Close and unlock the file using the fcntl.lockf primitive."""
|
||||
if self._locked:
|
||||
fcntl.lockf(self._fh.fileno(), fcntl.LOCK_UN)
|
||||
self._locked = False
|
||||
if self._fh:
|
||||
self._fh.close()
|
||||
except ImportError:
|
||||
_FcntlOpener = None
|
||||
|
||||
|
||||
try:
|
||||
import pywintypes
|
||||
import win32con
|
||||
import win32file
|
||||
|
||||
class _Win32Opener(_Opener):
|
||||
"""Open, lock, and unlock a file using windows primitives."""
|
||||
|
||||
# Error #33:
|
||||
# 'The process cannot access the file because another process'
|
||||
FILE_IN_USE_ERROR = 33
|
||||
|
||||
# Error #158:
|
||||
# 'The segment is already unlocked.'
|
||||
FILE_ALREADY_UNLOCKED_ERROR = 158
|
||||
|
||||
def open_and_lock(self, timeout, delay):
|
||||
"""Open the file and lock it.
|
||||
|
||||
Args:
|
||||
timeout: float, How long to try to lock for.
|
||||
delay: float, How long to wait between retries
|
||||
|
||||
Raises:
|
||||
AlreadyLockedException: if the lock is already acquired.
|
||||
IOError: if the open fails.
|
||||
CredentialsFileSymbolicLinkError if the file is a symbolic link.
|
||||
"""
|
||||
if self._locked:
|
||||
raise AlreadyLockedException('File %s is already locked' %
|
||||
self._filename)
|
||||
start_time = time.time()
|
||||
|
||||
validate_file(self._filename)
|
||||
try:
|
||||
self._fh = open(self._filename, self._mode)
|
||||
except IOError, e:
|
||||
# If we can't access with _mode, try _fallback_mode and don't lock.
|
||||
if e.errno == errno.EACCES:
|
||||
self._fh = open(self._filename, self._fallback_mode)
|
||||
return
|
||||
|
||||
# We opened in _mode, try to lock the file.
|
||||
while True:
|
||||
try:
|
||||
hfile = win32file._get_osfhandle(self._fh.fileno())
|
||||
win32file.LockFileEx(
|
||||
hfile,
|
||||
(win32con.LOCKFILE_FAIL_IMMEDIATELY|
|
||||
win32con.LOCKFILE_EXCLUSIVE_LOCK), 0, -0x10000,
|
||||
pywintypes.OVERLAPPED())
|
||||
self._locked = True
|
||||
return
|
||||
except pywintypes.error, e:
|
||||
if timeout == 0:
|
||||
raise e
|
||||
|
||||
# If the error is not that the file is already in use, raise.
|
||||
if e[0] != _Win32Opener.FILE_IN_USE_ERROR:
|
||||
raise
|
||||
|
||||
# We could not acquire the lock. Try again.
|
||||
if (time.time() - start_time) >= timeout:
|
||||
logger.warn('Could not lock %s in %s seconds' % (
|
||||
self._filename, timeout))
|
||||
if self._fh:
|
||||
self._fh.close()
|
||||
self._fh = open(self._filename, self._fallback_mode)
|
||||
return
|
||||
time.sleep(delay)
|
||||
|
||||
def unlock_and_close(self):
|
||||
"""Close and unlock the file using the win32 primitive."""
|
||||
if self._locked:
|
||||
try:
|
||||
hfile = win32file._get_osfhandle(self._fh.fileno())
|
||||
win32file.UnlockFileEx(hfile, 0, -0x10000, pywintypes.OVERLAPPED())
|
||||
except pywintypes.error, e:
|
||||
if e[0] != _Win32Opener.FILE_ALREADY_UNLOCKED_ERROR:
|
||||
raise
|
||||
self._locked = False
|
||||
if self._fh:
|
||||
self._fh.close()
|
||||
except ImportError:
|
||||
_Win32Opener = None
|
||||
|
||||
|
||||
class LockedFile(object):
|
||||
"""Represent a file that has exclusive access."""
|
||||
|
||||
@util.positional(4)
|
||||
def __init__(self, filename, mode, fallback_mode, use_native_locking=True):
|
||||
"""Construct a LockedFile.
|
||||
|
||||
Args:
|
||||
filename: string, The path of the file to open.
|
||||
mode: string, The mode to try to open the file with.
|
||||
fallback_mode: string, The mode to use if locking fails.
|
||||
use_native_locking: bool, Whether or not fcntl/win32 locking is used.
|
||||
"""
|
||||
opener = None
|
||||
if not opener and use_native_locking:
|
||||
if _Win32Opener:
|
||||
opener = _Win32Opener(filename, mode, fallback_mode)
|
||||
if _FcntlOpener:
|
||||
opener = _FcntlOpener(filename, mode, fallback_mode)
|
||||
|
||||
if not opener:
|
||||
opener = _PosixOpener(filename, mode, fallback_mode)
|
||||
|
||||
self._opener = opener
|
||||
|
||||
def filename(self):
|
||||
"""Return the filename we were constructed with."""
|
||||
return self._opener._filename
|
||||
|
||||
def file_handle(self):
|
||||
"""Return the file_handle to the opened file."""
|
||||
return self._opener.file_handle()
|
||||
|
||||
def is_locked(self):
|
||||
"""Return whether we successfully locked the file."""
|
||||
return self._opener.is_locked()
|
||||
|
||||
def open_and_lock(self, timeout=0, delay=0.05):
|
||||
"""Open the file, trying to lock it.
|
||||
|
||||
Args:
|
||||
timeout: float, The number of seconds to try to acquire the lock.
|
||||
delay: float, The number of seconds to wait between retry attempts.
|
||||
|
||||
Raises:
|
||||
AlreadyLockedException: if the lock is already acquired.
|
||||
IOError: if the open fails.
|
||||
"""
|
||||
self._opener.open_and_lock(timeout, delay)
|
||||
|
||||
def unlock_and_close(self):
|
||||
"""Unlock and close a file."""
|
||||
self._opener.unlock_and_close()
|
|
@ -0,0 +1,409 @@
|
|||
# Copyright 2011 Google Inc. All Rights Reserved.
|
||||
|
||||
"""Multi-credential file store with lock support.
|
||||
|
||||
This module implements a JSON credential store where multiple
|
||||
credentials can be stored in one file. That file supports locking
|
||||
both in a single process and across processes.
|
||||
|
||||
The credential themselves are keyed off of:
|
||||
* client_id
|
||||
* user_agent
|
||||
* scope
|
||||
|
||||
The format of the stored data is like so:
|
||||
{
|
||||
'file_version': 1,
|
||||
'data': [
|
||||
{
|
||||
'key': {
|
||||
'clientId': '<client id>',
|
||||
'userAgent': '<user agent>',
|
||||
'scope': '<scope>'
|
||||
},
|
||||
'credential': {
|
||||
# JSON serialized Credentials.
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
"""
|
||||
|
||||
__author__ = 'jbeda@google.com (Joe Beda)'
|
||||
|
||||
import base64
|
||||
import errno
|
||||
import logging
|
||||
import os
|
||||
import threading
|
||||
|
||||
from anyjson import simplejson
|
||||
from oauth2client.client import Storage as BaseStorage
|
||||
from oauth2client.client import Credentials
|
||||
from oauth2client import util
|
||||
from locked_file import LockedFile
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# A dict from 'filename'->_MultiStore instances
|
||||
_multistores = {}
|
||||
_multistores_lock = threading.Lock()
|
||||
|
||||
|
||||
class Error(Exception):
|
||||
"""Base error for this module."""
|
||||
pass
|
||||
|
||||
|
||||
class NewerCredentialStoreError(Error):
|
||||
"""The credential store is a newer version that supported."""
|
||||
pass
|
||||
|
||||
|
||||
@util.positional(4)
|
||||
def get_credential_storage(filename, client_id, user_agent, scope,
|
||||
warn_on_readonly=True):
|
||||
"""Get a Storage instance for a credential.
|
||||
|
||||
Args:
|
||||
filename: The JSON file storing a set of credentials
|
||||
client_id: The client_id for the credential
|
||||
user_agent: The user agent for the credential
|
||||
scope: string or iterable of strings, Scope(s) being requested
|
||||
warn_on_readonly: if True, log a warning if the store is readonly
|
||||
|
||||
Returns:
|
||||
An object derived from client.Storage for getting/setting the
|
||||
credential.
|
||||
"""
|
||||
# Recreate the legacy key with these specific parameters
|
||||
key = {'clientId': client_id, 'userAgent': user_agent,
|
||||
'scope': util.scopes_to_string(scope)}
|
||||
return get_credential_storage_custom_key(
|
||||
filename, key, warn_on_readonly=warn_on_readonly)
|
||||
|
||||
|
||||
@util.positional(2)
|
||||
def get_credential_storage_custom_string_key(
|
||||
filename, key_string, warn_on_readonly=True):
|
||||
"""Get a Storage instance for a credential using a single string as a key.
|
||||
|
||||
Allows you to provide a string as a custom key that will be used for
|
||||
credential storage and retrieval.
|
||||
|
||||
Args:
|
||||
filename: The JSON file storing a set of credentials
|
||||
key_string: A string to use as the key for storing this credential.
|
||||
warn_on_readonly: if True, log a warning if the store is readonly
|
||||
|
||||
Returns:
|
||||
An object derived from client.Storage for getting/setting the
|
||||
credential.
|
||||
"""
|
||||
# Create a key dictionary that can be used
|
||||
key_dict = {'key': key_string}
|
||||
return get_credential_storage_custom_key(
|
||||
filename, key_dict, warn_on_readonly=warn_on_readonly)
|
||||
|
||||
|
||||
@util.positional(2)
|
||||
def get_credential_storage_custom_key(
|
||||
filename, key_dict, warn_on_readonly=True):
|
||||
"""Get a Storage instance for a credential using a dictionary as a key.
|
||||
|
||||
Allows you to provide a dictionary as a custom key that will be used for
|
||||
credential storage and retrieval.
|
||||
|
||||
Args:
|
||||
filename: The JSON file storing a set of credentials
|
||||
key_dict: A dictionary to use as the key for storing this credential. There
|
||||
is no ordering of the keys in the dictionary. Logically equivalent
|
||||
dictionaries will produce equivalent storage keys.
|
||||
warn_on_readonly: if True, log a warning if the store is readonly
|
||||
|
||||
Returns:
|
||||
An object derived from client.Storage for getting/setting the
|
||||
credential.
|
||||
"""
|
||||
filename = os.path.expanduser(filename)
|
||||
_multistores_lock.acquire()
|
||||
try:
|
||||
multistore = _multistores.setdefault(
|
||||
filename, _MultiStore(filename, warn_on_readonly=warn_on_readonly))
|
||||
finally:
|
||||
_multistores_lock.release()
|
||||
key = util.dict_to_tuple_key(key_dict)
|
||||
return multistore._get_storage(key)
|
||||
|
||||
|
||||
class _MultiStore(object):
|
||||
"""A file backed store for multiple credentials."""
|
||||
|
||||
@util.positional(2)
|
||||
def __init__(self, filename, warn_on_readonly=True):
|
||||
"""Initialize the class.
|
||||
|
||||
This will create the file if necessary.
|
||||
"""
|
||||
self._file = LockedFile(filename, 'r+b', 'rb')
|
||||
self._thread_lock = threading.Lock()
|
||||
self._read_only = False
|
||||
self._warn_on_readonly = warn_on_readonly
|
||||
|
||||
self._create_file_if_needed()
|
||||
|
||||
# Cache of deserialized store. This is only valid after the
|
||||
# _MultiStore is locked or _refresh_data_cache is called. This is
|
||||
# of the form of:
|
||||
#
|
||||
# ((key, value), (key, value)...) -> OAuth2Credential
|
||||
#
|
||||
# If this is None, then the store hasn't been read yet.
|
||||
self._data = None
|
||||
|
||||
class _Storage(BaseStorage):
|
||||
"""A Storage object that knows how to read/write a single credential."""
|
||||
|
||||
def __init__(self, multistore, key):
|
||||
self._multistore = multistore
|
||||
self._key = key
|
||||
|
||||
def acquire_lock(self):
|
||||
"""Acquires any lock necessary to access this Storage.
|
||||
|
||||
This lock is not reentrant.
|
||||
"""
|
||||
self._multistore._lock()
|
||||
|
||||
def release_lock(self):
|
||||
"""Release the Storage lock.
|
||||
|
||||
Trying to release a lock that isn't held will result in a
|
||||
RuntimeError.
|
||||
"""
|
||||
self._multistore._unlock()
|
||||
|
||||
def locked_get(self):
|
||||
"""Retrieve credential.
|
||||
|
||||
The Storage lock must be held when this is called.
|
||||
|
||||
Returns:
|
||||
oauth2client.client.Credentials
|
||||
"""
|
||||
credential = self._multistore._get_credential(self._key)
|
||||
if credential:
|
||||
credential.set_store(self)
|
||||
return credential
|
||||
|
||||
def locked_put(self, credentials):
|
||||
"""Write a credential.
|
||||
|
||||
The Storage lock must be held when this is called.
|
||||
|
||||
Args:
|
||||
credentials: Credentials, the credentials to store.
|
||||
"""
|
||||
self._multistore._update_credential(self._key, credentials)
|
||||
|
||||
def locked_delete(self):
|
||||
"""Delete a credential.
|
||||
|
||||
The Storage lock must be held when this is called.
|
||||
|
||||
Args:
|
||||
credentials: Credentials, the credentials to store.
|
||||
"""
|
||||
self._multistore._delete_credential(self._key)
|
||||
|
||||
def _create_file_if_needed(self):
|
||||
"""Create an empty file if necessary.
|
||||
|
||||
This method will not initialize the file. Instead it implements a
|
||||
simple version of "touch" to ensure the file has been created.
|
||||
"""
|
||||
if not os.path.exists(self._file.filename()):
|
||||
old_umask = os.umask(0177)
|
||||
try:
|
||||
open(self._file.filename(), 'a+b').close()
|
||||
finally:
|
||||
os.umask(old_umask)
|
||||
|
||||
def _lock(self):
|
||||
"""Lock the entire multistore."""
|
||||
self._thread_lock.acquire()
|
||||
self._file.open_and_lock()
|
||||
if not self._file.is_locked():
|
||||
self._read_only = True
|
||||
if self._warn_on_readonly:
|
||||
logger.warn('The credentials file (%s) is not writable. Opening in '
|
||||
'read-only mode. Any refreshed credentials will only be '
|
||||
'valid for this run.' % self._file.filename())
|
||||
if os.path.getsize(self._file.filename()) == 0:
|
||||
logger.debug('Initializing empty multistore file')
|
||||
# The multistore is empty so write out an empty file.
|
||||
self._data = {}
|
||||
self._write()
|
||||
elif not self._read_only or self._data is None:
|
||||
# Only refresh the data if we are read/write or we haven't
|
||||
# cached the data yet. If we are readonly, we assume is isn't
|
||||
# changing out from under us and that we only have to read it
|
||||
# once. This prevents us from whacking any new access keys that
|
||||
# we have cached in memory but were unable to write out.
|
||||
self._refresh_data_cache()
|
||||
|
||||
def _unlock(self):
|
||||
"""Release the lock on the multistore."""
|
||||
self._file.unlock_and_close()
|
||||
self._thread_lock.release()
|
||||
|
||||
def _locked_json_read(self):
|
||||
"""Get the raw content of the multistore file.
|
||||
|
||||
The multistore must be locked when this is called.
|
||||
|
||||
Returns:
|
||||
The contents of the multistore decoded as JSON.
|
||||
"""
|
||||
assert self._thread_lock.locked()
|
||||
self._file.file_handle().seek(0)
|
||||
return simplejson.load(self._file.file_handle())
|
||||
|
||||
def _locked_json_write(self, data):
|
||||
"""Write a JSON serializable data structure to the multistore.
|
||||
|
||||
The multistore must be locked when this is called.
|
||||
|
||||
Args:
|
||||
data: The data to be serialized and written.
|
||||
"""
|
||||
assert self._thread_lock.locked()
|
||||
if self._read_only:
|
||||
return
|
||||
self._file.file_handle().seek(0)
|
||||
simplejson.dump(data, self._file.file_handle(), sort_keys=True, indent=2)
|
||||
self._file.file_handle().truncate()
|
||||
|
||||
def _refresh_data_cache(self):
|
||||
"""Refresh the contents of the multistore.
|
||||
|
||||
The multistore must be locked when this is called.
|
||||
|
||||
Raises:
|
||||
NewerCredentialStoreError: Raised when a newer client has written the
|
||||
store.
|
||||
"""
|
||||
self._data = {}
|
||||
try:
|
||||
raw_data = self._locked_json_read()
|
||||
except Exception:
|
||||
logger.warn('Credential data store could not be loaded. '
|
||||
'Will ignore and overwrite.')
|
||||
return
|
||||
|
||||
version = 0
|
||||
try:
|
||||
version = raw_data['file_version']
|
||||
except Exception:
|
||||
logger.warn('Missing version for credential data store. It may be '
|
||||
'corrupt or an old version. Overwriting.')
|
||||
if version > 1:
|
||||
raise NewerCredentialStoreError(
|
||||
'Credential file has file_version of %d. '
|
||||
'Only file_version of 1 is supported.' % version)
|
||||
|
||||
credentials = []
|
||||
try:
|
||||
credentials = raw_data['data']
|
||||
except (TypeError, KeyError):
|
||||
pass
|
||||
|
||||
for cred_entry in credentials:
|
||||
try:
|
||||
(key, credential) = self._decode_credential_from_json(cred_entry)
|
||||
self._data[key] = credential
|
||||
except:
|
||||
# If something goes wrong loading a credential, just ignore it
|
||||
logger.info('Error decoding credential, skipping', exc_info=True)
|
||||
|
||||
def _decode_credential_from_json(self, cred_entry):
|
||||
"""Load a credential from our JSON serialization.
|
||||
|
||||
Args:
|
||||
cred_entry: A dict entry from the data member of our format
|
||||
|
||||
Returns:
|
||||
(key, cred) where the key is the key tuple and the cred is the
|
||||
OAuth2Credential object.
|
||||
"""
|
||||
raw_key = cred_entry['key']
|
||||
key = util.dict_to_tuple_key(raw_key)
|
||||
credential = None
|
||||
credential = Credentials.new_from_json(simplejson.dumps(cred_entry['credential']))
|
||||
return (key, credential)
|
||||
|
||||
def _write(self):
|
||||
"""Write the cached data back out.
|
||||
|
||||
The multistore must be locked.
|
||||
"""
|
||||
raw_data = {'file_version': 1}
|
||||
raw_creds = []
|
||||
raw_data['data'] = raw_creds
|
||||
for (cred_key, cred) in self._data.items():
|
||||
raw_key = dict(cred_key)
|
||||
raw_cred = simplejson.loads(cred.to_json())
|
||||
raw_creds.append({'key': raw_key, 'credential': raw_cred})
|
||||
self._locked_json_write(raw_data)
|
||||
|
||||
def _get_credential(self, key):
|
||||
"""Get a credential from the multistore.
|
||||
|
||||
The multistore must be locked.
|
||||
|
||||
Args:
|
||||
key: The key used to retrieve the credential
|
||||
|
||||
Returns:
|
||||
The credential specified or None if not present
|
||||
"""
|
||||
return self._data.get(key, None)
|
||||
|
||||
def _update_credential(self, key, cred):
|
||||
"""Update a credential and write the multistore.
|
||||
|
||||
This must be called when the multistore is locked.
|
||||
|
||||
Args:
|
||||
key: The key used to retrieve the credential
|
||||
cred: The OAuth2Credential to update/set
|
||||
"""
|
||||
self._data[key] = cred
|
||||
self._write()
|
||||
|
||||
def _delete_credential(self, key):
|
||||
"""Delete a credential and write the multistore.
|
||||
|
||||
This must be called when the multistore is locked.
|
||||
|
||||
Args:
|
||||
key: The key used to retrieve the credential
|
||||
"""
|
||||
try:
|
||||
del self._data[key]
|
||||
except KeyError:
|
||||
pass
|
||||
self._write()
|
||||
|
||||
def _get_storage(self, key):
|
||||
"""Get a Storage object to get/set a credential.
|
||||
|
||||
This Storage is a 'view' into the multistore.
|
||||
|
||||
Args:
|
||||
key: The key used to retrieve the credential
|
||||
|
||||
Returns:
|
||||
A Storage object that can be used to get/set this cred
|
||||
"""
|
||||
return self._Storage(self, key)
|
|
@ -0,0 +1,205 @@
|
|||
# Copyright (C) 2010 Google Inc.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
"""Command-line tools for authenticating via OAuth 2.0
|
||||
|
||||
Do the OAuth 2.0 Web Server dance for a command line application. Stores the
|
||||
generated credentials in a common file that is used by other example apps in
|
||||
the same directory.
|
||||
"""
|
||||
|
||||
__author__ = 'jcgregorio@google.com (Joe Gregorio)'
|
||||
__all__ = ['run']
|
||||
|
||||
|
||||
import BaseHTTPServer
|
||||
from gflags import gflags
|
||||
import socket
|
||||
import sys
|
||||
import webbrowser
|
||||
|
||||
from oauth2client.client import FlowExchangeError
|
||||
from oauth2client.client import OOB_CALLBACK_URN
|
||||
from oauth2client import util
|
||||
|
||||
try:
|
||||
from urlparse import parse_qsl
|
||||
except ImportError:
|
||||
from cgi import parse_qsl
|
||||
|
||||
|
||||
FLAGS = gflags.FLAGS
|
||||
|
||||
gflags.DEFINE_boolean('auth_local_webserver', True,
|
||||
('Run a local web server to handle redirects during '
|
||||
'OAuth authorization.'))
|
||||
|
||||
gflags.DEFINE_string('auth_host_name', 'localhost',
|
||||
('Host name to use when running a local web server to '
|
||||
'handle redirects during OAuth authorization.'))
|
||||
|
||||
gflags.DEFINE_multi_int('auth_host_port', [8080, 8090],
|
||||
('Port to use when running a local web server to '
|
||||
'handle redirects during OAuth authorization.'))
|
||||
|
||||
|
||||
class ClientRedirectServer(BaseHTTPServer.HTTPServer):
|
||||
"""A server to handle OAuth 2.0 redirects back to localhost.
|
||||
|
||||
Waits for a single request and parses the query parameters
|
||||
into query_params and then stops serving.
|
||||
"""
|
||||
query_params = {}
|
||||
|
||||
|
||||
class ClientRedirectHandler(BaseHTTPServer.BaseHTTPRequestHandler):
|
||||
"""A handler for OAuth 2.0 redirects back to localhost.
|
||||
|
||||
Waits for a single request and parses the query parameters
|
||||
into the servers query_params and then stops serving.
|
||||
"""
|
||||
|
||||
def do_GET(s):
|
||||
"""Handle a GET request.
|
||||
|
||||
Parses the query parameters and prints a message
|
||||
if the flow has completed. Note that we can't detect
|
||||
if an error occurred.
|
||||
"""
|
||||
s.send_response(200)
|
||||
s.send_header("Content-type", "text/html")
|
||||
s.end_headers()
|
||||
query = s.path.split('?', 1)[-1]
|
||||
query = dict(parse_qsl(query))
|
||||
s.server.query_params = query
|
||||
s.wfile.write("<html><head><title>Authentication Status</title></head>")
|
||||
s.wfile.write("<body><p>The authentication flow has completed.</p>")
|
||||
s.wfile.write("</body></html>")
|
||||
|
||||
def log_message(self, format, *args):
|
||||
"""Do not log messages to stdout while running as command line program."""
|
||||
pass
|
||||
|
||||
|
||||
@util.positional(2)
|
||||
def run(flow, storage, http=None):
|
||||
"""Core code for a command-line application.
|
||||
|
||||
The run() function is called from your application and runs through all the
|
||||
steps to obtain credentials. It takes a Flow argument and attempts to open an
|
||||
authorization server page in the user's default web browser. The server asks
|
||||
the user to grant your application access to the user's data. If the user
|
||||
grants access, the run() function returns new credentials. The new credentials
|
||||
are also stored in the Storage argument, which updates the file associated
|
||||
with the Storage object.
|
||||
|
||||
It presumes it is run from a command-line application and supports the
|
||||
following flags:
|
||||
|
||||
--auth_host_name: Host name to use when running a local web server
|
||||
to handle redirects during OAuth authorization.
|
||||
(default: 'localhost')
|
||||
|
||||
--auth_host_port: Port to use when running a local web server to handle
|
||||
redirects during OAuth authorization.;
|
||||
repeat this option to specify a list of values
|
||||
(default: '[8080, 8090]')
|
||||
(an integer)
|
||||
|
||||
--[no]auth_local_webserver: Run a local web server to handle redirects
|
||||
during OAuth authorization.
|
||||
(default: 'true')
|
||||
|
||||
Since it uses flags make sure to initialize the gflags module before calling
|
||||
run().
|
||||
|
||||
Args:
|
||||
flow: Flow, an OAuth 2.0 Flow to step through.
|
||||
storage: Storage, a Storage to store the credential in.
|
||||
http: An instance of httplib2.Http.request
|
||||
or something that acts like it.
|
||||
|
||||
Returns:
|
||||
Credentials, the obtained credential.
|
||||
"""
|
||||
if FLAGS.auth_local_webserver:
|
||||
success = False
|
||||
port_number = 0
|
||||
for port in FLAGS.auth_host_port:
|
||||
port_number = port
|
||||
try:
|
||||
httpd = ClientRedirectServer((FLAGS.auth_host_name, port),
|
||||
ClientRedirectHandler)
|
||||
except socket.error, e:
|
||||
pass
|
||||
else:
|
||||
success = True
|
||||
break
|
||||
FLAGS.auth_local_webserver = success
|
||||
if not success:
|
||||
print 'Failed to start a local webserver listening on either port 8080'
|
||||
print 'or port 9090. Please check your firewall settings and locally'
|
||||
print 'running programs that may be blocking or using those ports.'
|
||||
print
|
||||
print 'Falling back to --noauth_local_webserver and continuing with',
|
||||
print 'authorization.'
|
||||
print
|
||||
|
||||
if FLAGS.auth_local_webserver:
|
||||
oauth_callback = 'http://%s:%s/' % (FLAGS.auth_host_name, port_number)
|
||||
else:
|
||||
oauth_callback = OOB_CALLBACK_URN
|
||||
flow.redirect_uri = oauth_callback
|
||||
authorize_url = flow.step1_get_authorize_url()
|
||||
|
||||
if FLAGS.auth_local_webserver:
|
||||
webbrowser.open(authorize_url, new=1, autoraise=True)
|
||||
print 'Your browser has been opened to visit:'
|
||||
print
|
||||
print ' ' + authorize_url
|
||||
print
|
||||
print 'If your browser is on a different machine then exit and re-run this'
|
||||
print 'application with the command-line parameter '
|
||||
print
|
||||
print ' --noauth_local_webserver'
|
||||
print
|
||||
else:
|
||||
print 'Go to the following link in your browser:'
|
||||
print
|
||||
print ' ' + authorize_url
|
||||
print
|
||||
|
||||
code = None
|
||||
if FLAGS.auth_local_webserver:
|
||||
httpd.handle_request()
|
||||
if 'error' in httpd.query_params:
|
||||
sys.exit('Authentication request was rejected.')
|
||||
if 'code' in httpd.query_params:
|
||||
code = httpd.query_params['code']
|
||||
else:
|
||||
print 'Failed to find "code" in the query parameters of the redirect.'
|
||||
sys.exit('Try running with --noauth_local_webserver.')
|
||||
else:
|
||||
code = raw_input('Enter verification code: ').strip()
|
||||
|
||||
try:
|
||||
credential = flow.step2_exchange(code, http=http)
|
||||
except FlowExchangeError, e:
|
||||
sys.exit('Authentication has failed: %s' % e)
|
||||
|
||||
storage.put(credential)
|
||||
credential.set_store(storage)
|
||||
print 'Authentication successful.'
|
||||
|
||||
return credential
|
|
@ -0,0 +1,192 @@
|
|||
#!/usr/bin/env python
|
||||
#
|
||||
# Copyright 2010 Google Inc.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
|
||||
"""Common utility library."""
|
||||
|
||||
__author__ = ['rafek@google.com (Rafe Kaplan)',
|
||||
'guido@google.com (Guido van Rossum)',
|
||||
]
|
||||
__all__ = [
|
||||
'positional',
|
||||
]
|
||||
|
||||
from gflags import gflags
|
||||
import inspect
|
||||
import logging
|
||||
import types
|
||||
import urllib
|
||||
import urlparse
|
||||
|
||||
try:
|
||||
from urlparse import parse_qsl
|
||||
except ImportError:
|
||||
from cgi import parse_qsl
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
FLAGS = gflags.FLAGS
|
||||
|
||||
gflags.DEFINE_enum('positional_parameters_enforcement', 'WARNING',
|
||||
['EXCEPTION', 'WARNING', 'IGNORE'],
|
||||
'The action when an oauth2client.util.positional declaration is violated.')
|
||||
|
||||
|
||||
def positional(max_positional_args):
|
||||
"""A decorator to declare that only the first N arguments my be positional.
|
||||
|
||||
This decorator makes it easy to support Python 3 style key-word only
|
||||
parameters. For example, in Python 3 it is possible to write:
|
||||
|
||||
def fn(pos1, *, kwonly1=None, kwonly1=None):
|
||||
...
|
||||
|
||||
All named parameters after * must be a keyword:
|
||||
|
||||
fn(10, 'kw1', 'kw2') # Raises exception.
|
||||
fn(10, kwonly1='kw1') # Ok.
|
||||
|
||||
Example:
|
||||
To define a function like above, do:
|
||||
|
||||
@positional(1)
|
||||
def fn(pos1, kwonly1=None, kwonly2=None):
|
||||
...
|
||||
|
||||
If no default value is provided to a keyword argument, it becomes a required
|
||||
keyword argument:
|
||||
|
||||
@positional(0)
|
||||
def fn(required_kw):
|
||||
...
|
||||
|
||||
This must be called with the keyword parameter:
|
||||
|
||||
fn() # Raises exception.
|
||||
fn(10) # Raises exception.
|
||||
fn(required_kw=10) # Ok.
|
||||
|
||||
When defining instance or class methods always remember to account for
|
||||
'self' and 'cls':
|
||||
|
||||
class MyClass(object):
|
||||
|
||||
@positional(2)
|
||||
def my_method(self, pos1, kwonly1=None):
|
||||
...
|
||||
|
||||
@classmethod
|
||||
@positional(2)
|
||||
def my_method(cls, pos1, kwonly1=None):
|
||||
...
|
||||
|
||||
The positional decorator behavior is controlled by the
|
||||
--positional_parameters_enforcement flag. The flag may be set to 'EXCEPTION',
|
||||
'WARNING' or 'IGNORE' to raise an exception, log a warning, or do nothing,
|
||||
respectively, if a declaration is violated.
|
||||
|
||||
Args:
|
||||
max_positional_arguments: Maximum number of positional arguments. All
|
||||
parameters after the this index must be keyword only.
|
||||
|
||||
Returns:
|
||||
A decorator that prevents using arguments after max_positional_args from
|
||||
being used as positional parameters.
|
||||
|
||||
Raises:
|
||||
TypeError if a key-word only argument is provided as a positional parameter,
|
||||
but only if the --positional_parameters_enforcement flag is set to
|
||||
'EXCEPTION'.
|
||||
"""
|
||||
def positional_decorator(wrapped):
|
||||
def positional_wrapper(*args, **kwargs):
|
||||
if len(args) > max_positional_args:
|
||||
plural_s = ''
|
||||
if max_positional_args != 1:
|
||||
plural_s = 's'
|
||||
message = '%s() takes at most %d positional argument%s (%d given)' % (
|
||||
wrapped.__name__, max_positional_args, plural_s, len(args))
|
||||
if FLAGS.positional_parameters_enforcement == 'EXCEPTION':
|
||||
raise TypeError(message)
|
||||
elif FLAGS.positional_parameters_enforcement == 'WARNING':
|
||||
logger.warning(message)
|
||||
else: # IGNORE
|
||||
pass
|
||||
return wrapped(*args, **kwargs)
|
||||
return positional_wrapper
|
||||
|
||||
if isinstance(max_positional_args, (int, long)):
|
||||
return positional_decorator
|
||||
else:
|
||||
args, _, _, defaults = inspect.getargspec(max_positional_args)
|
||||
return positional(len(args) - len(defaults))(max_positional_args)
|
||||
|
||||
|
||||
def scopes_to_string(scopes):
|
||||
"""Converts scope value to a string.
|
||||
|
||||
If scopes is a string then it is simply passed through. If scopes is an
|
||||
iterable then a string is returned that is all the individual scopes
|
||||
concatenated with spaces.
|
||||
|
||||
Args:
|
||||
scopes: string or iterable of strings, the scopes.
|
||||
|
||||
Returns:
|
||||
The scopes formatted as a single string.
|
||||
"""
|
||||
if isinstance(scopes, types.StringTypes):
|
||||
return scopes
|
||||
else:
|
||||
return ' '.join(scopes)
|
||||
|
||||
|
||||
def dict_to_tuple_key(dictionary):
|
||||
"""Converts a dictionary to a tuple that can be used as an immutable key.
|
||||
|
||||
The resulting key is always sorted so that logically equivalent dictionaries
|
||||
always produce an identical tuple for a key.
|
||||
|
||||
Args:
|
||||
dictionary: the dictionary to use as the key.
|
||||
|
||||
Returns:
|
||||
A tuple representing the dictionary in it's naturally sorted ordering.
|
||||
"""
|
||||
return tuple(sorted(dictionary.items()))
|
||||
|
||||
|
||||
def _add_query_parameter(url, name, value):
|
||||
"""Adds a query parameter to a url.
|
||||
|
||||
Replaces the current value if it already exists in the URL.
|
||||
|
||||
Args:
|
||||
url: string, url to add the query parameter to.
|
||||
name: string, query parameter name.
|
||||
value: string, query parameter value.
|
||||
|
||||
Returns:
|
||||
Updated query parameter. Does not update the url if value is None.
|
||||
"""
|
||||
if value is None:
|
||||
return url
|
||||
else:
|
||||
parsed = list(urlparse.urlparse(url))
|
||||
q = dict(parse_qsl(parsed[4]))
|
||||
q[name] = value
|
||||
parsed[4] = urllib.urlencode(q)
|
||||
return urlparse.urlunparse(parsed)
|
|
@ -0,0 +1,113 @@
|
|||
#!/usr/bin/python2.5
|
||||
#
|
||||
# Copyright 2010 the Melange authors.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
"""Helper methods for creating & verifying XSRF tokens."""
|
||||
|
||||
__authors__ = [
|
||||
'"Doug Coker" <dcoker@google.com>',
|
||||
'"Joe Gregorio" <jcgregorio@google.com>',
|
||||
]
|
||||
|
||||
|
||||
import base64
|
||||
import hmac
|
||||
import os # for urandom
|
||||
import time
|
||||
|
||||
from oauth2client import util
|
||||
|
||||
|
||||
# Delimiter character
|
||||
DELIMITER = ':'
|
||||
|
||||
# 1 hour in seconds
|
||||
DEFAULT_TIMEOUT_SECS = 1*60*60
|
||||
|
||||
@util.positional(2)
|
||||
def generate_token(key, user_id, action_id="", when=None):
|
||||
"""Generates a URL-safe token for the given user, action, time tuple.
|
||||
|
||||
Args:
|
||||
key: secret key to use.
|
||||
user_id: the user ID of the authenticated user.
|
||||
action_id: a string identifier of the action they requested
|
||||
authorization for.
|
||||
when: the time in seconds since the epoch at which the user was
|
||||
authorized for this action. If not set the current time is used.
|
||||
|
||||
Returns:
|
||||
A string XSRF protection token.
|
||||
"""
|
||||
when = when or int(time.time())
|
||||
digester = hmac.new(key)
|
||||
digester.update(str(user_id))
|
||||
digester.update(DELIMITER)
|
||||
digester.update(action_id)
|
||||
digester.update(DELIMITER)
|
||||
digester.update(str(when))
|
||||
digest = digester.digest()
|
||||
|
||||
token = base64.urlsafe_b64encode('%s%s%d' % (digest,
|
||||
DELIMITER,
|
||||
when))
|
||||
return token
|
||||
|
||||
|
||||
@util.positional(3)
|
||||
def validate_token(key, token, user_id, action_id="", current_time=None):
|
||||
"""Validates that the given token authorizes the user for the action.
|
||||
|
||||
Tokens are invalid if the time of issue is too old or if the token
|
||||
does not match what generateToken outputs (i.e. the token was forged).
|
||||
|
||||
Args:
|
||||
key: secret key to use.
|
||||
token: a string of the token generated by generateToken.
|
||||
user_id: the user ID of the authenticated user.
|
||||
action_id: a string identifier of the action they requested
|
||||
authorization for.
|
||||
|
||||
Returns:
|
||||
A boolean - True if the user is authorized for the action, False
|
||||
otherwise.
|
||||
"""
|
||||
if not token:
|
||||
return False
|
||||
try:
|
||||
decoded = base64.urlsafe_b64decode(str(token))
|
||||
token_time = long(decoded.split(DELIMITER)[-1])
|
||||
except (TypeError, ValueError):
|
||||
return False
|
||||
if current_time is None:
|
||||
current_time = time.time()
|
||||
# If the token is too old it's not valid.
|
||||
if current_time - token_time > DEFAULT_TIMEOUT_SECS:
|
||||
return False
|
||||
|
||||
# The given token should match the generated one with the same time.
|
||||
expected_token = generate_token(key, user_id, action_id=action_id,
|
||||
when=token_time)
|
||||
if len(token) != len(expected_token):
|
||||
return False
|
||||
|
||||
# Perform constant time comparison to avoid timing attacks
|
||||
different = 0
|
||||
for x, y in zip(token, expected_token):
|
||||
different |= ord(x) ^ ord(y)
|
||||
if different:
|
||||
return False
|
||||
|
||||
return True
|
|
@ -0,0 +1,28 @@
|
|||
#!/bin/bash
|
||||
|
||||
# A bootstrap for obtaining an OAuth2 access token for querying the BigStore
|
||||
# REST API from AppEngine. Run this script before testing from the dev server.
|
||||
# It is only necessary for local development.
|
||||
#
|
||||
# Note: the token needs to be acquired for an @google.com account.
|
||||
#
|
||||
# Author: Eric Bidelman <ericbidelman@chromium.org>
|
||||
|
||||
set -e
|
||||
|
||||
# The directory in which this script resides.
|
||||
readonly BASEDIR=$(dirname $BASH_SOURCE)
|
||||
|
||||
# The filename for the OAuth2 token. Should be within the app directory.
|
||||
readonly OAUTH2_CREDENTIALS_FILENAME="$BASEDIR"'/oauth2.data'
|
||||
|
||||
# If deploying to production, delete the OAuth2 access token (if it exists).
|
||||
# Otherwise, refresh the OAuth2 access token for fetching data from BigStore.
|
||||
if [ "$1" == "deploy" ]; then
|
||||
if [ -a "$OAUTH2_CREDENTIALS_FILENAME" ]; then
|
||||
rm "$OAUTH2_CREDENTIALS_FILENAME"
|
||||
fi
|
||||
else
|
||||
$BASEDIR/fetch_oauth_credentials.py --credentials_file="$OAUTH2_CREDENTIALS_FILENAME" \
|
||||
--client_secrets_file=client_secrets.json
|
||||
fi
|
76
server.py
76
server.py
|
@ -12,7 +12,6 @@
|
|||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
|
||||
__author__ = 'ericbidelman@chromium.org (Eric Bidelman)'
|
||||
|
||||
|
@ -20,83 +19,38 @@ import logging
|
|||
import os
|
||||
import webapp2
|
||||
|
||||
import models
|
||||
import common
|
||||
import settings
|
||||
|
||||
from django.template.loader import render_to_string
|
||||
|
||||
class LoggingHandler(webapp2.RequestHandler):
|
||||
|
||||
def __init__(self, request, response):
|
||||
self.initialize(request, response)
|
||||
|
||||
# Settings can't be global in python 2.7 env.
|
||||
logging.getLogger().setLevel(logging.DEBUG)
|
||||
#os.environ['DJANGO_SETTINGS_MODULE'] = 'settings'
|
||||
import uma
|
||||
|
||||
|
||||
class ContentHandler(LoggingHandler):
|
||||
|
||||
def render(self, data={}, template_path=None, status=None, message=None,
|
||||
relpath=None):
|
||||
if status is not None and status != 200:
|
||||
self.response.set_status(status, message)
|
||||
|
||||
# Add template data to every request.
|
||||
template_data = {
|
||||
#'is_mobile': self.is_awesome_mobile_device(),
|
||||
'prod': settings.PROD,
|
||||
'APP_TITLE': settings.APP_TITLE
|
||||
}
|
||||
template_data.update(data) # merge in other data.
|
||||
|
||||
# Add CORS and Chrome Frame to all responses.
|
||||
self.response.headers.add_header('Access-Control-Allow-Origin', '*')
|
||||
self.response.headers.add_header('X-UA-Compatible', 'IE=Edge,chrome=1')
|
||||
self.response.out.write(render_to_string(template_path, template_data))
|
||||
|
||||
class MainHandler(ContentHandler):
|
||||
class MainHandler(common.ContentHandler):
|
||||
|
||||
def get(self, path):
|
||||
# Default to features page.
|
||||
# TODO: remove later when we want an index.html
|
||||
if not path:
|
||||
return self.redirect('/features')
|
||||
|
||||
if path:
|
||||
template_file = self.request.path[1:] + '.html'
|
||||
else:
|
||||
template_file = 'index.html'
|
||||
# Remove trailing slash from URL and redirect. e.g. /metrics/ -> /metrics
|
||||
if path[-1] == '/':
|
||||
return self.redirect('/' + path.rstrip('/'))
|
||||
|
||||
self.render(template_path=os.path.join(template_file))
|
||||
template_data = {}
|
||||
template_file = path + '.html'
|
||||
|
||||
if path == 'metrics/featurelevel':
|
||||
template_data['CSS_PROPERTY_BUCKETS'] = uma.CSS_PROPERTY_BUCKETS
|
||||
|
||||
class AdminHandler(ContentHandler):
|
||||
self.render(data=template_data, template_path=os.path.join(template_file))
|
||||
|
||||
def get(self):
|
||||
# feature = models.Feature(
|
||||
# type=models.Resource.Type.ARTICLE, #TODO: use correct type for content.
|
||||
# title=doc.title(),
|
||||
# text=doc.summary(),#.decode('utf-8').decode('ascii','ignore'),
|
||||
# publication_date=datetime.datetime.today(), #TODO: save real date.
|
||||
# url=db.Link(result.final_url or url),
|
||||
# #fetch_date=datetime.date.today(),
|
||||
# #sharers
|
||||
# )
|
||||
template_data = {
|
||||
'feature_form': models.FeatureForm()
|
||||
}
|
||||
self.render(data=template_data, template_path=os.path.join('newfeature.html'))
|
||||
|
||||
|
||||
def handle_404(request, response, exception):
|
||||
response.write('Oops! Not Found.')
|
||||
response.set_status(404)
|
||||
|
||||
# Main URL routes.
|
||||
routes = [
|
||||
('/newfeature', AdminHandler),
|
||||
('/(.*)', MainHandler),
|
||||
]
|
||||
|
||||
app = webapp2.WSGIApplication(routes, debug=settings.DEBUG)
|
||||
app.error_handlers[404] = handle_404
|
||||
app.error_handlers[404] = common.handle_404
|
||||
if settings.PROD and not settings.DEBUG:
|
||||
app.error_handlers[500] = common.handle_500
|
||||
|
|
|
@ -9,7 +9,7 @@ INSTALLED_APPS = (
|
|||
ROOT_DIR = os.path.abspath(os.path.dirname(__file__))
|
||||
|
||||
TEMPLATE_DIRS = (
|
||||
os.path.join(ROOT_DIR, 'templates'),
|
||||
os.path.join(ROOT_DIR, 'templates')
|
||||
)
|
||||
################################################################################
|
||||
|
||||
|
|
Различия файлов скрыты, потому что одна или несколько строк слишком длинны
|
@ -0,0 +1 @@
|
|||
.line_chart{height:500px;width:900px;margin:0 auto}#dashboard-table{margin:0 auto}.metric-nav{border-bottom:2px solid #4580c0;text-align:center}.metric-nav li{display:inline-block;border-top-left-radius:3px;border-top-right-radius:3px;background:-webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(0%, #ffffff), color-stop(100%, #f2f2f2));background:-webkit-linear-gradient(top, #ffffff,#f2f2f2);background:-moz-linear-gradient(top, #ffffff,#f2f2f2);background:-o-linear-gradient(top, #ffffff,#f2f2f2);background:linear-gradient(top, #ffffff,#f2f2f2);box-shadow:1px 1px 4px rgba(0,0,0,0.065);padding:0.5em;margin:0 5px}#content section{padding-top:25px}#content section h1{display:inline-block;vertical-align:middle;margin-right:10px}#content section p{margin-top:10px}#content section #stack-rank-list{margin:1em 0}#content section #stack-rank-list li{padding:5px 0}#content section #stack-rank-list li>span{display:inline-block}#content section #stack-rank-list li>span:first-of-type{width:110px;margin-right:10px;text-align:right}@media only screen and (max-width: 700px){.metric-nav{text-align:center;margin-top:15px}}
|
|
@ -1,161 +1,173 @@
|
|||
/*
|
||||
AngularJS v1.0.5
|
||||
AngularJS v1.1.4
|
||||
(c) 2010-2012 Google, Inc. http://angularjs.org
|
||||
License: MIT
|
||||
*/
|
||||
(function(X,Y,q){'use strict';function n(b,a,c){var d;if(b)if(H(b))for(d in b)d!="prototype"&&d!="length"&&d!="name"&&b.hasOwnProperty(d)&&a.call(c,b[d],d);else if(b.forEach&&b.forEach!==n)b.forEach(a,c);else if(!b||typeof b.length!=="number"?0:typeof b.hasOwnProperty!="function"&&typeof b.constructor!="function"||b instanceof L||ca&&b instanceof ca||xa.call(b)!=="[object Object]"||typeof b.callee==="function")for(d=0;d<b.length;d++)a.call(c,b[d],d);else for(d in b)b.hasOwnProperty(d)&&a.call(c,b[d],
|
||||
d);return b}function mb(b){var a=[],c;for(c in b)b.hasOwnProperty(c)&&a.push(c);return a.sort()}function fc(b,a,c){for(var d=mb(b),e=0;e<d.length;e++)a.call(c,b[d[e]],d[e]);return d}function nb(b){return function(a,c){b(c,a)}}function ya(){for(var b=aa.length,a;b;){b--;a=aa[b].charCodeAt(0);if(a==57)return aa[b]="A",aa.join("");if(a==90)aa[b]="0";else return aa[b]=String.fromCharCode(a+1),aa.join("")}aa.unshift("0");return aa.join("")}function v(b){n(arguments,function(a){a!==b&&n(a,function(a,d){b[d]=
|
||||
a})});return b}function E(b){return parseInt(b,10)}function za(b,a){return v(new (v(function(){},{prototype:b})),a)}function C(){}function na(b){return b}function I(b){return function(){return b}}function w(b){return typeof b=="undefined"}function x(b){return typeof b!="undefined"}function M(b){return b!=null&&typeof b=="object"}function A(b){return typeof b=="string"}function Ra(b){return typeof b=="number"}function oa(b){return xa.apply(b)=="[object Date]"}function B(b){return xa.apply(b)=="[object Array]"}
|
||||
function H(b){return typeof b=="function"}function pa(b){return b&&b.document&&b.location&&b.alert&&b.setInterval}function O(b){return A(b)?b.replace(/^\s*/,"").replace(/\s*$/,""):b}function gc(b){return b&&(b.nodeName||b.bind&&b.find)}function Sa(b,a,c){var d=[];n(b,function(b,g,h){d.push(a.call(c,b,g,h))});return d}function Aa(b,a){if(b.indexOf)return b.indexOf(a);for(var c=0;c<b.length;c++)if(a===b[c])return c;return-1}function Ta(b,a){var c=Aa(b,a);c>=0&&b.splice(c,1);return a}function U(b,a){if(pa(b)||
|
||||
b&&b.$evalAsync&&b.$watch)throw Error("Can't copy Window or Scope");if(a){if(b===a)throw Error("Can't copy equivalent objects or arrays");if(B(b))for(var c=a.length=0;c<b.length;c++)a.push(U(b[c]));else for(c in n(a,function(b,c){delete a[c]}),b)a[c]=U(b[c])}else(a=b)&&(B(b)?a=U(b,[]):oa(b)?a=new Date(b.getTime()):M(b)&&(a=U(b,{})));return a}function hc(b,a){var a=a||{},c;for(c in b)b.hasOwnProperty(c)&&c.substr(0,2)!=="$$"&&(a[c]=b[c]);return a}function ga(b,a){if(b===a)return!0;if(b===null||a===
|
||||
null)return!1;if(b!==b&&a!==a)return!0;var c=typeof b,d;if(c==typeof a&&c=="object")if(B(b)){if((c=b.length)==a.length){for(d=0;d<c;d++)if(!ga(b[d],a[d]))return!1;return!0}}else if(oa(b))return oa(a)&&b.getTime()==a.getTime();else{if(b&&b.$evalAsync&&b.$watch||a&&a.$evalAsync&&a.$watch||pa(b)||pa(a))return!1;c={};for(d in b)if(!(d.charAt(0)==="$"||H(b[d]))){if(!ga(b[d],a[d]))return!1;c[d]=!0}for(d in a)if(!c[d]&&d.charAt(0)!=="$"&&a[d]!==q&&!H(a[d]))return!1;return!0}return!1}function Ua(b,a){var c=
|
||||
arguments.length>2?ha.call(arguments,2):[];return H(a)&&!(a instanceof RegExp)?c.length?function(){return arguments.length?a.apply(b,c.concat(ha.call(arguments,0))):a.apply(b,c)}:function(){return arguments.length?a.apply(b,arguments):a.call(b)}:a}function ic(b,a){var c=a;/^\$+/.test(b)?c=q:pa(a)?c="$WINDOW":a&&Y===a?c="$DOCUMENT":a&&a.$evalAsync&&a.$watch&&(c="$SCOPE");return c}function da(b,a){return JSON.stringify(b,ic,a?" ":null)}function ob(b){return A(b)?JSON.parse(b):b}function Va(b){b&&b.length!==
|
||||
0?(b=y(""+b),b=!(b=="f"||b=="0"||b=="false"||b=="no"||b=="n"||b=="[]")):b=!1;return b}function qa(b){b=u(b).clone();try{b.html("")}catch(a){}var c=u("<div>").append(b).html();try{return b[0].nodeType===3?y(c):c.match(/^(<[^>]+>)/)[1].replace(/^<([\w\-]+)/,function(a,b){return"<"+y(b)})}catch(d){return y(c)}}function Wa(b){var a={},c,d;n((b||"").split("&"),function(b){b&&(c=b.split("="),d=decodeURIComponent(c[0]),a[d]=x(c[1])?decodeURIComponent(c[1]):!0)});return a}function pb(b){var a=[];n(b,function(b,
|
||||
d){a.push(Xa(d,!0)+(b===!0?"":"="+Xa(b,!0)))});return a.length?a.join("&"):""}function Ya(b){return Xa(b,!0).replace(/%26/gi,"&").replace(/%3D/gi,"=").replace(/%2B/gi,"+")}function Xa(b,a){return encodeURIComponent(b).replace(/%40/gi,"@").replace(/%3A/gi,":").replace(/%24/g,"$").replace(/%2C/gi,",").replace(a?null:/%20/g,"+")}function jc(b,a){function c(a){a&&d.push(a)}var d=[b],e,g,h=["ng:app","ng-app","x-ng-app","data-ng-app"],f=/\sng[:\-]app(:\s*([\w\d_]+);?)?\s/;n(h,function(a){h[a]=!0;c(Y.getElementById(a));
|
||||
a=a.replace(":","\\:");b.querySelectorAll&&(n(b.querySelectorAll("."+a),c),n(b.querySelectorAll("."+a+"\\:"),c),n(b.querySelectorAll("["+a+"]"),c))});n(d,function(a){if(!e){var b=f.exec(" "+a.className+" ");b?(e=a,g=(b[2]||"").replace(/\s+/g,",")):n(a.attributes,function(b){if(!e&&h[b.name])e=a,g=b.value})}});e&&a(e,g?[g]:[])}function qb(b,a){b=u(b);a=a||[];a.unshift(["$provide",function(a){a.value("$rootElement",b)}]);a.unshift("ng");var c=rb(a);c.invoke(["$rootScope","$rootElement","$compile","$injector",
|
||||
function(a,b,c,h){a.$apply(function(){b.data("$injector",h);c(b)(a)})}]);return c}function Za(b,a){a=a||"_";return b.replace(kc,function(b,d){return(d?a:"")+b.toLowerCase()})}function $a(b,a,c){if(!b)throw Error("Argument '"+(a||"?")+"' is "+(c||"required"));return b}function ra(b,a,c){c&&B(b)&&(b=b[b.length-1]);$a(H(b),a,"not a function, got "+(b&&typeof b=="object"?b.constructor.name||"Object":typeof b));return b}function lc(b){function a(a,b,e){return a[b]||(a[b]=e())}return a(a(b,"angular",Object),
|
||||
"module",function(){var b={};return function(d,e,g){e&&b.hasOwnProperty(d)&&(b[d]=null);return a(b,d,function(){function a(c,d,e){return function(){b[e||"push"]([c,d,arguments]);return k}}if(!e)throw Error("No module: "+d);var b=[],c=[],i=a("$injector","invoke"),k={_invokeQueue:b,_runBlocks:c,requires:e,name:d,provider:a("$provide","provider"),factory:a("$provide","factory"),service:a("$provide","service"),value:a("$provide","value"),constant:a("$provide","constant","unshift"),filter:a("$filterProvider",
|
||||
"register"),controller:a("$controllerProvider","register"),directive:a("$compileProvider","directive"),config:i,run:function(a){c.push(a);return this}};g&&i(g);return k})}})}function sb(b){return b.replace(mc,function(a,b,d,e){return e?d.toUpperCase():d}).replace(nc,"Moz$1")}function ab(b,a){function c(){var e;for(var b=[this],c=a,h,f,j,i,k,m;b.length;){h=b.shift();f=0;for(j=h.length;f<j;f++){i=u(h[f]);c?i.triggerHandler("$destroy"):c=!c;k=0;for(e=(m=i.children()).length,i=e;k<i;k++)b.push(ca(m[k]))}}return d.apply(this,
|
||||
arguments)}var d=ca.fn[b],d=d.$original||d;c.$original=d;ca.fn[b]=c}function L(b){if(b instanceof L)return b;if(!(this instanceof L)){if(A(b)&&b.charAt(0)!="<")throw Error("selectors not implemented");return new L(b)}if(A(b)){var a=Y.createElement("div");a.innerHTML="<div> </div>"+b;a.removeChild(a.firstChild);bb(this,a.childNodes);this.remove()}else bb(this,b)}function cb(b){return b.cloneNode(!0)}function sa(b){tb(b);for(var a=0,b=b.childNodes||[];a<b.length;a++)sa(b[a])}function ub(b,a,c){var d=
|
||||
ba(b,"events");ba(b,"handle")&&(w(a)?n(d,function(a,c){db(b,c,a);delete d[c]}):w(c)?(db(b,a,d[a]),delete d[a]):Ta(d[a],c))}function tb(b){var a=b[Ba],c=Ca[a];c&&(c.handle&&(c.events.$destroy&&c.handle({},"$destroy"),ub(b)),delete Ca[a],b[Ba]=q)}function ba(b,a,c){var d=b[Ba],d=Ca[d||-1];if(x(c))d||(b[Ba]=d=++oc,d=Ca[d]={}),d[a]=c;else return d&&d[a]}function vb(b,a,c){var d=ba(b,"data"),e=x(c),g=!e&&x(a),h=g&&!M(a);!d&&!h&&ba(b,"data",d={});if(e)d[a]=c;else if(g)if(h)return d&&d[a];else v(d,a);else return d}
|
||||
function Da(b,a){return(" "+b.className+" ").replace(/[\n\t]/g," ").indexOf(" "+a+" ")>-1}function wb(b,a){a&&n(a.split(" "),function(a){b.className=O((" "+b.className+" ").replace(/[\n\t]/g," ").replace(" "+O(a)+" "," "))})}function xb(b,a){a&&n(a.split(" "),function(a){if(!Da(b,a))b.className=O(b.className+" "+O(a))})}function bb(b,a){if(a)for(var a=!a.nodeName&&x(a.length)&&!pa(a)?a:[a],c=0;c<a.length;c++)b.push(a[c])}function yb(b,a){return Ea(b,"$"+(a||"ngController")+"Controller")}function Ea(b,
|
||||
a,c){b=u(b);for(b[0].nodeType==9&&(b=b.find("html"));b.length;){if(c=b.data(a))return c;b=b.parent()}}function zb(b,a){var c=Fa[a.toLowerCase()];return c&&Ab[b.nodeName]&&c}function pc(b,a){var c=function(c,e){if(!c.preventDefault)c.preventDefault=function(){c.returnValue=!1};if(!c.stopPropagation)c.stopPropagation=function(){c.cancelBubble=!0};if(!c.target)c.target=c.srcElement||Y;if(w(c.defaultPrevented)){var g=c.preventDefault;c.preventDefault=function(){c.defaultPrevented=!0;g.call(c)};c.defaultPrevented=
|
||||
!1}c.isDefaultPrevented=function(){return c.defaultPrevented};n(a[e||c.type],function(a){a.call(b,c)});Z<=8?(c.preventDefault=null,c.stopPropagation=null,c.isDefaultPrevented=null):(delete c.preventDefault,delete c.stopPropagation,delete c.isDefaultPrevented)};c.elem=b;return c}function fa(b){var a=typeof b,c;if(a=="object"&&b!==null)if(typeof(c=b.$$hashKey)=="function")c=b.$$hashKey();else{if(c===q)c=b.$$hashKey=ya()}else c=b;return a+":"+c}function Ga(b){n(b,this.put,this)}function eb(){}function Bb(b){var a,
|
||||
c;if(typeof b=="function"){if(!(a=b.$inject))a=[],c=b.toString().replace(qc,""),c=c.match(rc),n(c[1].split(sc),function(b){b.replace(tc,function(b,c,d){a.push(d)})}),b.$inject=a}else B(b)?(c=b.length-1,ra(b[c],"fn"),a=b.slice(0,c)):ra(b,"fn",!0);return a}function rb(b){function a(a){return function(b,c){if(M(b))n(b,nb(a));else return a(b,c)}}function c(a,b){if(H(b)||B(b))b=m.instantiate(b);if(!b.$get)throw Error("Provider "+a+" must define $get factory method.");return k[a+f]=b}function d(a,b){return c(a,
|
||||
{$get:b})}function e(a){var b=[];n(a,function(a){if(!i.get(a))if(i.put(a,!0),A(a)){var c=ta(a);b=b.concat(e(c.requires)).concat(c._runBlocks);try{for(var d=c._invokeQueue,c=0,f=d.length;c<f;c++){var g=d[c],h=g[0]=="$injector"?m:m.get(g[0]);h[g[1]].apply(h,g[2])}}catch(j){throw j.message&&(j.message+=" from "+a),j;}}else if(H(a))try{b.push(m.invoke(a))}catch(o){throw o.message&&(o.message+=" from "+a),o;}else if(B(a))try{b.push(m.invoke(a))}catch(k){throw k.message&&(k.message+=" from "+String(a[a.length-
|
||||
1])),k;}else ra(a,"module")});return b}function g(a,b){function c(d){if(typeof d!=="string")throw Error("Service name expected");if(a.hasOwnProperty(d)){if(a[d]===h)throw Error("Circular dependency: "+j.join(" <- "));return a[d]}else try{return j.unshift(d),a[d]=h,a[d]=b(d)}finally{j.shift()}}function d(a,b,e){var f=[],i=Bb(a),g,h,j;h=0;for(g=i.length;h<g;h++)j=i[h],f.push(e&&e.hasOwnProperty(j)?e[j]:c(j));a.$inject||(a=a[g]);switch(b?-1:f.length){case 0:return a();case 1:return a(f[0]);case 2:return a(f[0],
|
||||
f[1]);case 3:return a(f[0],f[1],f[2]);case 4:return a(f[0],f[1],f[2],f[3]);case 5:return a(f[0],f[1],f[2],f[3],f[4]);case 6:return a(f[0],f[1],f[2],f[3],f[4],f[5]);case 7:return a(f[0],f[1],f[2],f[3],f[4],f[5],f[6]);case 8:return a(f[0],f[1],f[2],f[3],f[4],f[5],f[6],f[7]);case 9:return a(f[0],f[1],f[2],f[3],f[4],f[5],f[6],f[7],f[8]);case 10:return a(f[0],f[1],f[2],f[3],f[4],f[5],f[6],f[7],f[8],f[9]);default:return a.apply(b,f)}}return{invoke:d,instantiate:function(a,b){var c=function(){},e;c.prototype=
|
||||
(B(a)?a[a.length-1]:a).prototype;c=new c;e=d(a,c,b);return M(e)?e:c},get:c,annotate:Bb}}var h={},f="Provider",j=[],i=new Ga,k={$provide:{provider:a(c),factory:a(d),service:a(function(a,b){return d(a,["$injector",function(a){return a.instantiate(b)}])}),value:a(function(a,b){return d(a,I(b))}),constant:a(function(a,b){k[a]=b;l[a]=b}),decorator:function(a,b){var c=m.get(a+f),d=c.$get;c.$get=function(){var a=t.invoke(d,c);return t.invoke(b,null,{$delegate:a})}}}},m=g(k,function(){throw Error("Unknown provider: "+
|
||||
j.join(" <- "));}),l={},t=l.$injector=g(l,function(a){a=m.get(a+f);return t.invoke(a.$get,a)});n(e(b),function(a){t.invoke(a||C)});return t}function uc(){var b=!0;this.disableAutoScrolling=function(){b=!1};this.$get=["$window","$location","$rootScope",function(a,c,d){function e(a){var b=null;n(a,function(a){!b&&y(a.nodeName)==="a"&&(b=a)});return b}function g(){var b=c.hash(),d;b?(d=h.getElementById(b))?d.scrollIntoView():(d=e(h.getElementsByName(b)))?d.scrollIntoView():b==="top"&&a.scrollTo(0,0):
|
||||
a.scrollTo(0,0)}var h=a.document;b&&d.$watch(function(){return c.hash()},function(){d.$evalAsync(g)});return g}]}function vc(b,a,c,d){function e(a){try{a.apply(null,ha.call(arguments,1))}finally{if(o--,o===0)for(;p.length;)try{p.pop()()}catch(b){c.error(b)}}}function g(a,b){(function R(){n(s,function(a){a()});J=b(R,a)})()}function h(){F!=f.url()&&(F=f.url(),n(V,function(a){a(f.url())}))}var f=this,j=a[0],i=b.location,k=b.history,m=b.setTimeout,l=b.clearTimeout,t={};f.isMock=!1;var o=0,p=[];f.$$completeOutstandingRequest=
|
||||
e;f.$$incOutstandingRequestCount=function(){o++};f.notifyWhenNoOutstandingRequests=function(a){n(s,function(a){a()});o===0?a():p.push(a)};var s=[],J;f.addPollFn=function(a){w(J)&&g(100,m);s.push(a);return a};var F=i.href,z=a.find("base");f.url=function(a,b){if(a){if(F!=a)return F=a,d.history?b?k.replaceState(null,"",a):(k.pushState(null,"",a),z.attr("href",z.attr("href"))):b?i.replace(a):i.href=a,f}else return i.href.replace(/%27/g,"'")};var V=[],K=!1;f.onUrlChange=function(a){K||(d.history&&u(b).bind("popstate",
|
||||
h),d.hashchange?u(b).bind("hashchange",h):f.addPollFn(h),K=!0);V.push(a);return a};f.baseHref=function(){var a=z.attr("href");return a?a.replace(/^https?\:\/\/[^\/]*/,""):""};var r={},$="",P=f.baseHref();f.cookies=function(a,b){var d,e,f,i;if(a)if(b===q)j.cookie=escape(a)+"=;path="+P+";expires=Thu, 01 Jan 1970 00:00:00 GMT";else{if(A(b))d=(j.cookie=escape(a)+"="+escape(b)+";path="+P).length+1,d>4096&&c.warn("Cookie '"+a+"' possibly not set or overflowed because it was too large ("+d+" > 4096 bytes)!")}else{if(j.cookie!==
|
||||
$){$=j.cookie;d=$.split("; ");r={};for(f=0;f<d.length;f++)e=d[f],i=e.indexOf("="),i>0&&(r[unescape(e.substring(0,i))]=unescape(e.substring(i+1)))}return r}};f.defer=function(a,b){var c;o++;c=m(function(){delete t[c];e(a)},b||0);t[c]=!0;return c};f.defer.cancel=function(a){return t[a]?(delete t[a],l(a),e(C),!0):!1}}function wc(){this.$get=["$window","$log","$sniffer","$document",function(b,a,c,d){return new vc(b,d,a,c)}]}function xc(){this.$get=function(){function b(b,d){function e(a){if(a!=m){if(l){if(l==
|
||||
a)l=a.n}else l=a;g(a.n,a.p);g(a,m);m=a;m.n=null}}function g(a,b){if(a!=b){if(a)a.p=b;if(b)b.n=a}}if(b in a)throw Error("cacheId "+b+" taken");var h=0,f=v({},d,{id:b}),j={},i=d&&d.capacity||Number.MAX_VALUE,k={},m=null,l=null;return a[b]={put:function(a,b){var c=k[a]||(k[a]={key:a});e(c);w(b)||(a in j||h++,j[a]=b,h>i&&this.remove(l.key))},get:function(a){var b=k[a];if(b)return e(b),j[a]},remove:function(a){var b=k[a];if(b){if(b==m)m=b.p;if(b==l)l=b.n;g(b.n,b.p);delete k[a];delete j[a];h--}},removeAll:function(){j=
|
||||
{};h=0;k={};m=l=null},destroy:function(){k=f=j=null;delete a[b]},info:function(){return v({},f,{size:h})}}}var a={};b.info=function(){var b={};n(a,function(a,e){b[e]=a.info()});return b};b.get=function(b){return a[b]};return b}}function yc(){this.$get=["$cacheFactory",function(b){return b("templates")}]}function Cb(b){var a={},c="Directive",d=/^\s*directive\:\s*([\d\w\-_]+)\s+(.*)$/,e=/(([\d\w\-_]+)(?:\:([^;]+))?;?)/,g="Template must have exactly one root element. was: ",h=/^\s*(https?|ftp|mailto):/;
|
||||
this.directive=function j(d,e){A(d)?($a(e,"directive"),a.hasOwnProperty(d)||(a[d]=[],b.factory(d+c,["$injector","$exceptionHandler",function(b,c){var e=[];n(a[d],function(a){try{var g=b.invoke(a);if(H(g))g={compile:I(g)};else if(!g.compile&&g.link)g.compile=I(g.link);g.priority=g.priority||0;g.name=g.name||d;g.require=g.require||g.controller&&g.name;g.restrict=g.restrict||"A";e.push(g)}catch(h){c(h)}});return e}])),a[d].push(e)):n(d,nb(j));return this};this.urlSanitizationWhitelist=function(a){return x(a)?
|
||||
(h=a,this):h};this.$get=["$injector","$interpolate","$exceptionHandler","$http","$templateCache","$parse","$controller","$rootScope","$document",function(b,i,k,m,l,t,o,p,s){function J(a,b,c){a instanceof u||(a=u(a));n(a,function(b,c){b.nodeType==3&&b.nodeValue.match(/\S+/)&&(a[c]=u(b).wrap("<span></span>").parent()[0])});var d=z(a,b,a,c);return function(b,c){$a(b,"scope");for(var e=c?va.clone.call(a):a,g=0,i=e.length;g<i;g++){var h=e[g];(h.nodeType==1||h.nodeType==9)&&e.eq(g).data("$scope",b)}F(e,
|
||||
"ng-scope");c&&c(e,b);d&&d(b,e,e);return e}}function F(a,b){try{a.addClass(b)}catch(c){}}function z(a,b,c,d){function e(a,c,d,i){var h,j,k,o,l,m,t,s=[];l=0;for(m=c.length;l<m;l++)s.push(c[l]);t=l=0;for(m=g.length;l<m;t++)j=s[t],c=g[l++],h=g[l++],c?(c.scope?(k=a.$new(M(c.scope)),u(j).data("$scope",k)):k=a,(o=c.transclude)||!i&&b?c(h,k,j,d,function(b){return function(c){var d=a.$new();d.$$transcluded=!0;return b(d,c).bind("$destroy",Ua(d,d.$destroy))}}(o||b)):c(h,k,j,q,i)):h&&h(a,j.childNodes,q,i)}
|
||||
for(var g=[],i,h,j,k=0;k<a.length;k++)h=new ia,i=V(a[k],[],h,d),h=(i=i.length?K(i,a[k],h,b,c):null)&&i.terminal||!a[k].childNodes.length?null:z(a[k].childNodes,i?i.transclude:b),g.push(i),g.push(h),j=j||i||h;return j?e:null}function V(a,b,c,i){var g=c.$attr,h;switch(a.nodeType){case 1:r(b,ea(fb(a).toLowerCase()),"E",i);var j,k,l;h=a.attributes;for(var o=0,m=h&&h.length;o<m;o++)if(j=h[o],j.specified)k=j.name,l=ea(k.toLowerCase()),g[l]=k,c[l]=j=O(Z&&k=="href"?decodeURIComponent(a.getAttribute(k,2)):
|
||||
j.value),zb(a,l)&&(c[l]=!0),R(a,b,j,l),r(b,l,"A",i);a=a.className;if(A(a)&&a!=="")for(;h=e.exec(a);)l=ea(h[2]),r(b,l,"C",i)&&(c[l]=O(h[3])),a=a.substr(h.index+h[0].length);break;case 3:x(b,a.nodeValue);break;case 8:try{if(h=d.exec(a.nodeValue))l=ea(h[1]),r(b,l,"M",i)&&(c[l]=O(h[2]))}catch(t){}}b.sort(G);return b}function K(a,b,c,d,e){function i(a,b){if(a)a.require=r.require,m.push(a);if(b)b.require=r.require,s.push(b)}function h(a,b){var c,d="data",e=!1;if(A(a)){for(;(c=a.charAt(0))=="^"||c=="?";)a=
|
||||
a.substr(1),c=="^"&&(d="inheritedData"),e=e||c=="?";c=b[d]("$"+a+"Controller");if(!c&&!e)throw Error("No controller: "+a);}else B(a)&&(c=[],n(a,function(a){c.push(h(a,b))}));return c}function j(a,d,e,i,g){var l,p,r,D,F;l=b===e?c:hc(c,new ia(u(e),c.$attr));p=l.$$element;if(K){var J=/^\s*([@=&])\s*(\w*)\s*$/,ja=d.$parent||d;n(K.scope,function(a,b){var c=a.match(J)||[],e=c[2]||b,c=c[1],i,g,h;d.$$isolateBindings[b]=c+e;switch(c){case "@":l.$observe(e,function(a){d[b]=a});l.$$observers[e].$$scope=ja;break;
|
||||
case "=":g=t(l[e]);h=g.assign||function(){i=d[b]=g(ja);throw Error(Db+l[e]+" (directive: "+K.name+")");};i=d[b]=g(ja);d.$watch(function(){var a=g(ja);a!==d[b]&&(a!==i?i=d[b]=a:h(ja,a=i=d[b]));return a});break;case "&":g=t(l[e]);d[b]=function(a){return g(ja,a)};break;default:throw Error("Invalid isolate scope definition for directive "+K.name+": "+a);}})}x&&n(x,function(a){var b={$scope:d,$element:p,$attrs:l,$transclude:g};F=a.controller;F=="@"&&(F=l[a.name]);p.data("$"+a.name+"Controller",o(F,b))});
|
||||
i=0;for(r=m.length;i<r;i++)try{D=m[i],D(d,p,l,D.require&&h(D.require,p))}catch(z){k(z,qa(p))}a&&a(d,e.childNodes,q,g);i=0;for(r=s.length;i<r;i++)try{D=s[i],D(d,p,l,D.require&&h(D.require,p))}catch(zc){k(zc,qa(p))}}for(var l=-Number.MAX_VALUE,m=[],s=[],p=null,K=null,z=null,D=c.$$element=u(b),r,G,S,ka,R=d,x,w,W,v=0,y=a.length;v<y;v++){r=a[v];S=q;if(l>r.priority)break;if(W=r.scope)ua("isolated scope",K,r,D),M(W)&&(F(D,"ng-isolate-scope"),K=r),F(D,"ng-scope"),p=p||r;G=r.name;if(W=r.controller)x=x||{},
|
||||
ua("'"+G+"' controller",x[G],r,D),x[G]=r;if(W=r.transclude)ua("transclusion",ka,r,D),ka=r,l=r.priority,W=="element"?(S=u(b),D=c.$$element=u(Y.createComment(" "+G+": "+c[G]+" ")),b=D[0],C(e,u(S[0]),b),R=J(S,d,l)):(S=u(cb(b)).contents(),D.html(""),R=J(S,d));if(W=r.template)if(ua("template",z,r,D),z=r,W=Eb(W),r.replace){S=u("<div>"+O(W)+"</div>").contents();b=S[0];if(S.length!=1||b.nodeType!==1)throw Error(g+W);C(e,D,b);G={$attr:{}};a=a.concat(V(b,a.splice(v+1,a.length-(v+1)),G));$(c,G);y=a.length}else D.html(W);
|
||||
if(r.templateUrl)ua("template",z,r,D),z=r,j=P(a.splice(v,a.length-v),j,D,c,e,r.replace,R),y=a.length;else if(r.compile)try{w=r.compile(D,c,R),H(w)?i(null,w):w&&i(w.pre,w.post)}catch(E){k(E,qa(D))}if(r.terminal)j.terminal=!0,l=Math.max(l,r.priority)}j.scope=p&&p.scope;j.transclude=ka&&R;return j}function r(d,e,i,g){var h=!1;if(a.hasOwnProperty(e))for(var l,e=b.get(e+c),o=0,m=e.length;o<m;o++)try{if(l=e[o],(g===q||g>l.priority)&&l.restrict.indexOf(i)!=-1)d.push(l),h=!0}catch(t){k(t)}return h}function $(a,
|
||||
b){var c=b.$attr,d=a.$attr,e=a.$$element;n(a,function(d,e){e.charAt(0)!="$"&&(b[e]&&(d+=(e==="style"?";":" ")+b[e]),a.$set(e,d,!0,c[e]))});n(b,function(b,i){i=="class"?(F(e,b),a["class"]=(a["class"]?a["class"]+" ":"")+b):i=="style"?e.attr("style",e.attr("style")+";"+b):i.charAt(0)!="$"&&!a.hasOwnProperty(i)&&(a[i]=b,d[i]=c[i])})}function P(a,b,c,d,e,i,h){var j=[],k,o,t=c[0],s=a.shift(),p=v({},s,{controller:null,templateUrl:null,transclude:null,scope:null});c.html("");m.get(s.templateUrl,{cache:l}).success(function(l){var m,
|
||||
s,l=Eb(l);if(i){s=u("<div>"+O(l)+"</div>").contents();m=s[0];if(s.length!=1||m.nodeType!==1)throw Error(g+l);l={$attr:{}};C(e,c,m);V(m,a,l);$(d,l)}else m=t,c.html(l);a.unshift(p);k=K(a,m,d,h);for(o=z(c.contents(),h);j.length;){var ia=j.pop(),l=j.pop();s=j.pop();var r=j.pop(),D=m;s!==t&&(D=cb(m),C(l,u(s),D));k(function(){b(o,r,D,e,ia)},r,D,e,ia)}j=null}).error(function(a,b,c,d){throw Error("Failed to load template: "+d.url);});return function(a,c,d,e,i){j?(j.push(c),j.push(d),j.push(e),j.push(i)):
|
||||
k(function(){b(o,c,d,e,i)},c,d,e,i)}}function G(a,b){return b.priority-a.priority}function ua(a,b,c,d){if(b)throw Error("Multiple directives ["+b.name+", "+c.name+"] asking for "+a+" on: "+qa(d));}function x(a,b){var c=i(b,!0);c&&a.push({priority:0,compile:I(function(a,b){var d=b.parent(),e=d.data("$binding")||[];e.push(c);F(d.data("$binding",e),"ng-binding");a.$watch(c,function(a){b[0].nodeValue=a})})})}function R(a,b,c,d){var e=i(c,!0);e&&b.push({priority:100,compile:I(function(a,b,c){b=c.$$observers||
|
||||
(c.$$observers={});d==="class"&&(e=i(c[d],!0));c[d]=q;(b[d]||(b[d]=[])).$$inter=!0;(c.$$observers&&c.$$observers[d].$$scope||a).$watch(e,function(a){c.$set(d,a)})})})}function C(a,b,c){var d=b[0],e=d.parentNode,i,g;if(a){i=0;for(g=a.length;i<g;i++)if(a[i]==d){a[i]=c;break}}e&&e.replaceChild(c,d);c[u.expando]=d[u.expando];b[0]=c}var ia=function(a,b){this.$$element=a;this.$attr=b||{}};ia.prototype={$normalize:ea,$set:function(a,b,c,d){var e=zb(this.$$element[0],a),i=this.$$observers;e&&(this.$$element.prop(a,
|
||||
b),d=e);this[a]=b;d?this.$attr[a]=d:(d=this.$attr[a])||(this.$attr[a]=d=Za(a,"-"));if(fb(this.$$element[0])==="A"&&a==="href")D.setAttribute("href",b),e=D.href,e.match(h)||(this[a]=b="unsafe:"+e);c!==!1&&(b===null||b===q?this.$$element.removeAttr(d):this.$$element.attr(d,b));i&&n(i[a],function(a){try{a(b)}catch(c){k(c)}})},$observe:function(a,b){var c=this,d=c.$$observers||(c.$$observers={}),e=d[a]||(d[a]=[]);e.push(b);p.$evalAsync(function(){e.$$inter||b(c[a])});return b}};var D=s[0].createElement("a"),
|
||||
S=i.startSymbol(),ka=i.endSymbol(),Eb=S=="{{"||ka=="}}"?na:function(a){return a.replace(/\{\{/g,S).replace(/}}/g,ka)};return J}]}function ea(b){return sb(b.replace(Ac,""))}function Bc(){var b={};this.register=function(a,c){M(a)?v(b,a):b[a]=c};this.$get=["$injector","$window",function(a,c){return function(d,e){if(A(d)){var g=d,d=b.hasOwnProperty(g)?b[g]:gb(e.$scope,g,!0)||gb(c,g,!0);ra(d,g,!0)}return a.instantiate(d,e)}}]}function Cc(){this.$get=["$window",function(b){return u(b.document)}]}function Dc(){this.$get=
|
||||
["$log",function(b){return function(a,c){b.error.apply(b,arguments)}}]}function Ec(){var b="{{",a="}}";this.startSymbol=function(a){return a?(b=a,this):b};this.endSymbol=function(b){return b?(a=b,this):a};this.$get=["$parse",function(c){function d(d,f){for(var j,i,k=0,m=[],l=d.length,t=!1,o=[];k<l;)(j=d.indexOf(b,k))!=-1&&(i=d.indexOf(a,j+e))!=-1?(k!=j&&m.push(d.substring(k,j)),m.push(k=c(t=d.substring(j+e,i))),k.exp=t,k=i+g,t=!0):(k!=l&&m.push(d.substring(k)),k=l);if(!(l=m.length))m.push(""),l=1;
|
||||
if(!f||t)return o.length=l,k=function(a){for(var b=0,c=l,d;b<c;b++){if(typeof(d=m[b])=="function")d=d(a),d==null||d==q?d="":typeof d!="string"&&(d=da(d));o[b]=d}return o.join("")},k.exp=d,k.parts=m,k}var e=b.length,g=a.length;d.startSymbol=function(){return b};d.endSymbol=function(){return a};return d}]}function Fb(b){for(var b=b.split("/"),a=b.length;a--;)b[a]=Ya(b[a]);return b.join("/")}function wa(b,a){var c=Gb.exec(b),c={protocol:c[1],host:c[3],port:E(c[5])||Hb[c[1]]||null,path:c[6]||"/",search:c[8],
|
||||
hash:c[10]};if(a)a.$$protocol=c.protocol,a.$$host=c.host,a.$$port=c.port;return c}function la(b,a,c){return b+"://"+a+(c==Hb[b]?"":":"+c)}function Fc(b,a,c){var d=wa(b);return decodeURIComponent(d.path)!=a||w(d.hash)||d.hash.indexOf(c)!==0?b:la(d.protocol,d.host,d.port)+a.substr(0,a.lastIndexOf("/"))+d.hash.substr(c.length)}function Gc(b,a,c){var d=wa(b);if(decodeURIComponent(d.path)==a)return b;else{var e=d.search&&"?"+d.search||"",g=d.hash&&"#"+d.hash||"",h=a.substr(0,a.lastIndexOf("/")),f=d.path.substr(h.length);
|
||||
if(d.path.indexOf(h)!==0)throw Error('Invalid url "'+b+'", missing path prefix "'+h+'" !');return la(d.protocol,d.host,d.port)+a+"#"+c+f+e+g}}function hb(b,a,c){a=a||"";this.$$parse=function(b){var c=wa(b,this);if(c.path.indexOf(a)!==0)throw Error('Invalid url "'+b+'", missing path prefix "'+a+'" !');this.$$path=decodeURIComponent(c.path.substr(a.length));this.$$search=Wa(c.search);this.$$hash=c.hash&&decodeURIComponent(c.hash)||"";this.$$compose()};this.$$compose=function(){var b=pb(this.$$search),
|
||||
c=this.$$hash?"#"+Ya(this.$$hash):"";this.$$url=Fb(this.$$path)+(b?"?"+b:"")+c;this.$$absUrl=la(this.$$protocol,this.$$host,this.$$port)+a+this.$$url};this.$$rewriteAppUrl=function(a){if(a.indexOf(c)==0)return a};this.$$parse(b)}function Ha(b,a,c){var d;this.$$parse=function(b){var c=wa(b,this);if(c.hash&&c.hash.indexOf(a)!==0)throw Error('Invalid url "'+b+'", missing hash prefix "'+a+'" !');d=c.path+(c.search?"?"+c.search:"");c=Hc.exec((c.hash||"").substr(a.length));this.$$path=c[1]?(c[1].charAt(0)==
|
||||
"/"?"":"/")+decodeURIComponent(c[1]):"";this.$$search=Wa(c[3]);this.$$hash=c[5]&&decodeURIComponent(c[5])||"";this.$$compose()};this.$$compose=function(){var b=pb(this.$$search),c=this.$$hash?"#"+Ya(this.$$hash):"";this.$$url=Fb(this.$$path)+(b?"?"+b:"")+c;this.$$absUrl=la(this.$$protocol,this.$$host,this.$$port)+d+(this.$$url?"#"+a+this.$$url:"")};this.$$rewriteAppUrl=function(a){if(a.indexOf(c)==0)return a};this.$$parse(b)}function Ib(b,a,c,d){Ha.apply(this,arguments);this.$$rewriteAppUrl=function(b){if(b.indexOf(c)==
|
||||
0)return c+d+"#"+a+b.substr(c.length)}}function Ia(b){return function(){return this[b]}}function Jb(b,a){return function(c){if(w(c))return this[b];this[b]=a(c);this.$$compose();return this}}function Ic(){var b="",a=!1;this.hashPrefix=function(a){return x(a)?(b=a,this):b};this.html5Mode=function(b){return x(b)?(a=b,this):a};this.$get=["$rootScope","$browser","$sniffer","$rootElement",function(c,d,e,g){function h(a){c.$broadcast("$locationChangeSuccess",f.absUrl(),a)}var f,j,i,k=d.url(),m=wa(k);a?(j=
|
||||
d.baseHref()||"/",i=j.substr(0,j.lastIndexOf("/")),m=la(m.protocol,m.host,m.port)+i+"/",f=e.history?new hb(Fc(k,j,b),i,m):new Ib(Gc(k,j,b),b,m,j.substr(i.length+1))):(m=la(m.protocol,m.host,m.port)+(m.path||"")+(m.search?"?"+m.search:"")+"#"+b+"/",f=new Ha(k,b,m));g.bind("click",function(a){if(!a.ctrlKey&&!(a.metaKey||a.which==2)){for(var b=u(a.target);y(b[0].nodeName)!=="a";)if(b[0]===g[0]||!(b=b.parent())[0])return;var d=b.prop("href"),e=f.$$rewriteAppUrl(d);d&&!b.attr("target")&&e&&(f.$$parse(e),
|
||||
c.$apply(),a.preventDefault(),X.angular["ff-684208-preventDefault"]=!0)}});f.absUrl()!=k&&d.url(f.absUrl(),!0);d.onUrlChange(function(a){f.absUrl()!=a&&(c.$evalAsync(function(){var b=f.absUrl();f.$$parse(a);h(b)}),c.$$phase||c.$digest())});var l=0;c.$watch(function(){var a=d.url(),b=f.$$replace;if(!l||a!=f.absUrl())l++,c.$evalAsync(function(){c.$broadcast("$locationChangeStart",f.absUrl(),a).defaultPrevented?f.$$parse(a):(d.url(f.absUrl(),b),h(a))});f.$$replace=!1;return l});return f}]}function Jc(){this.$get=
|
||||
["$window",function(b){function a(a){a instanceof Error&&(a.stack?a=a.message&&a.stack.indexOf(a.message)===-1?"Error: "+a.message+"\n"+a.stack:a.stack:a.sourceURL&&(a=a.message+"\n"+a.sourceURL+":"+a.line));return a}function c(c){var e=b.console||{},g=e[c]||e.log||C;return g.apply?function(){var b=[];n(arguments,function(c){b.push(a(c))});return g.apply(e,b)}:function(a,b){g(a,b)}}return{log:c("log"),warn:c("warn"),info:c("info"),error:c("error")}}]}function Kc(b,a){function c(a){return a.indexOf(s)!=
|
||||
-1}function d(){return o+1<b.length?b.charAt(o+1):!1}function e(a){return"0"<=a&&a<="9"}function g(a){return a==" "||a=="\r"||a=="\t"||a=="\n"||a=="\u000b"||a=="\u00a0"}function h(a){return"a"<=a&&a<="z"||"A"<=a&&a<="Z"||"_"==a||a=="$"}function f(a){return a=="-"||a=="+"||e(a)}function j(a,c,d){d=d||o;throw Error("Lexer Error: "+a+" at column"+(x(c)?"s "+c+"-"+o+" ["+b.substring(c,d)+"]":" "+d)+" in expression ["+b+"].");}function i(){for(var a="",c=o;o<b.length;){var i=y(b.charAt(o));if(i=="."||
|
||||
e(i))a+=i;else{var g=d();if(i=="e"&&f(g))a+=i;else if(f(i)&&g&&e(g)&&a.charAt(a.length-1)=="e")a+=i;else if(f(i)&&(!g||!e(g))&&a.charAt(a.length-1)=="e")j("Invalid exponent");else break}o++}a*=1;l.push({index:c,text:a,json:!0,fn:function(){return a}})}function k(){for(var c="",d=o,f,i,j;o<b.length;){var k=b.charAt(o);if(k=="."||h(k)||e(k))k=="."&&(f=o),c+=k;else break;o++}if(f)for(i=o;i<b.length;){k=b.charAt(i);if(k=="("){j=c.substr(f-d+1);c=c.substr(0,f-d);o=i;break}if(g(k))i++;else break}d={index:d,
|
||||
text:c};if(Ja.hasOwnProperty(c))d.fn=d.json=Ja[c];else{var m=Kb(c,a);d.fn=v(function(a,b){return m(a,b)},{assign:function(a,b){return Lb(a,c,b)}})}l.push(d);j&&(l.push({index:f,text:".",json:!1}),l.push({index:f+1,text:j,json:!1}))}function m(a){var c=o;o++;for(var d="",e=a,f=!1;o<b.length;){var i=b.charAt(o);e+=i;if(f)i=="u"?(i=b.substring(o+1,o+5),i.match(/[\da-f]{4}/i)||j("Invalid unicode escape [\\u"+i+"]"),o+=4,d+=String.fromCharCode(parseInt(i,16))):(f=Lc[i],d+=f?f:i),f=!1;else if(i=="\\")f=
|
||||
!0;else if(i==a){o++;l.push({index:c,text:e,string:d,json:!0,fn:function(){return d}});return}else d+=i;o++}j("Unterminated quote",c)}for(var l=[],t,o=0,p=[],s,J=":";o<b.length;){s=b.charAt(o);if(c("\"'"))m(s);else if(e(s)||c(".")&&e(d()))i();else if(h(s)){if(k(),"{,".indexOf(J)!=-1&&p[0]=="{"&&(t=l[l.length-1]))t.json=t.text.indexOf(".")==-1}else if(c("(){}[].,;:"))l.push({index:o,text:s,json:":[,".indexOf(J)!=-1&&c("{[")||c("}]:,")}),c("{[")&&p.unshift(s),c("}]")&&p.shift(),o++;else if(g(s)){o++;
|
||||
continue}else{var n=s+d(),z=Ja[s],V=Ja[n];V?(l.push({index:o,text:n,fn:V}),o+=2):z?(l.push({index:o,text:s,fn:z,json:"[,:".indexOf(J)!=-1&&c("+-")}),o+=1):j("Unexpected next character ",o,o+1)}J=s}return l}function Mc(b,a,c,d){function e(a,c){throw Error("Syntax Error: Token '"+c.text+"' "+a+" at column "+(c.index+1)+" of the expression ["+b+"] starting at ["+b.substring(c.index)+"].");}function g(){if(P.length===0)throw Error("Unexpected end of expression: "+b);return P[0]}function h(a,b,c,d){if(P.length>
|
||||
0){var e=P[0],f=e.text;if(f==a||f==b||f==c||f==d||!a&&!b&&!c&&!d)return e}return!1}function f(b,c,d,f){return(b=h(b,c,d,f))?(a&&!b.json&&e("is not valid json",b),P.shift(),b):!1}function j(a){f(a)||e("is unexpected, expecting ["+a+"]",h())}function i(a,b){return function(c,d){return a(c,d,b)}}function k(a,b,c){return function(d,e){return b(d,e,a,c)}}function m(){for(var a=[];;)if(P.length>0&&!h("}",")",";","]")&&a.push(w()),!f(";"))return a.length==1?a[0]:function(b,c){for(var d,e=0;e<a.length;e++){var f=
|
||||
a[e];f&&(d=f(b,c))}return d}}function l(){for(var a=f(),b=c(a.text),d=[];;)if(a=f(":"))d.push(G());else{var e=function(a,c,e){for(var e=[e],f=0;f<d.length;f++)e.push(d[f](a,c));return b.apply(a,e)};return function(){return e}}}function t(){for(var a=o(),b;;)if(b=f("||"))a=k(a,b.fn,o());else return a}function o(){var a=p(),b;if(b=f("&&"))a=k(a,b.fn,o());return a}function p(){var a=s(),b;if(b=f("==","!="))a=k(a,b.fn,p());return a}function s(){var a;a=J();for(var b;b=f("+","-");)a=k(a,b.fn,J());if(b=
|
||||
f("<",">","<=",">="))a=k(a,b.fn,s());return a}function J(){for(var a=n(),b;b=f("*","/","%");)a=k(a,b.fn,n());return a}function n(){var a;return f("+")?z():(a=f("-"))?k(r,a.fn,n()):(a=f("!"))?i(a.fn,n()):z()}function z(){var a;if(f("("))a=w(),j(")");else if(f("["))a=V();else if(f("{"))a=K();else{var b=f();(a=b.fn)||e("not a primary expression",b)}for(var c;b=f("(","[",".");)b.text==="("?(a=x(a,c),c=null):b.text==="["?(c=a,a=R(a)):b.text==="."?(c=a,a=u(a)):e("IMPOSSIBLE");return a}function V(){var a=
|
||||
[];if(g().text!="]"){do a.push(G());while(f(","))}j("]");return function(b,c){for(var d=[],e=0;e<a.length;e++)d.push(a[e](b,c));return d}}function K(){var a=[];if(g().text!="}"){do{var b=f(),b=b.string||b.text;j(":");var c=G();a.push({key:b,value:c})}while(f(","))}j("}");return function(b,c){for(var d={},e=0;e<a.length;e++){var f=a[e],i=f.value(b,c);d[f.key]=i}return d}}var r=I(0),$,P=Kc(b,d),G=function(){var a=t(),c,d;return(d=f("="))?(a.assign||e("implies assignment but ["+b.substring(0,d.index)+
|
||||
"] can not be assigned to",d),c=t(),function(b,d){return a.assign(b,c(b,d),d)}):a},x=function(a,b){var c=[];if(g().text!=")"){do c.push(G());while(f(","))}j(")");return function(d,e){for(var f=[],i=b?b(d,e):d,g=0;g<c.length;g++)f.push(c[g](d,e));g=a(d,e)||C;return g.apply?g.apply(i,f):g(f[0],f[1],f[2],f[3],f[4])}},u=function(a){var b=f().text,c=Kb(b,d);return v(function(b,d){return c(a(b,d),d)},{assign:function(c,d,e){return Lb(a(c,e),b,d)}})},R=function(a){var b=G();j("]");return v(function(c,d){var e=
|
||||
a(c,d),f=b(c,d),i;if(!e)return q;if((e=e[f])&&e.then){i=e;if(!("$$v"in e))i.$$v=q,i.then(function(a){i.$$v=a});e=e.$$v}return e},{assign:function(c,d,e){return a(c,e)[b(c,e)]=d}})},w=function(){for(var a=G(),b;;)if(b=f("|"))a=k(a,b.fn,l());else return a};a?(G=t,x=u=R=w=function(){e("is not valid json",{text:b,index:0})},$=z()):$=m();P.length!==0&&e("is an unexpected token",P[0]);return $}function Lb(b,a,c){for(var a=a.split("."),d=0;a.length>1;d++){var e=a.shift(),g=b[e];g||(g={},b[e]=g);b=g}return b[a.shift()]=
|
||||
c}function gb(b,a,c){if(!a)return b;for(var a=a.split("."),d,e=b,g=a.length,h=0;h<g;h++)d=a[h],b&&(b=(e=b)[d]);return!c&&H(b)?Ua(e,b):b}function Mb(b,a,c,d,e){return function(g,h){var f=h&&h.hasOwnProperty(b)?h:g,j;if(f===null||f===q)return f;if((f=f[b])&&f.then){if(!("$$v"in f))j=f,j.$$v=q,j.then(function(a){j.$$v=a});f=f.$$v}if(!a||f===null||f===q)return f;if((f=f[a])&&f.then){if(!("$$v"in f))j=f,j.$$v=q,j.then(function(a){j.$$v=a});f=f.$$v}if(!c||f===null||f===q)return f;if((f=f[c])&&f.then){if(!("$$v"in
|
||||
f))j=f,j.$$v=q,j.then(function(a){j.$$v=a});f=f.$$v}if(!d||f===null||f===q)return f;if((f=f[d])&&f.then){if(!("$$v"in f))j=f,j.$$v=q,j.then(function(a){j.$$v=a});f=f.$$v}if(!e||f===null||f===q)return f;if((f=f[e])&&f.then){if(!("$$v"in f))j=f,j.$$v=q,j.then(function(a){j.$$v=a});f=f.$$v}return f}}function Kb(b,a){if(ib.hasOwnProperty(b))return ib[b];var c=b.split("."),d=c.length,e;if(a)e=d<6?Mb(c[0],c[1],c[2],c[3],c[4]):function(a,b){var e=0,i;do i=Mb(c[e++],c[e++],c[e++],c[e++],c[e++])(a,b),b=q,
|
||||
a=i;while(e<d);return i};else{var g="var l, fn, p;\n";n(c,function(a,b){g+="if(s === null || s === undefined) return s;\nl=s;\ns="+(b?"s":'((k&&k.hasOwnProperty("'+a+'"))?k:s)')+'["'+a+'"];\nif (s && s.then) {\n if (!("$$v" in s)) {\n p=s;\n p.$$v = undefined;\n p.then(function(v) {p.$$v=v;});\n}\n s=s.$$v\n}\n'});g+="return s;";e=Function("s","k",g);e.toString=function(){return g}}return ib[b]=e}function Nc(){var b={};this.$get=["$filter","$sniffer",function(a,c){return function(d){switch(typeof d){case "string":return b.hasOwnProperty(d)?
|
||||
b[d]:b[d]=Mc(d,!1,a,c.csp);case "function":return d;default:return C}}}]}function Oc(){this.$get=["$rootScope","$exceptionHandler",function(b,a){return Pc(function(a){b.$evalAsync(a)},a)}]}function Pc(b,a){function c(a){return a}function d(a){return h(a)}var e=function(){var f=[],j,i;return i={resolve:function(a){if(f){var c=f;f=q;j=g(a);c.length&&b(function(){for(var a,b=0,d=c.length;b<d;b++)a=c[b],j.then(a[0],a[1])})}},reject:function(a){i.resolve(h(a))},promise:{then:function(b,i){var g=e(),h=
|
||||
function(d){try{g.resolve((b||c)(d))}catch(e){a(e),g.reject(e)}},o=function(b){try{g.resolve((i||d)(b))}catch(c){a(c),g.reject(c)}};f?f.push([h,o]):j.then(h,o);return g.promise}}}},g=function(a){return a&&a.then?a:{then:function(c){var d=e();b(function(){d.resolve(c(a))});return d.promise}}},h=function(a){return{then:function(c,i){var g=e();b(function(){g.resolve((i||d)(a))});return g.promise}}};return{defer:e,reject:h,when:function(f,j,i){var k=e(),m,l=function(b){try{return(j||c)(b)}catch(d){return a(d),
|
||||
h(d)}},t=function(b){try{return(i||d)(b)}catch(c){return a(c),h(c)}};b(function(){g(f).then(function(a){m||(m=!0,k.resolve(g(a).then(l,t)))},function(a){m||(m=!0,k.resolve(t(a)))})});return k.promise},all:function(a){var b=e(),c=a.length,d=[];c?n(a,function(a,e){g(a).then(function(a){e in d||(d[e]=a,--c||b.resolve(d))},function(a){e in d||b.reject(a)})}):b.resolve(d);return b.promise}}}function Qc(){var b={};this.when=function(a,c){b[a]=v({reloadOnSearch:!0},c);if(a){var d=a[a.length-1]=="/"?a.substr(0,
|
||||
a.length-1):a+"/";b[d]={redirectTo:a}}return this};this.otherwise=function(a){this.when(null,a);return this};this.$get=["$rootScope","$location","$routeParams","$q","$injector","$http","$templateCache",function(a,c,d,e,g,h,f){function j(a,b){for(var b="^"+b.replace(/[-\/\\^$*+?.()|[\]{}]/g,"\\$&")+"$",c="",d=[],e={},f=/:(\w+)/g,i,g=0;(i=f.exec(b))!==null;)c+=b.slice(g,i.index),c+="([^\\/]*)",d.push(i[1]),g=f.lastIndex;c+=b.substr(g);var h=a.match(RegExp(c));h&&n(d,function(a,b){e[a]=h[b+1]});return h?
|
||||
e:null}function i(){var b=k(),i=t.current;if(b&&i&&b.$route===i.$route&&ga(b.pathParams,i.pathParams)&&!b.reloadOnSearch&&!l)i.params=b.params,U(i.params,d),a.$broadcast("$routeUpdate",i);else if(b||i)l=!1,a.$broadcast("$routeChangeStart",b,i),(t.current=b)&&b.redirectTo&&(A(b.redirectTo)?c.path(m(b.redirectTo,b.params)).search(b.params).replace():c.url(b.redirectTo(b.pathParams,c.path(),c.search())).replace()),e.when(b).then(function(){if(b){var a=[],c=[],d;n(b.resolve||{},function(b,d){a.push(d);
|
||||
c.push(A(b)?g.get(b):g.invoke(b))});if(!x(d=b.template))if(x(d=b.templateUrl))d=h.get(d,{cache:f}).then(function(a){return a.data});x(d)&&(a.push("$template"),c.push(d));return e.all(c).then(function(b){var c={};n(b,function(b,d){c[a[d]]=b});return c})}}).then(function(c){if(b==t.current){if(b)b.locals=c,U(b.params,d);a.$broadcast("$routeChangeSuccess",b,i)}},function(c){b==t.current&&a.$broadcast("$routeChangeError",b,i,c)})}function k(){var a,d;n(b,function(b,e){if(!d&&(a=j(c.path(),e)))d=za(b,
|
||||
{params:v({},c.search(),a),pathParams:a}),d.$route=b});return d||b[null]&&za(b[null],{params:{},pathParams:{}})}function m(a,b){var c=[];n((a||"").split(":"),function(a,d){if(d==0)c.push(a);else{var e=a.match(/(\w+)(.*)/),f=e[1];c.push(b[f]);c.push(e[2]||"");delete b[f]}});return c.join("")}var l=!1,t={routes:b,reload:function(){l=!0;a.$evalAsync(i)}};a.$on("$locationChangeSuccess",i);return t}]}function Rc(){this.$get=I({})}function Sc(){var b=10;this.digestTtl=function(a){arguments.length&&(b=a);
|
||||
return b};this.$get=["$injector","$exceptionHandler","$parse",function(a,c,d){function e(){this.$id=ya();this.$$phase=this.$parent=this.$$watchers=this.$$nextSibling=this.$$prevSibling=this.$$childHead=this.$$childTail=null;this["this"]=this.$root=this;this.$$destroyed=!1;this.$$asyncQueue=[];this.$$listeners={};this.$$isolateBindings={}}function g(a){if(j.$$phase)throw Error(j.$$phase+" already in progress");j.$$phase=a}function h(a,b){var c=d(a);ra(c,b);return c}function f(){}e.prototype={$new:function(a){if(H(a))throw Error("API-CHANGE: Use $controller to instantiate controllers.");
|
||||
a?(a=new e,a.$root=this.$root):(a=function(){},a.prototype=this,a=new a,a.$id=ya());a["this"]=a;a.$$listeners={};a.$parent=this;a.$$asyncQueue=[];a.$$watchers=a.$$nextSibling=a.$$childHead=a.$$childTail=null;a.$$prevSibling=this.$$childTail;this.$$childHead?this.$$childTail=this.$$childTail.$$nextSibling=a:this.$$childHead=this.$$childTail=a;return a},$watch:function(a,b,c){var d=h(a,"watch"),e=this.$$watchers,g={fn:b,last:f,get:d,exp:a,eq:!!c};if(!H(b)){var j=h(b||C,"listener");g.fn=function(a,b,
|
||||
c){j(c)}}if(!e)e=this.$$watchers=[];e.unshift(g);return function(){Ta(e,g)}},$digest:function(){var a,d,e,h,t,o,p,s=b,n,F=[],z,q;g("$digest");do{p=!1;n=this;do{for(t=n.$$asyncQueue;t.length;)try{n.$eval(t.shift())}catch(K){c(K)}if(h=n.$$watchers)for(o=h.length;o--;)try{if(a=h[o],(d=a.get(n))!==(e=a.last)&&!(a.eq?ga(d,e):typeof d=="number"&&typeof e=="number"&&isNaN(d)&&isNaN(e)))p=!0,a.last=a.eq?U(d):d,a.fn(d,e===f?d:e,n),s<5&&(z=4-s,F[z]||(F[z]=[]),q=H(a.exp)?"fn: "+(a.exp.name||a.exp.toString()):
|
||||
a.exp,q+="; newVal: "+da(d)+"; oldVal: "+da(e),F[z].push(q))}catch(r){c(r)}if(!(h=n.$$childHead||n!==this&&n.$$nextSibling))for(;n!==this&&!(h=n.$$nextSibling);)n=n.$parent}while(n=h);if(p&&!s--)throw j.$$phase=null,Error(b+" $digest() iterations reached. Aborting!\nWatchers fired in the last 5 iterations: "+da(F));}while(p||t.length);j.$$phase=null},$destroy:function(){if(!(j==this||this.$$destroyed)){var a=this.$parent;this.$broadcast("$destroy");this.$$destroyed=!0;if(a.$$childHead==this)a.$$childHead=
|
||||
this.$$nextSibling;if(a.$$childTail==this)a.$$childTail=this.$$prevSibling;if(this.$$prevSibling)this.$$prevSibling.$$nextSibling=this.$$nextSibling;if(this.$$nextSibling)this.$$nextSibling.$$prevSibling=this.$$prevSibling;this.$parent=this.$$nextSibling=this.$$prevSibling=this.$$childHead=this.$$childTail=null}},$eval:function(a,b){return d(a)(this,b)},$evalAsync:function(a){this.$$asyncQueue.push(a)},$apply:function(a){try{return g("$apply"),this.$eval(a)}catch(b){c(b)}finally{j.$$phase=null;try{j.$digest()}catch(d){throw c(d),
|
||||
d;}}},$on:function(a,b){var c=this.$$listeners[a];c||(this.$$listeners[a]=c=[]);c.push(b);return function(){c[Aa(c,b)]=null}},$emit:function(a,b){var d=[],e,f=this,g=!1,h={name:a,targetScope:f,stopPropagation:function(){g=!0},preventDefault:function(){h.defaultPrevented=!0},defaultPrevented:!1},j=[h].concat(ha.call(arguments,1)),n,q;do{e=f.$$listeners[a]||d;h.currentScope=f;n=0;for(q=e.length;n<q;n++)if(e[n])try{if(e[n].apply(null,j),g)return h}catch(z){c(z)}else e.splice(n,1),n--,q--;f=f.$parent}while(f);
|
||||
return h},$broadcast:function(a,b){var d=this,e=this,f={name:a,targetScope:this,preventDefault:function(){f.defaultPrevented=!0},defaultPrevented:!1},g=[f].concat(ha.call(arguments,1)),h,j;do{d=e;f.currentScope=d;e=d.$$listeners[a]||[];h=0;for(j=e.length;h<j;h++)if(e[h])try{e[h].apply(null,g)}catch(n){c(n)}else e.splice(h,1),h--,j--;if(!(e=d.$$childHead||d!==this&&d.$$nextSibling))for(;d!==this&&!(e=d.$$nextSibling);)d=d.$parent}while(d=e);return f}};var j=new e;return j}]}function Tc(){this.$get=
|
||||
["$window",function(b){var a={},c=E((/android (\d+)/.exec(y(b.navigator.userAgent))||[])[1]);return{history:!(!b.history||!b.history.pushState||c<4),hashchange:"onhashchange"in b&&(!b.document.documentMode||b.document.documentMode>7),hasEvent:function(c){if(c=="input"&&Z==9)return!1;if(w(a[c])){var e=b.document.createElement("div");a[c]="on"+c in e}return a[c]},csp:!1}}]}function Uc(){this.$get=I(X)}function Nb(b){var a={},c,d,e;if(!b)return a;n(b.split("\n"),function(b){e=b.indexOf(":");c=y(O(b.substr(0,
|
||||
e)));d=O(b.substr(e+1));c&&(a[c]?a[c]+=", "+d:a[c]=d)});return a}function Ob(b){var a=M(b)?b:q;return function(c){a||(a=Nb(b));return c?a[y(c)]||null:a}}function Pb(b,a,c){if(H(c))return c(b,a);n(c,function(c){b=c(b,a)});return b}function Vc(){var b=/^\s*(\[|\{[^\{])/,a=/[\}\]]\s*$/,c=/^\)\]\}',?\n/,d=this.defaults={transformResponse:[function(d){A(d)&&(d=d.replace(c,""),b.test(d)&&a.test(d)&&(d=ob(d,!0)));return d}],transformRequest:[function(a){return M(a)&&xa.apply(a)!=="[object File]"?da(a):a}],
|
||||
headers:{common:{Accept:"application/json, text/plain, */*","X-Requested-With":"XMLHttpRequest"},post:{"Content-Type":"application/json;charset=utf-8"},put:{"Content-Type":"application/json;charset=utf-8"}}},e=this.responseInterceptors=[];this.$get=["$httpBackend","$browser","$cacheFactory","$rootScope","$q","$injector",function(a,b,c,j,i,k){function m(a){function c(a){var b=v({},a,{data:Pb(a.data,a.headers,f)});return 200<=a.status&&a.status<300?b:i.reject(b)}a.method=ma(a.method);var e=a.transformRequest||
|
||||
d.transformRequest,f=a.transformResponse||d.transformResponse,g=d.headers,g=v({"X-XSRF-TOKEN":b.cookies()["XSRF-TOKEN"]},g.common,g[y(a.method)],a.headers),e=Pb(a.data,Ob(g),e),j;w(a.data)&&delete g["Content-Type"];j=l(a,e,g);j=j.then(c,c);n(p,function(a){j=a(j)});j.success=function(b){j.then(function(c){b(c.data,c.status,c.headers,a)});return j};j.error=function(b){j.then(null,function(c){b(c.data,c.status,c.headers,a)});return j};return j}function l(b,c,d){function e(a,b,c){n&&(200<=a&&a<300?n.put(q,
|
||||
[a,b,Nb(c)]):n.remove(q));f(b,a,c);j.$apply()}function f(a,c,d){c=Math.max(c,0);(200<=c&&c<300?k.resolve:k.reject)({data:a,status:c,headers:Ob(d),config:b})}function h(){var a=Aa(m.pendingRequests,b);a!==-1&&m.pendingRequests.splice(a,1)}var k=i.defer(),l=k.promise,n,p,q=t(b.url,b.params);m.pendingRequests.push(b);l.then(h,h);b.cache&&b.method=="GET"&&(n=M(b.cache)?b.cache:o);if(n)if(p=n.get(q))if(p.then)return p.then(h,h),p;else B(p)?f(p[1],p[0],U(p[2])):f(p,200,{});else n.put(q,l);p||a(b.method,
|
||||
q,c,e,d,b.timeout,b.withCredentials);return l}function t(a,b){if(!b)return a;var c=[];fc(b,function(a,b){a==null||a==q||(M(a)&&(a=da(a)),c.push(encodeURIComponent(b)+"="+encodeURIComponent(a)))});return a+(a.indexOf("?")==-1?"?":"&")+c.join("&")}var o=c("$http"),p=[];n(e,function(a){p.push(A(a)?k.get(a):k.invoke(a))});m.pendingRequests=[];(function(a){n(arguments,function(a){m[a]=function(b,c){return m(v(c||{},{method:a,url:b}))}})})("get","delete","head","jsonp");(function(a){n(arguments,function(a){m[a]=
|
||||
function(b,c,d){return m(v(d||{},{method:a,url:b,data:c}))}})})("post","put");m.defaults=d;return m}]}function Wc(){this.$get=["$browser","$window","$document",function(b,a,c){return Xc(b,Yc,b.defer,a.angular.callbacks,c[0],a.location.protocol.replace(":",""))}]}function Xc(b,a,c,d,e,g){function h(a,b){var c=e.createElement("script"),d=function(){e.body.removeChild(c);b&&b()};c.type="text/javascript";c.src=a;Z?c.onreadystatechange=function(){/loaded|complete/.test(c.readyState)&&d()}:c.onload=c.onerror=
|
||||
d;e.body.appendChild(c)}return function(e,j,i,k,m,l,t){function o(a,c,d,e){c=(j.match(Gb)||["",g])[1]=="file"?d?200:404:c;a(c==1223?204:c,d,e);b.$$completeOutstandingRequest(C)}b.$$incOutstandingRequestCount();j=j||b.url();if(y(e)=="jsonp"){var p="_"+(d.counter++).toString(36);d[p]=function(a){d[p].data=a};h(j.replace("JSON_CALLBACK","angular.callbacks."+p),function(){d[p].data?o(k,200,d[p].data):o(k,-2);delete d[p]})}else{var s=new a;s.open(e,j,!0);n(m,function(a,b){a&&s.setRequestHeader(b,a)});
|
||||
var q;s.onreadystatechange=function(){if(s.readyState==4){var a=s.getAllResponseHeaders(),b=["Cache-Control","Content-Language","Content-Type","Expires","Last-Modified","Pragma"];a||(a="",n(b,function(b){var c=s.getResponseHeader(b);c&&(a+=b+": "+c+"\n")}));o(k,q||s.status,s.responseText,a)}};if(t)s.withCredentials=!0;s.send(i||"");l>0&&c(function(){q=-1;s.abort()},l)}}}function Zc(){this.$get=function(){return{id:"en-us",NUMBER_FORMATS:{DECIMAL_SEP:".",GROUP_SEP:",",PATTERNS:[{minInt:1,minFrac:0,
|
||||
maxFrac:3,posPre:"",posSuf:"",negPre:"-",negSuf:"",gSize:3,lgSize:3},{minInt:1,minFrac:2,maxFrac:2,posPre:"\u00a4",posSuf:"",negPre:"(\u00a4",negSuf:")",gSize:3,lgSize:3}],CURRENCY_SYM:"$"},DATETIME_FORMATS:{MONTH:"January,February,March,April,May,June,July,August,September,October,November,December".split(","),SHORTMONTH:"Jan,Feb,Mar,Apr,May,Jun,Jul,Aug,Sep,Oct,Nov,Dec".split(","),DAY:"Sunday,Monday,Tuesday,Wednesday,Thursday,Friday,Saturday".split(","),SHORTDAY:"Sun,Mon,Tue,Wed,Thu,Fri,Sat".split(","),
|
||||
AMPMS:["AM","PM"],medium:"MMM d, y h:mm:ss a","short":"M/d/yy h:mm a",fullDate:"EEEE, MMMM d, y",longDate:"MMMM d, y",mediumDate:"MMM d, y",shortDate:"M/d/yy",mediumTime:"h:mm:ss a",shortTime:"h:mm a"},pluralCat:function(b){return b===1?"one":"other"}}}}function $c(){this.$get=["$rootScope","$browser","$q","$exceptionHandler",function(b,a,c,d){function e(e,f,j){var i=c.defer(),k=i.promise,m=x(j)&&!j,f=a.defer(function(){try{i.resolve(e())}catch(a){i.reject(a),d(a)}m||b.$apply()},f),j=function(){delete g[k.$$timeoutId]};
|
||||
k.$$timeoutId=f;g[f]=i;k.then(j,j);return k}var g={};e.cancel=function(b){return b&&b.$$timeoutId in g?(g[b.$$timeoutId].reject("canceled"),a.defer.cancel(b.$$timeoutId)):!1};return e}]}function Qb(b){function a(a,e){return b.factory(a+c,e)}var c="Filter";this.register=a;this.$get=["$injector",function(a){return function(b){return a.get(b+c)}}];a("currency",Rb);a("date",Sb);a("filter",ad);a("json",bd);a("limitTo",cd);a("lowercase",dd);a("number",Tb);a("orderBy",Ub);a("uppercase",ed)}function ad(){return function(b,
|
||||
a){if(!B(b))return b;var c=[];c.check=function(a){for(var b=0;b<c.length;b++)if(!c[b](a))return!1;return!0};var d=function(a,b){if(b.charAt(0)==="!")return!d(a,b.substr(1));switch(typeof a){case "boolean":case "number":case "string":return(""+a).toLowerCase().indexOf(b)>-1;case "object":for(var c in a)if(c.charAt(0)!=="$"&&d(a[c],b))return!0;return!1;case "array":for(c=0;c<a.length;c++)if(d(a[c],b))return!0;return!1;default:return!1}};switch(typeof a){case "boolean":case "number":case "string":a=
|
||||
{$:a};case "object":for(var e in a)e=="$"?function(){var b=(""+a[e]).toLowerCase();b&&c.push(function(a){return d(a,b)})}():function(){var b=e,f=(""+a[e]).toLowerCase();f&&c.push(function(a){return d(gb(a,b),f)})}();break;case "function":c.push(a);break;default:return b}for(var g=[],h=0;h<b.length;h++){var f=b[h];c.check(f)&&g.push(f)}return g}}function Rb(b){var a=b.NUMBER_FORMATS;return function(b,d){if(w(d))d=a.CURRENCY_SYM;return Vb(b,a.PATTERNS[1],a.GROUP_SEP,a.DECIMAL_SEP,2).replace(/\u00A4/g,
|
||||
d)}}function Tb(b){var a=b.NUMBER_FORMATS;return function(b,d){return Vb(b,a.PATTERNS[0],a.GROUP_SEP,a.DECIMAL_SEP,d)}}function Vb(b,a,c,d,e){if(isNaN(b)||!isFinite(b))return"";var g=b<0,b=Math.abs(b),h=b+"",f="",j=[],i=!1;if(h.indexOf("e")!==-1){var k=h.match(/([\d\.]+)e(-?)(\d+)/);k&&k[2]=="-"&&k[3]>e+1?h="0":(f=h,i=!0)}if(!i){h=(h.split(Wb)[1]||"").length;w(e)&&(e=Math.min(Math.max(a.minFrac,h),a.maxFrac));var h=Math.pow(10,e),b=Math.round(b*h)/h,b=(""+b).split(Wb),h=b[0],b=b[1]||"",i=0,k=a.lgSize,
|
||||
m=a.gSize;if(h.length>=k+m)for(var i=h.length-k,l=0;l<i;l++)(i-l)%m===0&&l!==0&&(f+=c),f+=h.charAt(l);for(l=i;l<h.length;l++)(h.length-l)%k===0&&l!==0&&(f+=c),f+=h.charAt(l);for(;b.length<e;)b+="0";e&&e!=="0"&&(f+=d+b.substr(0,e))}j.push(g?a.negPre:a.posPre);j.push(f);j.push(g?a.negSuf:a.posSuf);return j.join("")}function jb(b,a,c){var d="";b<0&&(d="-",b=-b);for(b=""+b;b.length<a;)b="0"+b;c&&(b=b.substr(b.length-a));return d+b}function N(b,a,c,d){return function(e){e=e["get"+b]();if(c>0||e>-c)e+=
|
||||
c;e===0&&c==-12&&(e=12);return jb(e,a,d)}}function Ka(b,a){return function(c,d){var e=c["get"+b](),g=ma(a?"SHORT"+b:b);return d[g][e]}}function Sb(b){function a(a){var b;if(b=a.match(c)){var a=new Date(0),g=0,h=0;b[9]&&(g=E(b[9]+b[10]),h=E(b[9]+b[11]));a.setUTCFullYear(E(b[1]),E(b[2])-1,E(b[3]));a.setUTCHours(E(b[4]||0)-g,E(b[5]||0)-h,E(b[6]||0),E(b[7]||0))}return a}var c=/^(\d{4})-?(\d\d)-?(\d\d)(?:T(\d\d)(?::?(\d\d)(?::?(\d\d)(?:\.(\d+))?)?)?(Z|([+-])(\d\d):?(\d\d))?)?$/;return function(c,e){var g=
|
||||
"",h=[],f,j,e=e||"mediumDate",e=b.DATETIME_FORMATS[e]||e;A(c)&&(c=fd.test(c)?E(c):a(c));Ra(c)&&(c=new Date(c));if(!oa(c))return c;for(;e;)(j=gd.exec(e))?(h=h.concat(ha.call(j,1)),e=h.pop()):(h.push(e),e=null);n(h,function(a){f=hd[a];g+=f?f(c,b.DATETIME_FORMATS):a.replace(/(^'|'$)/g,"").replace(/''/g,"'")});return g}}function bd(){return function(b){return da(b,!0)}}function cd(){return function(b,a){if(!(b instanceof Array))return b;var a=E(a),c=[],d,e;if(!b||!(b instanceof Array))return c;a>b.length?
|
||||
a=b.length:a<-b.length&&(a=-b.length);a>0?(d=0,e=a):(d=b.length+a,e=b.length);for(;d<e;d++)c.push(b[d]);return c}}function Ub(b){return function(a,c,d){function e(a,b){return Va(b)?function(b,c){return a(c,b)}:a}if(!B(a))return a;if(!c)return a;for(var c=B(c)?c:[c],c=Sa(c,function(a){var c=!1,d=a||na;if(A(a)){if(a.charAt(0)=="+"||a.charAt(0)=="-")c=a.charAt(0)=="-",a=a.substring(1);d=b(a)}return e(function(a,b){var c;c=d(a);var e=d(b),f=typeof c,g=typeof e;f==g?(f=="string"&&(c=c.toLowerCase()),f==
|
||||
"string"&&(e=e.toLowerCase()),c=c===e?0:c<e?-1:1):c=f<g?-1:1;return c},c)}),g=[],h=0;h<a.length;h++)g.push(a[h]);return g.sort(e(function(a,b){for(var d=0;d<c.length;d++){var e=c[d](a,b);if(e!==0)return e}return 0},d))}}function Q(b){H(b)&&(b={link:b});b.restrict=b.restrict||"AC";return I(b)}function Xb(b,a){function c(a,c){c=c?"-"+Za(c,"-"):"";b.removeClass((a?La:Ma)+c).addClass((a?Ma:La)+c)}var d=this,e=b.parent().controller("form")||Na,g=0,h=d.$error={};d.$name=a.name;d.$dirty=!1;d.$pristine=!0;
|
||||
d.$valid=!0;d.$invalid=!1;e.$addControl(d);b.addClass(Oa);c(!0);d.$addControl=function(a){a.$name&&!d.hasOwnProperty(a.$name)&&(d[a.$name]=a)};d.$removeControl=function(a){a.$name&&d[a.$name]===a&&delete d[a.$name];n(h,function(b,c){d.$setValidity(c,!0,a)})};d.$setValidity=function(a,b,i){var k=h[a];if(b){if(k&&(Ta(k,i),!k.length)){g--;if(!g)c(b),d.$valid=!0,d.$invalid=!1;h[a]=!1;c(!0,a);e.$setValidity(a,!0,d)}}else{g||c(b);if(k){if(Aa(k,i)!=-1)return}else h[a]=k=[],g++,c(!1,a),e.$setValidity(a,!1,
|
||||
d);k.push(i);d.$valid=!1;d.$invalid=!0}};d.$setDirty=function(){b.removeClass(Oa).addClass(Yb);d.$dirty=!0;d.$pristine=!1;e.$setDirty()}}function T(b){return w(b)||b===""||b===null||b!==b}function Pa(b,a,c,d,e,g){var h=function(){var c=O(a.val());d.$viewValue!==c&&b.$apply(function(){d.$setViewValue(c)})};if(e.hasEvent("input"))a.bind("input",h);else{var f;a.bind("keydown",function(a){a=a.keyCode;a===91||15<a&&a<19||37<=a&&a<=40||f||(f=g.defer(function(){h();f=null}))});a.bind("change",h)}d.$render=
|
||||
function(){a.val(T(d.$viewValue)?"":d.$viewValue)};var j=c.ngPattern,i=function(a,b){return T(b)||a.test(b)?(d.$setValidity("pattern",!0),b):(d.$setValidity("pattern",!1),q)};j&&(j.match(/^\/(.*)\/$/)?(j=RegExp(j.substr(1,j.length-2)),e=function(a){return i(j,a)}):e=function(a){var c=b.$eval(j);if(!c||!c.test)throw Error("Expected "+j+" to be a RegExp but was "+c);return i(c,a)},d.$formatters.push(e),d.$parsers.push(e));if(c.ngMinlength){var k=E(c.ngMinlength),e=function(a){return!T(a)&&a.length<
|
||||
k?(d.$setValidity("minlength",!1),q):(d.$setValidity("minlength",!0),a)};d.$parsers.push(e);d.$formatters.push(e)}if(c.ngMaxlength){var m=E(c.ngMaxlength),c=function(a){return!T(a)&&a.length>m?(d.$setValidity("maxlength",!1),q):(d.$setValidity("maxlength",!0),a)};d.$parsers.push(c);d.$formatters.push(c)}}function kb(b,a){b="ngClass"+b;return Q(function(c,d,e){function g(b){if(a===!0||c.$index%2===a)j&&b!==j&&h(j),f(b);j=b}function h(a){M(a)&&!B(a)&&(a=Sa(a,function(a,b){if(a)return b}));d.removeClass(B(a)?
|
||||
a.join(" "):a)}function f(a){M(a)&&!B(a)&&(a=Sa(a,function(a,b){if(a)return b}));a&&d.addClass(B(a)?a.join(" "):a)}var j=q;c.$watch(e[b],g,!0);e.$observe("class",function(){var a=c.$eval(e[b]);g(a,a)});b!=="ngClass"&&c.$watch("$index",function(d,g){var j=d%2;j!==g%2&&(j==a?f(c.$eval(e[b])):h(c.$eval(e[b])))})})}var y=function(b){return A(b)?b.toLowerCase():b},ma=function(b){return A(b)?b.toUpperCase():b},Z=E((/msie (\d+)/.exec(y(navigator.userAgent))||[])[1]),u,ca,ha=[].slice,Qa=[].push,xa=Object.prototype.toString,
|
||||
Zb=X.angular||(X.angular={}),ta,fb,aa=["0","0","0"];C.$inject=[];na.$inject=[];fb=Z<9?function(b){b=b.nodeName?b:b[0];return b.scopeName&&b.scopeName!="HTML"?ma(b.scopeName+":"+b.nodeName):b.nodeName}:function(b){return b.nodeName?b.nodeName:b[0].nodeName};var kc=/[A-Z]/g,id={full:"1.0.5",major:1,minor:0,dot:5,codeName:"flatulent-propulsion"},Ca=L.cache={},Ba=L.expando="ng-"+(new Date).getTime(),oc=1,$b=X.document.addEventListener?function(b,a,c){b.addEventListener(a,c,!1)}:function(b,a,c){b.attachEvent("on"+
|
||||
a,c)},db=X.document.removeEventListener?function(b,a,c){b.removeEventListener(a,c,!1)}:function(b,a,c){b.detachEvent("on"+a,c)},mc=/([\:\-\_]+(.))/g,nc=/^moz([A-Z])/,va=L.prototype={ready:function(b){function a(){c||(c=!0,b())}var c=!1;this.bind("DOMContentLoaded",a);L(X).bind("load",a)},toString:function(){var b=[];n(this,function(a){b.push(""+a)});return"["+b.join(", ")+"]"},eq:function(b){return b>=0?u(this[b]):u(this[this.length+b])},length:0,push:Qa,sort:[].sort,splice:[].splice},Fa={};n("multiple,selected,checked,disabled,readOnly,required".split(","),
|
||||
function(b){Fa[y(b)]=b});var Ab={};n("input,select,option,textarea,button,form".split(","),function(b){Ab[ma(b)]=!0});n({data:vb,inheritedData:Ea,scope:function(b){return Ea(b,"$scope")},controller:yb,injector:function(b){return Ea(b,"$injector")},removeAttr:function(b,a){b.removeAttribute(a)},hasClass:Da,css:function(b,a,c){a=sb(a);if(x(c))b.style[a]=c;else{var d;Z<=8&&(d=b.currentStyle&&b.currentStyle[a],d===""&&(d="auto"));d=d||b.style[a];Z<=8&&(d=d===""?q:d);return d}},attr:function(b,a,c){var d=
|
||||
y(a);if(Fa[d])if(x(c))c?(b[a]=!0,b.setAttribute(a,d)):(b[a]=!1,b.removeAttribute(d));else return b[a]||(b.attributes.getNamedItem(a)||C).specified?d:q;else if(x(c))b.setAttribute(a,c);else if(b.getAttribute)return b=b.getAttribute(a,2),b===null?q:b},prop:function(b,a,c){if(x(c))b[a]=c;else return b[a]},text:v(Z<9?function(b,a){if(b.nodeType==1){if(w(a))return b.innerText;b.innerText=a}else{if(w(a))return b.nodeValue;b.nodeValue=a}}:function(b,a){if(w(a))return b.textContent;b.textContent=a},{$dv:""}),
|
||||
val:function(b,a){if(w(a))return b.value;b.value=a},html:function(b,a){if(w(a))return b.innerHTML;for(var c=0,d=b.childNodes;c<d.length;c++)sa(d[c]);b.innerHTML=a}},function(b,a){L.prototype[a]=function(a,d){var e,g;if((b.length==2&&b!==Da&&b!==yb?a:d)===q)if(M(a)){for(e=0;e<this.length;e++)if(b===vb)b(this[e],a);else for(g in a)b(this[e],g,a[g]);return this}else{if(this.length)return b(this[0],a,d)}else{for(e=0;e<this.length;e++)b(this[e],a,d);return this}return b.$dv}});n({removeData:tb,dealoc:sa,
|
||||
bind:function a(c,d,e){var g=ba(c,"events"),h=ba(c,"handle");g||ba(c,"events",g={});h||ba(c,"handle",h=pc(c,g));n(d.split(" "),function(d){var j=g[d];if(!j){if(d=="mouseenter"||d=="mouseleave"){var i=0;g.mouseenter=[];g.mouseleave=[];a(c,"mouseover",function(a){i++;i==1&&h(a,"mouseenter")});a(c,"mouseout",function(a){i--;i==0&&h(a,"mouseleave")})}else $b(c,d,h),g[d]=[];j=g[d]}j.push(e)})},unbind:ub,replaceWith:function(a,c){var d,e=a.parentNode;sa(a);n(new L(c),function(c){d?e.insertBefore(c,d.nextSibling):
|
||||
e.replaceChild(c,a);d=c})},children:function(a){var c=[];n(a.childNodes,function(a){a.nodeType===1&&c.push(a)});return c},contents:function(a){return a.childNodes||[]},append:function(a,c){n(new L(c),function(c){a.nodeType===1&&a.appendChild(c)})},prepend:function(a,c){if(a.nodeType===1){var d=a.firstChild;n(new L(c),function(c){d?a.insertBefore(c,d):(a.appendChild(c),d=c)})}},wrap:function(a,c){var c=u(c)[0],d=a.parentNode;d&&d.replaceChild(c,a);c.appendChild(a)},remove:function(a){sa(a);var c=a.parentNode;
|
||||
c&&c.removeChild(a)},after:function(a,c){var d=a,e=a.parentNode;n(new L(c),function(a){e.insertBefore(a,d.nextSibling);d=a})},addClass:xb,removeClass:wb,toggleClass:function(a,c,d){w(d)&&(d=!Da(a,c));(d?xb:wb)(a,c)},parent:function(a){return(a=a.parentNode)&&a.nodeType!==11?a:null},next:function(a){if(a.nextElementSibling)return a.nextElementSibling;for(a=a.nextSibling;a!=null&&a.nodeType!==1;)a=a.nextSibling;return a},find:function(a,c){return a.getElementsByTagName(c)},clone:cb,triggerHandler:function(a,
|
||||
c){var d=(ba(a,"events")||{})[c];n(d,function(c){c.call(a,null)})}},function(a,c){L.prototype[c]=function(c,e){for(var g,h=0;h<this.length;h++)g==q?(g=a(this[h],c,e),g!==q&&(g=u(g))):bb(g,a(this[h],c,e));return g==q?this:g}});Ga.prototype={put:function(a,c){this[fa(a)]=c},get:function(a){return this[fa(a)]},remove:function(a){var c=this[a=fa(a)];delete this[a];return c}};eb.prototype={push:function(a,c){var d=this[a=fa(a)];d?d.push(c):this[a]=[c]},shift:function(a){var c=this[a=fa(a)];if(c)return c.length==
|
||||
1?(delete this[a],c[0]):c.shift()},peek:function(a){if(a=this[fa(a)])return a[0]}};var rc=/^function\s*[^\(]*\(\s*([^\)]*)\)/m,sc=/,/,tc=/^\s*(_?)(\S+?)\1\s*$/,qc=/((\/\/.*$)|(\/\*[\s\S]*?\*\/))/mg,Db="Non-assignable model expression: ";Cb.$inject=["$provide"];var Ac=/^(x[\:\-_]|data[\:\-_])/i,Gb=/^([^:]+):\/\/(\w+:{0,1}\w*@)?([\w\.-]*)(:([0-9]+))?(\/[^\?#]*)?(\?([^#]*))?(#(.*))?$/,ac=/^([^\?#]*)?(\?([^#]*))?(#(.*))?$/,Hc=ac,Hb={http:80,https:443,ftp:21};hb.prototype={$$replace:!1,absUrl:Ia("$$absUrl"),
|
||||
url:function(a,c){if(w(a))return this.$$url;var d=ac.exec(a);d[1]&&this.path(decodeURIComponent(d[1]));if(d[2]||d[1])this.search(d[3]||"");this.hash(d[5]||"",c);return this},protocol:Ia("$$protocol"),host:Ia("$$host"),port:Ia("$$port"),path:Jb("$$path",function(a){return a.charAt(0)=="/"?a:"/"+a}),search:function(a,c){if(w(a))return this.$$search;x(c)?c===null?delete this.$$search[a]:this.$$search[a]=c:this.$$search=A(a)?Wa(a):a;this.$$compose();return this},hash:Jb("$$hash",na),replace:function(){this.$$replace=
|
||||
!0;return this}};Ha.prototype=za(hb.prototype);Ib.prototype=za(Ha.prototype);var Ja={"null":function(){return null},"true":function(){return!0},"false":function(){return!1},undefined:C,"+":function(a,c,d,e){d=d(a,c);e=e(a,c);return x(d)?x(e)?d+e:d:x(e)?e:q},"-":function(a,c,d,e){d=d(a,c);e=e(a,c);return(x(d)?d:0)-(x(e)?e:0)},"*":function(a,c,d,e){return d(a,c)*e(a,c)},"/":function(a,c,d,e){return d(a,c)/e(a,c)},"%":function(a,c,d,e){return d(a,c)%e(a,c)},"^":function(a,c,d,e){return d(a,c)^e(a,c)},
|
||||
"=":C,"==":function(a,c,d,e){return d(a,c)==e(a,c)},"!=":function(a,c,d,e){return d(a,c)!=e(a,c)},"<":function(a,c,d,e){return d(a,c)<e(a,c)},">":function(a,c,d,e){return d(a,c)>e(a,c)},"<=":function(a,c,d,e){return d(a,c)<=e(a,c)},">=":function(a,c,d,e){return d(a,c)>=e(a,c)},"&&":function(a,c,d,e){return d(a,c)&&e(a,c)},"||":function(a,c,d,e){return d(a,c)||e(a,c)},"&":function(a,c,d,e){return d(a,c)&e(a,c)},"|":function(a,c,d,e){return e(a,c)(a,c,d(a,c))},"!":function(a,c,d){return!d(a,c)}},Lc=
|
||||
{n:"\n",f:"\u000c",r:"\r",t:"\t",v:"\u000b","'":"'",'"':'"'},ib={},Yc=X.XMLHttpRequest||function(){try{return new ActiveXObject("Msxml2.XMLHTTP.6.0")}catch(a){}try{return new ActiveXObject("Msxml2.XMLHTTP.3.0")}catch(c){}try{return new ActiveXObject("Msxml2.XMLHTTP")}catch(d){}throw Error("This browser does not support XMLHttpRequest.");};Qb.$inject=["$provide"];Rb.$inject=["$locale"];Tb.$inject=["$locale"];var Wb=".",hd={yyyy:N("FullYear",4),yy:N("FullYear",2,0,!0),y:N("FullYear",1),MMMM:Ka("Month"),
|
||||
MMM:Ka("Month",!0),MM:N("Month",2,1),M:N("Month",1,1),dd:N("Date",2),d:N("Date",1),HH:N("Hours",2),H:N("Hours",1),hh:N("Hours",2,-12),h:N("Hours",1,-12),mm:N("Minutes",2),m:N("Minutes",1),ss:N("Seconds",2),s:N("Seconds",1),EEEE:Ka("Day"),EEE:Ka("Day",!0),a:function(a,c){return a.getHours()<12?c.AMPMS[0]:c.AMPMS[1]},Z:function(a){var a=-1*a.getTimezoneOffset(),c=a>=0?"+":"";c+=jb(a/60,2)+jb(Math.abs(a%60),2);return c}},gd=/((?:[^yMdHhmsaZE']+)|(?:'(?:[^']|'')*')|(?:E+|y+|M+|d+|H+|h+|m+|s+|a|Z))(.*)/,
|
||||
fd=/^\d+$/;Sb.$inject=["$locale"];var dd=I(y),ed=I(ma);Ub.$inject=["$parse"];var jd=I({restrict:"E",compile:function(a,c){Z<=8&&(!c.href&&!c.name&&c.$set("href",""),a.append(Y.createComment("IE fix")));return function(a,c){c.bind("click",function(a){c.attr("href")||a.preventDefault()})}}}),lb={};n(Fa,function(a,c){var d=ea("ng-"+c);lb[d]=function(){return{priority:100,compile:function(){return function(a,g,h){a.$watch(h[d],function(a){h.$set(c,!!a)})}}}}});n(["src","href"],function(a){var c=ea("ng-"+
|
||||
a);lb[c]=function(){return{priority:99,link:function(d,e,g){g.$observe(c,function(c){c&&(g.$set(a,c),Z&&e.prop(a,g[a]))})}}}});var Na={$addControl:C,$removeControl:C,$setValidity:C,$setDirty:C};Xb.$inject=["$element","$attrs","$scope"];var Qa=function(a){return["$timeout",function(c){var d={name:"form",restrict:"E",controller:Xb,compile:function(){return{pre:function(a,d,h,f){if(!h.action){var j=function(a){a.preventDefault?a.preventDefault():a.returnValue=!1};$b(d[0],"submit",j);d.bind("$destroy",
|
||||
function(){c(function(){db(d[0],"submit",j)},0,!1)})}var i=d.parent().controller("form"),k=h.name||h.ngForm;k&&(a[k]=f);i&&d.bind("$destroy",function(){i.$removeControl(f);k&&(a[k]=q);v(f,Na)})}}}};return a?v(U(d),{restrict:"EAC"}):d}]},kd=Qa(),ld=Qa(!0),md=/^(ftp|http|https):\/\/(\w+:{0,1}\w*@)?(\S+)(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-\/]))?$/,nd=/^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,4}$/,od=/^\s*(\-|\+)?(\d+|(\d*(\.\d*)))\s*$/,bc={text:Pa,number:function(a,c,d,e,g,h){Pa(a,c,d,e,g,h);e.$parsers.push(function(a){var c=
|
||||
T(a);return c||od.test(a)?(e.$setValidity("number",!0),a===""?null:c?a:parseFloat(a)):(e.$setValidity("number",!1),q)});e.$formatters.push(function(a){return T(a)?"":""+a});if(d.min){var f=parseFloat(d.min),a=function(a){return!T(a)&&a<f?(e.$setValidity("min",!1),q):(e.$setValidity("min",!0),a)};e.$parsers.push(a);e.$formatters.push(a)}if(d.max){var j=parseFloat(d.max),d=function(a){return!T(a)&&a>j?(e.$setValidity("max",!1),q):(e.$setValidity("max",!0),a)};e.$parsers.push(d);e.$formatters.push(d)}e.$formatters.push(function(a){return T(a)||
|
||||
Ra(a)?(e.$setValidity("number",!0),a):(e.$setValidity("number",!1),q)})},url:function(a,c,d,e,g,h){Pa(a,c,d,e,g,h);a=function(a){return T(a)||md.test(a)?(e.$setValidity("url",!0),a):(e.$setValidity("url",!1),q)};e.$formatters.push(a);e.$parsers.push(a)},email:function(a,c,d,e,g,h){Pa(a,c,d,e,g,h);a=function(a){return T(a)||nd.test(a)?(e.$setValidity("email",!0),a):(e.$setValidity("email",!1),q)};e.$formatters.push(a);e.$parsers.push(a)},radio:function(a,c,d,e){w(d.name)&&c.attr("name",ya());c.bind("click",
|
||||
function(){c[0].checked&&a.$apply(function(){e.$setViewValue(d.value)})});e.$render=function(){c[0].checked=d.value==e.$viewValue};d.$observe("value",e.$render)},checkbox:function(a,c,d,e){var g=d.ngTrueValue,h=d.ngFalseValue;A(g)||(g=!0);A(h)||(h=!1);c.bind("click",function(){a.$apply(function(){e.$setViewValue(c[0].checked)})});e.$render=function(){c[0].checked=e.$viewValue};e.$formatters.push(function(a){return a===g});e.$parsers.push(function(a){return a?g:h})},hidden:C,button:C,submit:C,reset:C},
|
||||
cc=["$browser","$sniffer",function(a,c){return{restrict:"E",require:"?ngModel",link:function(d,e,g,h){h&&(bc[y(g.type)]||bc.text)(d,e,g,h,c,a)}}}],Ma="ng-valid",La="ng-invalid",Oa="ng-pristine",Yb="ng-dirty",pd=["$scope","$exceptionHandler","$attrs","$element","$parse",function(a,c,d,e,g){function h(a,c){c=c?"-"+Za(c,"-"):"";e.removeClass((a?La:Ma)+c).addClass((a?Ma:La)+c)}this.$modelValue=this.$viewValue=Number.NaN;this.$parsers=[];this.$formatters=[];this.$viewChangeListeners=[];this.$pristine=
|
||||
!0;this.$dirty=!1;this.$valid=!0;this.$invalid=!1;this.$name=d.name;var f=g(d.ngModel),j=f.assign;if(!j)throw Error(Db+d.ngModel+" ("+qa(e)+")");this.$render=C;var i=e.inheritedData("$formController")||Na,k=0,m=this.$error={};e.addClass(Oa);h(!0);this.$setValidity=function(a,c){if(m[a]!==!c){if(c){if(m[a]&&k--,!k)h(!0),this.$valid=!0,this.$invalid=!1}else h(!1),this.$invalid=!0,this.$valid=!1,k++;m[a]=!c;h(c,a);i.$setValidity(a,c,this)}};this.$setViewValue=function(d){this.$viewValue=d;if(this.$pristine)this.$dirty=
|
||||
!0,this.$pristine=!1,e.removeClass(Oa).addClass(Yb),i.$setDirty();n(this.$parsers,function(a){d=a(d)});if(this.$modelValue!==d)this.$modelValue=d,j(a,d),n(this.$viewChangeListeners,function(a){try{a()}catch(d){c(d)}})};var l=this;a.$watch(function(){var c=f(a);if(l.$modelValue!==c){var d=l.$formatters,e=d.length;for(l.$modelValue=c;e--;)c=d[e](c);if(l.$viewValue!==c)l.$viewValue=c,l.$render()}})}],qd=function(){return{require:["ngModel","^?form"],controller:pd,link:function(a,c,d,e){var g=e[0],h=
|
||||
e[1]||Na;h.$addControl(g);c.bind("$destroy",function(){h.$removeControl(g)})}}},rd=I({require:"ngModel",link:function(a,c,d,e){e.$viewChangeListeners.push(function(){a.$eval(d.ngChange)})}}),dc=function(){return{require:"?ngModel",link:function(a,c,d,e){if(e){d.required=!0;var g=function(a){if(d.required&&(T(a)||a===!1))e.$setValidity("required",!1);else return e.$setValidity("required",!0),a};e.$formatters.push(g);e.$parsers.unshift(g);d.$observe("required",function(){g(e.$viewValue)})}}}},sd=function(){return{require:"ngModel",
|
||||
link:function(a,c,d,e){var g=(a=/\/(.*)\//.exec(d.ngList))&&RegExp(a[1])||d.ngList||",";e.$parsers.push(function(a){var c=[];a&&n(a.split(g),function(a){a&&c.push(O(a))});return c});e.$formatters.push(function(a){return B(a)?a.join(", "):q})}}},td=/^(true|false|\d+)$/,ud=function(){return{priority:100,compile:function(a,c){return td.test(c.ngValue)?function(a,c,g){g.$set("value",a.$eval(g.ngValue))}:function(a,c,g){a.$watch(g.ngValue,function(a){g.$set("value",a,!1)})}}}},vd=Q(function(a,c,d){c.addClass("ng-binding").data("$binding",
|
||||
d.ngBind);a.$watch(d.ngBind,function(a){c.text(a==q?"":a)})}),wd=["$interpolate",function(a){return function(c,d,e){c=a(d.attr(e.$attr.ngBindTemplate));d.addClass("ng-binding").data("$binding",c);e.$observe("ngBindTemplate",function(a){d.text(a)})}}],xd=[function(){return function(a,c,d){c.addClass("ng-binding").data("$binding",d.ngBindHtmlUnsafe);a.$watch(d.ngBindHtmlUnsafe,function(a){c.html(a||"")})}}],yd=kb("",!0),zd=kb("Odd",0),Ad=kb("Even",1),Bd=Q({compile:function(a,c){c.$set("ngCloak",q);
|
||||
a.removeClass("ng-cloak")}}),Cd=[function(){return{scope:!0,controller:"@"}}],Dd=["$sniffer",function(a){return{priority:1E3,compile:function(){a.csp=!0}}}],ec={};n("click dblclick mousedown mouseup mouseover mouseout mousemove mouseenter mouseleave".split(" "),function(a){var c=ea("ng-"+a);ec[c]=["$parse",function(d){return function(e,g,h){var f=d(h[c]);g.bind(y(a),function(a){e.$apply(function(){f(e,{$event:a})})})}}]});var Ed=Q(function(a,c,d){c.bind("submit",function(){a.$apply(d.ngSubmit)})}),
|
||||
Fd=["$http","$templateCache","$anchorScroll","$compile",function(a,c,d,e){return{restrict:"ECA",terminal:!0,compile:function(g,h){var f=h.ngInclude||h.src,j=h.onload||"",i=h.autoscroll;return function(g,h){var l=0,n,o=function(){n&&(n.$destroy(),n=null);h.html("")};g.$watch(f,function(f){var s=++l;f?a.get(f,{cache:c}).success(function(a){s===l&&(n&&n.$destroy(),n=g.$new(),h.html(a),e(h.contents())(n),x(i)&&(!i||g.$eval(i))&&d(),n.$emit("$includeContentLoaded"),g.$eval(j))}).error(function(){s===l&&
|
||||
o()}):o()})}}}}],Gd=Q({compile:function(){return{pre:function(a,c,d){a.$eval(d.ngInit)}}}}),Hd=Q({terminal:!0,priority:1E3}),Id=["$locale","$interpolate",function(a,c){var d=/{}/g;return{restrict:"EA",link:function(e,g,h){var f=h.count,j=g.attr(h.$attr.when),i=h.offset||0,k=e.$eval(j),m={},l=c.startSymbol(),t=c.endSymbol();n(k,function(a,e){m[e]=c(a.replace(d,l+f+"-"+i+t))});e.$watch(function(){var c=parseFloat(e.$eval(f));return isNaN(c)?"":(k[c]||(c=a.pluralCat(c-i)),m[c](e,g,!0))},function(a){g.text(a)})}}}],
|
||||
Jd=Q({transclude:"element",priority:1E3,terminal:!0,compile:function(a,c,d){return function(a,c,h){var f=h.ngRepeat,h=f.match(/^\s*(.+)\s+in\s+(.*)\s*$/),j,i,k;if(!h)throw Error("Expected ngRepeat in form of '_item_ in _collection_' but got '"+f+"'.");f=h[1];j=h[2];h=f.match(/^(?:([\$\w]+)|\(([\$\w]+)\s*,\s*([\$\w]+)\))$/);if(!h)throw Error("'item' in 'item in collection' should be identifier or (key, value) but got '"+f+"'.");i=h[3]||h[1];k=h[2];var m=new eb;a.$watch(function(a){var e,f,h=a.$eval(j),
|
||||
n=c,q=new eb,x,z,u,w,r,v;if(B(h))r=h||[];else{r=[];for(u in h)h.hasOwnProperty(u)&&u.charAt(0)!="$"&&r.push(u);r.sort()}x=r.length;e=0;for(f=r.length;e<f;e++){u=h===r?e:r[e];w=h[u];if(v=m.shift(w)){z=v.scope;q.push(w,v);if(e!==v.index)v.index=e,n.after(v.element);n=v.element}else z=a.$new();z[i]=w;k&&(z[k]=u);z.$index=e;z.$first=e===0;z.$last=e===x-1;z.$middle=!(z.$first||z.$last);v||d(z,function(a){n.after(a);v={scope:z,element:n=a,index:e};q.push(w,v)})}for(u in m)if(m.hasOwnProperty(u))for(r=m[u];r.length;)w=
|
||||
r.pop(),w.element.remove(),w.scope.$destroy();m=q})}}}),Kd=Q(function(a,c,d){a.$watch(d.ngShow,function(a){c.css("display",Va(a)?"":"none")})}),Ld=Q(function(a,c,d){a.$watch(d.ngHide,function(a){c.css("display",Va(a)?"none":"")})}),Md=Q(function(a,c,d){a.$watch(d.ngStyle,function(a,d){d&&a!==d&&n(d,function(a,d){c.css(d,"")});a&&c.css(a)},!0)}),Nd=I({restrict:"EA",require:"ngSwitch",controller:["$scope",function(){this.cases={}}],link:function(a,c,d,e){var g,h,f;a.$watch(d.ngSwitch||d.on,function(j){h&&
|
||||
(f.$destroy(),h.remove(),h=f=null);if(g=e.cases["!"+j]||e.cases["?"])a.$eval(d.change),f=a.$new(),g(f,function(a){h=a;c.append(a)})})}}),Od=Q({transclude:"element",priority:500,require:"^ngSwitch",compile:function(a,c,d){return function(a,g,h,f){f.cases["!"+c.ngSwitchWhen]=d}}}),Pd=Q({transclude:"element",priority:500,require:"^ngSwitch",compile:function(a,c,d){return function(a,c,h,f){f.cases["?"]=d}}}),Qd=Q({controller:["$transclude","$element",function(a,c){a(function(a){c.append(a)})}]}),Rd=["$http",
|
||||
"$templateCache","$route","$anchorScroll","$compile","$controller",function(a,c,d,e,g,h){return{restrict:"ECA",terminal:!0,link:function(a,c,i){function k(){var i=d.current&&d.current.locals,k=i&&i.$template;if(k){c.html(k);m&&(m.$destroy(),m=null);var k=g(c.contents()),n=d.current;m=n.scope=a.$new();if(n.controller)i.$scope=m,i=h(n.controller,i),c.children().data("$ngControllerController",i);k(m);m.$emit("$viewContentLoaded");m.$eval(l);e()}else c.html(""),m&&(m.$destroy(),m=null)}var m,l=i.onload||
|
||||
"";a.$on("$routeChangeSuccess",k);k()}}}],Sd=["$templateCache",function(a){return{restrict:"E",terminal:!0,compile:function(c,d){d.type=="text/ng-template"&&a.put(d.id,c[0].text)}}}],Td=I({terminal:!0}),Ud=["$compile","$parse",function(a,c){var d=/^\s*(.*?)(?:\s+as\s+(.*?))?(?:\s+group\s+by\s+(.*))?\s+for\s+(?:([\$\w][\$\w\d]*)|(?:\(\s*([\$\w][\$\w\d]*)\s*,\s*([\$\w][\$\w\d]*)\s*\)))\s+in\s+(.*)$/,e={$setViewValue:C};return{restrict:"E",require:["select","?ngModel"],controller:["$element","$scope",
|
||||
"$attrs",function(a,c,d){var j=this,i={},k=e,m;j.databound=d.ngModel;j.init=function(a,c,d){k=a;m=d};j.addOption=function(c){i[c]=!0;k.$viewValue==c&&(a.val(c),m.parent()&&m.remove())};j.removeOption=function(a){this.hasOption(a)&&(delete i[a],k.$viewValue==a&&this.renderUnknownOption(a))};j.renderUnknownOption=function(c){c="? "+fa(c)+" ?";m.val(c);a.prepend(m);a.val(c);m.prop("selected",!0)};j.hasOption=function(a){return i.hasOwnProperty(a)};c.$on("$destroy",function(){j.renderUnknownOption=C})}],
|
||||
link:function(e,h,f,j){function i(a,c,d,e){d.$render=function(){var a=d.$viewValue;e.hasOption(a)?(y.parent()&&y.remove(),c.val(a),a===""&&v.prop("selected",!0)):w(a)&&v?c.val(""):e.renderUnknownOption(a)};c.bind("change",function(){a.$apply(function(){y.parent()&&y.remove();d.$setViewValue(c.val())})})}function k(a,c,d){var e;d.$render=function(){var a=new Ga(d.$viewValue);n(c.find("option"),function(c){c.selected=x(a.get(c.value))})};a.$watch(function(){ga(e,d.$viewValue)||(e=U(d.$viewValue),d.$render())});
|
||||
c.bind("change",function(){a.$apply(function(){var a=[];n(c.find("option"),function(c){c.selected&&a.push(c.value)});d.$setViewValue(a)})})}function m(e,f,g){function h(){var a={"":[]},c=[""],d,i,p,u,v;p=g.$modelValue;u=t(e)||[];var w=l?mb(u):u,x,y,A;y={};v=!1;var B,E;if(o)v=new Ga(p);else if(p===null||s)a[""].push({selected:p===null,id:"",label:""}),v=!0;for(A=0;x=w.length,A<x;A++){y[k]=u[l?y[l]=w[A]:A];d=m(e,y)||"";if(!(i=a[d]))i=a[d]=[],c.push(d);o?d=v.remove(n(e,y))!=q:(d=p===n(e,y),v=v||d);B=
|
||||
j(e,y);B=B===q?"":B;i.push({id:l?w[A]:A,label:B,selected:d})}!o&&!v&&a[""].unshift({id:"?",label:"",selected:!0});y=0;for(w=c.length;y<w;y++){d=c[y];i=a[d];if(r.length<=y)p={element:z.clone().attr("label",d),label:i.label},u=[p],r.push(u),f.append(p.element);else if(u=r[y],p=u[0],p.label!=d)p.element.attr("label",p.label=d);B=null;A=0;for(x=i.length;A<x;A++)if(d=i[A],v=u[A+1]){B=v.element;if(v.label!==d.label)B.text(v.label=d.label);if(v.id!==d.id)B.val(v.id=d.id);if(v.element.selected!==d.selected)B.prop("selected",
|
||||
v.selected=d.selected)}else d.id===""&&s?E=s:(E=C.clone()).val(d.id).attr("selected",d.selected).text(d.label),u.push({element:E,label:d.label,id:d.id,selected:d.selected}),B?B.after(E):p.element.append(E),B=E;for(A++;u.length>A;)u.pop().element.remove()}for(;r.length>y;)r.pop()[0].element.remove()}var i;if(!(i=p.match(d)))throw Error("Expected ngOptions in form of '_select_ (as _label_)? for (_key_,)?_value_ in _collection_' but got '"+p+"'.");var j=c(i[2]||i[1]),k=i[4]||i[6],l=i[5],m=c(i[3]||""),
|
||||
n=c(i[2]?i[1]:k),t=c(i[7]),r=[[{element:f,label:""}]];s&&(a(s)(e),s.removeClass("ng-scope"),s.remove());f.html("");f.bind("change",function(){e.$apply(function(){var a,c=t(e)||[],d={},h,i,j,m,p,s;if(o){i=[];m=0;for(s=r.length;m<s;m++){a=r[m];j=1;for(p=a.length;j<p;j++)if((h=a[j].element)[0].selected)h=h.val(),l&&(d[l]=h),d[k]=c[h],i.push(n(e,d))}}else h=f.val(),h=="?"?i=q:h==""?i=null:(d[k]=c[h],l&&(d[l]=h),i=n(e,d));g.$setViewValue(i)})});g.$render=h;e.$watch(h)}if(j[1]){for(var l=j[0],t=j[1],o=
|
||||
f.multiple,p=f.ngOptions,s=!1,v,C=u(Y.createElement("option")),z=u(Y.createElement("optgroup")),y=C.clone(),j=0,A=h.children(),r=A.length;j<r;j++)if(A[j].value==""){v=s=A.eq(j);break}l.init(t,s,y);if(o&&(f.required||f.ngRequired)){var B=function(a){t.$setValidity("required",!f.required||a&&a.length);return a};t.$parsers.push(B);t.$formatters.unshift(B);f.$observe("required",function(){B(t.$viewValue)})}p?m(e,h,t):o?k(e,h,t):i(e,h,t,l)}}}}],Vd=["$interpolate",function(a){var c={addOption:C,removeOption:C};
|
||||
return{restrict:"E",priority:100,compile:function(d,e){if(w(e.value)){var g=a(d.text(),!0);g||e.$set("value",d.text())}return function(a,d,e){var i=d.parent(),k=i.data("$selectController")||i.parent().data("$selectController");k&&k.databound?d.prop("selected",!1):k=c;g?a.$watch(g,function(a,c){e.$set("value",a);a!==c&&k.removeOption(c);k.addOption(a)}):k.addOption(e.value);d.bind("$destroy",function(){k.removeOption(e.value)})}}}}],Wd=I({restrict:"E",terminal:!0});(ca=X.jQuery)?(u=ca,v(ca.fn,{scope:va.scope,
|
||||
controller:va.controller,injector:va.injector,inheritedData:va.inheritedData}),ab("remove",!0),ab("empty"),ab("html")):u=L;Zb.element=u;(function(a){v(a,{bootstrap:qb,copy:U,extend:v,equals:ga,element:u,forEach:n,injector:rb,noop:C,bind:Ua,toJson:da,fromJson:ob,identity:na,isUndefined:w,isDefined:x,isString:A,isFunction:H,isObject:M,isNumber:Ra,isElement:gc,isArray:B,version:id,isDate:oa,lowercase:y,uppercase:ma,callbacks:{counter:0}});ta=lc(X);try{ta("ngLocale")}catch(c){ta("ngLocale",[]).provider("$locale",
|
||||
Zc)}ta("ng",["ngLocale"],["$provide",function(a){a.provider("$compile",Cb).directive({a:jd,input:cc,textarea:cc,form:kd,script:Sd,select:Ud,style:Wd,option:Vd,ngBind:vd,ngBindHtmlUnsafe:xd,ngBindTemplate:wd,ngClass:yd,ngClassEven:Ad,ngClassOdd:zd,ngCsp:Dd,ngCloak:Bd,ngController:Cd,ngForm:ld,ngHide:Ld,ngInclude:Fd,ngInit:Gd,ngNonBindable:Hd,ngPluralize:Id,ngRepeat:Jd,ngShow:Kd,ngSubmit:Ed,ngStyle:Md,ngSwitch:Nd,ngSwitchWhen:Od,ngSwitchDefault:Pd,ngOptions:Td,ngView:Rd,ngTransclude:Qd,ngModel:qd,ngList:sd,
|
||||
ngChange:rd,required:dc,ngRequired:dc,ngValue:ud}).directive(lb).directive(ec);a.provider({$anchorScroll:uc,$browser:wc,$cacheFactory:xc,$controller:Bc,$document:Cc,$exceptionHandler:Dc,$filter:Qb,$interpolate:Ec,$http:Vc,$httpBackend:Wc,$location:Ic,$log:Jc,$parse:Nc,$route:Qc,$routeParams:Rc,$rootScope:Sc,$q:Oc,$sniffer:Tc,$templateCache:yc,$timeout:$c,$window:Uc})}])})(Zb);u(Y).ready(function(){jc(Y,qb)})})(window,document);angular.element(document).find("head").append('<style type="text/css">@charset "UTF-8";[ng\\:cloak],[ng-cloak],[data-ng-cloak],[x-ng-cloak],.ng-cloak,.x-ng-cloak{display:none;}ng\\:form{display:block;}</style>');
|
||||
(function(M,V,s){'use strict';function gc(){var b=M.angular;M.angular=hc;return b}function o(b,a,c){var d;if(b)if(I(b))for(d in b)d!="prototype"&&d!="length"&&d!="name"&&b.hasOwnProperty(d)&&a.call(c,b[d],d);else if(b.forEach&&b.forEach!==o)b.forEach(a,c);else if(!b||typeof b.length!=="number"?0:typeof b.hasOwnProperty!="function"&&typeof b.constructor!="function"||b instanceof P||ca&&b instanceof ca||Da.call(b)!=="[object Object]"||typeof b.callee==="function")for(d=0;d<b.length;d++)a.call(c,b[d],
|
||||
d);else for(d in b)b.hasOwnProperty(d)&&a.call(c,b[d],d);return b}function rb(b){var a=[],c;for(c in b)b.hasOwnProperty(c)&&a.push(c);return a.sort()}function ic(b,a,c){for(var d=rb(b),e=0;e<d.length;e++)a.call(c,b[d[e]],d[e]);return d}function sb(b){return function(a,c){b(c,a)}}function Ea(){for(var b=Z.length,a;b;){b--;a=Z[b].charCodeAt(0);if(a==57)return Z[b]="A",Z.join("");if(a==90)Z[b]="0";else return Z[b]=String.fromCharCode(a+1),Z.join("")}Z.unshift("0");return Z.join("")}function y(b){o(arguments,
|
||||
function(a){a!==b&&o(a,function(a,d){b[d]=a})});return b}function K(b){return parseInt(b,10)}function Fa(b,a){return y(new (y(function(){},{prototype:b})),a)}function t(){}function pa(b){return b}function Q(b){return function(){return b}}function u(b){return typeof b=="undefined"}function w(b){return typeof b!="undefined"}function L(b){return b!=null&&typeof b=="object"}function x(b){return typeof b=="string"}function Za(b){return typeof b=="number"}function qa(b){return Da.apply(b)=="[object Date]"}
|
||||
function C(b){return Da.apply(b)=="[object Array]"}function I(b){return typeof b=="function"}function ra(b){return b&&b.document&&b.location&&b.alert&&b.setInterval}function S(b){return x(b)?b.replace(/^\s*/,"").replace(/\s*$/,""):b}function jc(b){return b&&(b.nodeName||b.bind&&b.find)}function $a(b,a,c){var d=[];o(b,function(b,f,i){d.push(a.call(c,b,f,i))});return d}function Ga(b,a){if(b.indexOf)return b.indexOf(a);for(var c=0;c<b.length;c++)if(a===b[c])return c;return-1}function sa(b,a){var c=Ga(b,
|
||||
a);c>=0&&b.splice(c,1);return a}function W(b,a){if(ra(b)||b&&b.$evalAsync&&b.$watch)throw Error("Can't copy Window or Scope");if(a){if(b===a)throw Error("Can't copy equivalent objects or arrays");if(C(b))for(var c=a.length=0;c<b.length;c++)a.push(W(b[c]));else for(c in o(a,function(b,c){delete a[c]}),b)a[c]=W(b[c])}else(a=b)&&(C(b)?a=W(b,[]):qa(b)?a=new Date(b.getTime()):L(b)&&(a=W(b,{})));return a}function kc(b,a){var a=a||{},c;for(c in b)b.hasOwnProperty(c)&&c.substr(0,2)!=="$$"&&(a[c]=b[c]);return a}
|
||||
function ja(b,a){if(b===a)return!0;if(b===null||a===null)return!1;if(b!==b&&a!==a)return!0;var c=typeof b,d;if(c==typeof a&&c=="object")if(C(b)){if((c=b.length)==a.length){for(d=0;d<c;d++)if(!ja(b[d],a[d]))return!1;return!0}}else if(qa(b))return qa(a)&&b.getTime()==a.getTime();else{if(b&&b.$evalAsync&&b.$watch||a&&a.$evalAsync&&a.$watch||ra(b)||ra(a))return!1;c={};for(d in b)if(!(d.charAt(0)==="$"||I(b[d]))){if(!ja(b[d],a[d]))return!1;c[d]=!0}for(d in a)if(!c[d]&&d.charAt(0)!=="$"&&a[d]!==s&&!I(a[d]))return!1;
|
||||
return!0}return!1}function ab(b,a){var c=arguments.length>2?ka.call(arguments,2):[];return I(a)&&!(a instanceof RegExp)?c.length?function(){return arguments.length?a.apply(b,c.concat(ka.call(arguments,0))):a.apply(b,c)}:function(){return arguments.length?a.apply(b,arguments):a.call(b)}:a}function lc(b,a){var c=a;/^\$+/.test(b)?c=s:ra(a)?c="$WINDOW":a&&V===a?c="$DOCUMENT":a&&a.$evalAsync&&a.$watch&&(c="$SCOPE");return c}function da(b,a){return JSON.stringify(b,lc,a?" ":null)}function tb(b){return x(b)?
|
||||
JSON.parse(b):b}function Ha(b){b&&b.length!==0?(b=J(""+b),b=!(b=="f"||b=="0"||b=="false"||b=="no"||b=="n"||b=="[]")):b=!1;return b}function ta(b){b=v(b).clone();try{b.html("")}catch(a){}var c=v("<div>").append(b).html();try{return b[0].nodeType===3?J(c):c.match(/^(<[^>]+>)/)[1].replace(/^<([\w\-]+)/,function(a,b){return"<"+J(b)})}catch(d){return J(c)}}function bb(b){var a={},c,d;o((b||"").split("&"),function(b){b&&(c=b.split("="),d=decodeURIComponent(c[0]),a[d]=w(c[1])?decodeURIComponent(c[1]):!0)});
|
||||
return a}function ub(b){var a=[];o(b,function(b,d){a.push(ua(d,!0)+(b===!0?"":"="+ua(b,!0)))});return a.length?a.join("&"):""}function cb(b){return ua(b,!0).replace(/%26/gi,"&").replace(/%3D/gi,"=").replace(/%2B/gi,"+")}function ua(b,a){return encodeURIComponent(b).replace(/%40/gi,"@").replace(/%3A/gi,":").replace(/%24/g,"$").replace(/%2C/gi,",").replace(/%20/g,a?"%20":"+")}function mc(b,a){function c(a){a&&d.push(a)}var d=[b],e,f,i=["ng:app","ng-app","x-ng-app","data-ng-app"],h=/\sng[:\-]app(:\s*([\w\d_]+);?)?\s/;
|
||||
o(i,function(a){i[a]=!0;c(V.getElementById(a));a=a.replace(":","\\:");b.querySelectorAll&&(o(b.querySelectorAll("."+a),c),o(b.querySelectorAll("."+a+"\\:"),c),o(b.querySelectorAll("["+a+"]"),c))});o(d,function(a){if(!e){var b=h.exec(" "+a.className+" ");b?(e=a,f=(b[2]||"").replace(/\s+/g,",")):o(a.attributes,function(b){if(!e&&i[b.name])e=a,f=b.value})}});e&&a(e,f?[f]:[])}function vb(b,a){var c=function(){b=v(b);a=a||[];a.unshift(["$provide",function(a){a.value("$rootElement",b)}]);a.unshift("ng");
|
||||
var c=wb(a);c.invoke(["$rootScope","$rootElement","$compile","$injector",function(a,b,c,d){a.$apply(function(){b.data("$injector",d);c(b)(a)})}]);return c},d=/^NG_DEFER_BOOTSTRAP!/;if(M&&!d.test(M.name))return c();M.name=M.name.replace(d,"");Ia.resumeBootstrap=function(b){o(b,function(b){a.push(b)});c()}}function db(b,a){a=a||"_";return b.replace(nc,function(b,d){return(d?a:"")+b.toLowerCase()})}function eb(b,a,c){if(!b)throw Error("Argument '"+(a||"?")+"' is "+(c||"required"));return b}function va(b,
|
||||
a,c){c&&C(b)&&(b=b[b.length-1]);eb(I(b),a,"not a function, got "+(b&&typeof b=="object"?b.constructor.name||"Object":typeof b));return b}function oc(b){function a(a,b,e){return a[b]||(a[b]=e())}return a(a(b,"angular",Object),"module",function(){var b={};return function(d,e,f){e&&b.hasOwnProperty(d)&&(b[d]=null);return a(b,d,function(){function a(c,d,e){return function(){b[e||"push"]([c,d,arguments]);return m}}if(!e)throw Error("No module: "+d);var b=[],c=[],g=a("$injector","invoke"),m={_invokeQueue:b,
|
||||
_runBlocks:c,requires:e,name:d,provider:a("$provide","provider"),factory:a("$provide","factory"),service:a("$provide","service"),value:a("$provide","value"),constant:a("$provide","constant","unshift"),animation:a("$animationProvider","register"),filter:a("$filterProvider","register"),controller:a("$controllerProvider","register"),directive:a("$compileProvider","directive"),config:g,run:function(a){c.push(a);return this}};f&&g(f);return m})}})}function Ja(b){return b.replace(pc,function(a,b,d,e){return e?
|
||||
d.toUpperCase():d}).replace(qc,"Moz$1")}function fb(b,a){function c(){var e;for(var b=[this],c=a,i,h,j,g,m,k;b.length;){i=b.shift();h=0;for(j=i.length;h<j;h++){g=v(i[h]);c?g.triggerHandler("$destroy"):c=!c;m=0;for(e=(k=g.children()).length,g=e;m<g;m++)b.push(ca(k[m]))}}return d.apply(this,arguments)}var d=ca.fn[b],d=d.$original||d;c.$original=d;ca.fn[b]=c}function P(b){if(b instanceof P)return b;if(!(this instanceof P)){if(x(b)&&b.charAt(0)!="<")throw Error("selectors not implemented");return new P(b)}if(x(b)){var a=
|
||||
V.createElement("div");a.innerHTML="<div> </div>"+b;a.removeChild(a.firstChild);gb(this,a.childNodes);this.remove()}else gb(this,b)}function hb(b){return b.cloneNode(!0)}function wa(b){xb(b);for(var a=0,b=b.childNodes||[];a<b.length;a++)wa(b[a])}function yb(b,a,c){var d=$(b,"events");$(b,"handle")&&(u(a)?o(d,function(a,c){ib(b,c,a);delete d[c]}):u(c)?(ib(b,a,d[a]),delete d[a]):sa(d[a],c))}function xb(b){var a=b[Ka],c=La[a];c&&(c.handle&&(c.events.$destroy&&c.handle({},"$destroy"),yb(b)),delete La[a],
|
||||
b[Ka]=s)}function $(b,a,c){var d=b[Ka],d=La[d||-1];if(w(c))d||(b[Ka]=d=++rc,d=La[d]={}),d[a]=c;else return d&&d[a]}function zb(b,a,c){var d=$(b,"data"),e=w(c),f=!e&&w(a),i=f&&!L(a);!d&&!i&&$(b,"data",d={});if(e)d[a]=c;else if(f)if(i)return d&&d[a];else y(d,a);else return d}function Ma(b,a){return(" "+b.className+" ").replace(/[\n\t]/g," ").indexOf(" "+a+" ")>-1}function Ab(b,a){a&&o(a.split(" "),function(a){b.className=S((" "+b.className+" ").replace(/[\n\t]/g," ").replace(" "+S(a)+" "," "))})}function Bb(b,
|
||||
a){a&&o(a.split(" "),function(a){if(!Ma(b,a))b.className=S(b.className+" "+S(a))})}function gb(b,a){if(a)for(var a=!a.nodeName&&w(a.length)&&!ra(a)?a:[a],c=0;c<a.length;c++)b.push(a[c])}function Cb(b,a){return Na(b,"$"+(a||"ngController")+"Controller")}function Na(b,a,c){b=v(b);for(b[0].nodeType==9&&(b=b.find("html"));b.length;){if(c=b.data(a))return c;b=b.parent()}}function Db(b,a){var c=Oa[a.toLowerCase()];return c&&Eb[b.nodeName]&&c}function sc(b,a){var c=function(c,e){if(!c.preventDefault)c.preventDefault=
|
||||
function(){c.returnValue=!1};if(!c.stopPropagation)c.stopPropagation=function(){c.cancelBubble=!0};if(!c.target)c.target=c.srcElement||V;if(u(c.defaultPrevented)){var f=c.preventDefault;c.preventDefault=function(){c.defaultPrevented=!0;f.call(c)};c.defaultPrevented=!1}c.isDefaultPrevented=function(){return c.defaultPrevented};o(a[e||c.type],function(a){a.call(b,c)});X<=8?(c.preventDefault=null,c.stopPropagation=null,c.isDefaultPrevented=null):(delete c.preventDefault,delete c.stopPropagation,delete c.isDefaultPrevented)};
|
||||
c.elem=b;return c}function la(b){var a=typeof b,c;if(a=="object"&&b!==null)if(typeof(c=b.$$hashKey)=="function")c=b.$$hashKey();else{if(c===s)c=b.$$hashKey=Ea()}else c=b;return a+":"+c}function Pa(b){o(b,this.put,this)}function Fb(b){var a,c;if(typeof b=="function"){if(!(a=b.$inject))a=[],c=b.toString().replace(tc,""),c=c.match(uc),o(c[1].split(vc),function(b){b.replace(wc,function(b,c,d){a.push(d)})}),b.$inject=a}else C(b)?(c=b.length-1,va(b[c],"fn"),a=b.slice(0,c)):va(b,"fn",!0);return a}function wb(b){function a(a){return function(b,
|
||||
c){if(L(b))o(b,sb(a));else return a(b,c)}}function c(a,b){if(I(b)||C(b))b=k.instantiate(b);if(!b.$get)throw Error("Provider "+a+" must define $get factory method.");return m[a+h]=b}function d(a,b){return c(a,{$get:b})}function e(a){var b=[];o(a,function(a){if(!g.get(a))if(g.put(a,!0),x(a)){var c=xa(a);b=b.concat(e(c.requires)).concat(c._runBlocks);try{for(var d=c._invokeQueue,c=0,h=d.length;c<h;c++){var f=d[c],n=k.get(f[0]);n[f[1]].apply(n,f[2])}}catch(j){throw j.message&&(j.message+=" from "+a),
|
||||
j;}}else if(I(a))try{b.push(k.invoke(a))}catch(i){throw i.message&&(i.message+=" from "+a),i;}else if(C(a))try{b.push(k.invoke(a))}catch(l){throw l.message&&(l.message+=" from "+String(a[a.length-1])),l;}else va(a,"module")});return b}function f(a,b){function c(d){if(typeof d!=="string")throw Error("Service name expected");if(a.hasOwnProperty(d)){if(a[d]===i)throw Error("Circular dependency: "+j.join(" <- "));return a[d]}else try{return j.unshift(d),a[d]=i,a[d]=b(d)}finally{j.shift()}}function d(a,
|
||||
b,e){var g=[],h=Fb(a),f,j,n;j=0;for(f=h.length;j<f;j++)n=h[j],g.push(e&&e.hasOwnProperty(n)?e[n]:c(n));a.$inject||(a=a[f]);switch(b?-1:g.length){case 0:return a();case 1:return a(g[0]);case 2:return a(g[0],g[1]);case 3:return a(g[0],g[1],g[2]);case 4:return a(g[0],g[1],g[2],g[3]);case 5:return a(g[0],g[1],g[2],g[3],g[4]);case 6:return a(g[0],g[1],g[2],g[3],g[4],g[5]);case 7:return a(g[0],g[1],g[2],g[3],g[4],g[5],g[6]);case 8:return a(g[0],g[1],g[2],g[3],g[4],g[5],g[6],g[7]);case 9:return a(g[0],g[1],
|
||||
g[2],g[3],g[4],g[5],g[6],g[7],g[8]);case 10:return a(g[0],g[1],g[2],g[3],g[4],g[5],g[6],g[7],g[8],g[9]);default:return a.apply(b,g)}}return{invoke:d,instantiate:function(a,b){var c=function(){},e;c.prototype=(C(a)?a[a.length-1]:a).prototype;c=new c;e=d(a,c,b);return L(e)?e:c},get:c,annotate:Fb}}var i={},h="Provider",j=[],g=new Pa,m={$provide:{provider:a(c),factory:a(d),service:a(function(a,b){return d(a,["$injector",function(a){return a.instantiate(b)}])}),value:a(function(a,b){return d(a,Q(b))}),
|
||||
constant:a(function(a,b){m[a]=b;l[a]=b}),decorator:function(a,b){var c=k.get(a+h),d=c.$get;c.$get=function(){var a=q.invoke(d,c);return q.invoke(b,null,{$delegate:a})}}}},k=m.$injector=f(m,function(){throw Error("Unknown provider: "+j.join(" <- "));}),l={},q=l.$injector=f(l,function(a){a=k.get(a+h);return q.invoke(a.$get,a)});o(e(b),function(a){q.invoke(a||t)});return q}function xc(){var b=!0;this.disableAutoScrolling=function(){b=!1};this.$get=["$window","$location","$rootScope",function(a,c,d){function e(a){var b=
|
||||
null;o(a,function(a){!b&&J(a.nodeName)==="a"&&(b=a)});return b}function f(){var b=c.hash(),d;b?(d=i.getElementById(b))?d.scrollIntoView():(d=e(i.getElementsByName(b)))?d.scrollIntoView():b==="top"&&a.scrollTo(0,0):a.scrollTo(0,0)}var i=a.document;b&&d.$watch(function(){return c.hash()},function(){d.$evalAsync(f)});return f}]}function Gb(b){this.register=function(a,c){b.factory(Ja(a)+"Animation",c)};this.$get=["$injector",function(a){return function(b){if(b)try{return a.get(Ja(b)+"Animation")}catch(d){}}}]}
|
||||
function yc(b,a,c,d){function e(a){try{a.apply(null,ka.call(arguments,1))}finally{if(n--,n===0)for(;B.length;)try{B.pop()()}catch(b){c.error(b)}}}function f(a,b){(function z(){o(r,function(a){a()});p=b(z,a)})()}function i(){E!=h.url()&&(E=h.url(),o(G,function(a){a(h.url())}))}var h=this,j=a[0],g=b.location,m=b.history,k=b.setTimeout,l=b.clearTimeout,q={};h.isMock=!1;var n=0,B=[];h.$$completeOutstandingRequest=e;h.$$incOutstandingRequestCount=function(){n++};h.notifyWhenNoOutstandingRequests=function(a){o(r,
|
||||
function(a){a()});n===0?a():B.push(a)};var r=[],p;h.addPollFn=function(a){u(p)&&f(100,k);r.push(a);return a};var E=g.href,D=a.find("base");h.url=function(a,b){if(a){if(E!=a)return E=a,d.history?b?m.replaceState(null,"",a):(m.pushState(null,"",a),D.attr("href",D.attr("href"))):b?g.replace(a):g.href=a,h}else return g.href.replace(/%27/g,"'")};var G=[],R=!1;h.onUrlChange=function(a){R||(d.history&&v(b).bind("popstate",i),d.hashchange?v(b).bind("hashchange",i):h.addPollFn(i),R=!0);G.push(a);return a};
|
||||
h.baseHref=function(){var a=D.attr("href");return a?a.replace(/^https?\:\/\/[^\/]*/,""):""};var A={},H="",F=h.baseHref();h.cookies=function(a,b){var d,e,g,h;if(a)if(b===s)j.cookie=escape(a)+"=;path="+F+";expires=Thu, 01 Jan 1970 00:00:00 GMT";else{if(x(b))d=(j.cookie=escape(a)+"="+escape(b)+";path="+F).length+1,d>4096&&c.warn("Cookie '"+a+"' possibly not set or overflowed because it was too large ("+d+" > 4096 bytes)!")}else{if(j.cookie!==H){H=j.cookie;d=H.split("; ");A={};for(g=0;g<d.length;g++)e=
|
||||
d[g],h=e.indexOf("="),h>0&&(A[unescape(e.substring(0,h))]=unescape(e.substring(h+1)))}return A}};h.defer=function(a,b){var c;n++;c=k(function(){delete q[c];e(a)},b||0);q[c]=!0;return c};h.defer.cancel=function(a){return q[a]?(delete q[a],l(a),e(t),!0):!1}}function zc(){this.$get=["$window","$log","$sniffer","$document",function(b,a,c,d){return new yc(b,d,a,c)}]}function Ac(){this.$get=function(){function b(b,d){function e(a){if(a!=k){if(l){if(l==a)l=a.n}else l=a;f(a.n,a.p);f(a,k);k=a;k.n=null}}function f(a,
|
||||
b){if(a!=b){if(a)a.p=b;if(b)b.n=a}}if(b in a)throw Error("cacheId "+b+" taken");var i=0,h=y({},d,{id:b}),j={},g=d&&d.capacity||Number.MAX_VALUE,m={},k=null,l=null;return a[b]={put:function(a,b){var c=m[a]||(m[a]={key:a});e(c);if(!u(b))return a in j||i++,j[a]=b,i>g&&this.remove(l.key),b},get:function(a){var b=m[a];if(b)return e(b),j[a]},remove:function(a){var b=m[a];if(b){if(b==k)k=b.p;if(b==l)l=b.n;f(b.n,b.p);delete m[a];delete j[a];i--}},removeAll:function(){j={};i=0;m={};k=l=null},destroy:function(){m=
|
||||
h=j=null;delete a[b]},info:function(){return y({},h,{size:i})}}}var a={};b.info=function(){var b={};o(a,function(a,e){b[e]=a.info()});return b};b.get=function(b){return a[b]};return b}}function Bc(){this.$get=["$cacheFactory",function(b){return b("templates")}]}function Hb(b){var a={},c="Directive",d=/^\s*directive\:\s*([\d\w\-_]+)\s+(.*)$/,e=/(([\d\w\-_]+)(?:\:([^;]+))?;?)/,f="Template must have exactly one root element. was: ",i=/^\s*(https?|ftp|mailto|file):/;this.directive=function j(d,e){x(d)?
|
||||
(eb(e,"directive"),a.hasOwnProperty(d)||(a[d]=[],b.factory(d+c,["$injector","$exceptionHandler",function(b,c){var e=[];o(a[d],function(a){try{var f=b.invoke(a);if(I(f))f={compile:Q(f)};else if(!f.compile&&f.link)f.compile=Q(f.link);f.priority=f.priority||0;f.name=f.name||d;f.require=f.require||f.controller&&f.name;f.restrict=f.restrict||"A";e.push(f)}catch(j){c(j)}});return e}])),a[d].push(e)):o(d,sb(j));return this};this.urlSanitizationWhitelist=function(a){return w(a)?(i=a,this):i};this.$get=["$injector",
|
||||
"$interpolate","$exceptionHandler","$http","$templateCache","$parse","$controller","$rootScope","$document",function(b,g,m,k,l,q,n,B,r){function p(a,b,c){a instanceof v||(a=v(a));o(a,function(b,c){b.nodeType==3&&b.nodeValue.match(/\S+/)&&(a[c]=v(b).wrap("<span></span>").parent()[0])});var d=D(a,b,a,c);return function(b,c){eb(b,"scope");for(var e=c?za.clone.call(a):a,g=0,f=e.length;g<f;g++){var j=e[g];(j.nodeType==1||j.nodeType==9)&&e.eq(g).data("$scope",b)}E(e,"ng-scope");c&&c(e,b);d&&d(b,e,e);return e}}
|
||||
function E(a,b){try{a.addClass(b)}catch(c){}}function D(a,b,c,d){function e(a,c,d,f){var j,i,l,m,n,k,q,p=[];n=0;for(k=c.length;n<k;n++)p.push(c[n]);q=n=0;for(k=g.length;n<k;q++)i=p[q],c=g[n++],j=g[n++],c?(c.scope?(l=a.$new(L(c.scope)),v(i).data("$scope",l)):l=a,(m=c.transclude)||!f&&b?c(j,l,i,d,function(b){return function(c){var d=a.$new();d.$$transcluded=!0;return b(d,c).bind("$destroy",ab(d,d.$destroy))}}(m||b)):c(j,l,i,s,f)):j&&j(a,i.childNodes,s,f)}for(var g=[],f,j,i,l=0;l<a.length;l++)j=new ya,
|
||||
f=G(a[l],[],j,d),j=(f=f.length?R(f,a[l],j,b,c):null)&&f.terminal||!a[l].childNodes||!a[l].childNodes.length?null:D(a[l].childNodes,f?f.transclude:b),g.push(f),g.push(j),i=i||f||j;return i?e:null}function G(a,b,c,g){var f=c.$attr,j;switch(a.nodeType){case 1:A(b,aa(jb(a).toLowerCase()),"E",g);var i,l,n;j=a.attributes;for(var m=0,k=j&&j.length;m<k;m++)if(i=j[m],i.specified)l=i.name,n=aa(l),ha.test(n)&&(l=n.substr(6).toLowerCase()),n=aa(l.toLowerCase()),f[n]=l,c[n]=i=S(X&&l=="href"?decodeURIComponent(a.getAttribute(l,
|
||||
2)):i.value),Db(a,n)&&(c[n]=!0),z(a,b,i,n),A(b,n,"A",g);a=a.className;if(x(a)&&a!=="")for(;j=e.exec(a);)n=aa(j[2]),A(b,n,"C",g)&&(c[n]=S(j[3])),a=a.substr(j.index+j[0].length);break;case 3:ga(b,a.nodeValue);break;case 8:try{if(j=d.exec(a.nodeValue))n=aa(j[1]),A(b,n,"M",g)&&(c[n]=S(j[2]))}catch(q){}}b.sort(N);return b}function R(a,b,c,d,e){function j(a,b){if(a)a.require=z.require,B.push(a);if(b)b.require=z.require,r.push(b)}function i(a,b){var c,d="data",e=!1;if(x(a)){for(;(c=a.charAt(0))=="^"||c==
|
||||
"?";)a=a.substr(1),c=="^"&&(d="inheritedData"),e=e||c=="?";c=b[d]("$"+a+"Controller");if(!c&&!e)throw Error("No controller: "+a);}else C(a)&&(c=[],o(a,function(a){c.push(i(a,b))}));return c}function l(a,d,e,f,j){var k,p,D,G,H;k=b===e?c:kc(c,new ya(v(e),c.$attr));p=k.$$element;if(ba){var z=/^\s*([@=&])(\??)\s*(\w*)\s*$/,F=d.$parent||d;o(ba.scope,function(a,b){var c=a.match(z)||[],e=c[3]||b,f=c[2]=="?",c=c[1],j,l,i;d.$$isolateBindings[b]=c+e;switch(c){case "@":k.$observe(e,function(a){d[b]=a});k.$$observers[e].$$scope=
|
||||
F;k[e]&&(d[b]=g(k[e])(F));break;case "=":if(f&&!k[e])break;l=q(k[e]);i=l.assign||function(){j=d[b]=l(F);throw Error(Ib+k[e]+" (directive: "+ba.name+")");};j=d[b]=l(F);d.$watch(function(){var a=l(F);a!==d[b]&&(a!==j?j=d[b]=a:i(F,a=j=d[b]));return a});break;case "&":l=q(k[e]);d[b]=function(a){return l(F,a)};break;default:throw Error("Invalid isolate scope definition for directive "+ba.name+": "+a);}})}ha&&o(ha,function(a){var b={$scope:d,$element:p,$attrs:k,$transclude:j};H=a.controller;H=="@"&&(H=
|
||||
k[a.name]);p.data("$"+a.name+"Controller",n(H,b))});f=0;for(D=B.length;f<D;f++)try{G=B[f],G(d,p,k,G.require&&i(G.require,p))}catch(E){m(E,ta(p))}a&&a(d,e.childNodes,s,j);f=0;for(D=r.length;f<D;f++)try{G=r[f],G(d,p,k,G.require&&i(G.require,p))}catch(N){m(N,ta(p))}}for(var k=-Number.MAX_VALUE,B=[],r=[],D=null,ba=null,N=null,A=c.$$element=v(b),z,T,R,ga,ia=d,ha,t,y,w=0,u=a.length;w<u;w++){z=a[w];R=s;if(k>z.priority)break;if(y=z.scope)ea("isolated scope",ba,z,A),L(y)&&(E(A,"ng-isolate-scope"),ba=z),E(A,
|
||||
"ng-scope"),D=D||z;T=z.name;if(y=z.controller)ha=ha||{},ea("'"+T+"' controller",ha[T],z,A),ha[T]=z;if(y=z.transclude)ea("transclusion",ga,z,A),ga=z,k=z.priority,y=="element"?(R=v(b),A=c.$$element=v(V.createComment(" "+T+": "+c[T]+" ")),b=A[0],fa(e,v(R[0]),b),ia=p(R,d,k)):(R=v(hb(b)).contents(),A.html(""),ia=p(R,d));if(z.template)if(ea("template",N,z,A),N=z,y=I(z.template)?z.template(A,c):z.template,y=Jb(y),z.replace){R=v("<div>"+S(y)+"</div>").contents();b=R[0];if(R.length!=1||b.nodeType!==1)throw Error(f+
|
||||
y);fa(e,A,b);T={$attr:{}};a=a.concat(G(b,a.splice(w+1,a.length-(w+1)),T));H(c,T);u=a.length}else A.html(y);if(z.templateUrl)ea("template",N,z,A),N=z,l=F(a.splice(w,a.length-w),l,A,c,e,z.replace,ia),u=a.length;else if(z.compile)try{t=z.compile(A,c,ia),I(t)?j(null,t):t&&j(t.pre,t.post)}catch(J){m(J,ta(A))}if(z.terminal)l.terminal=!0,k=Math.max(k,z.priority)}l.scope=D&&D.scope;l.transclude=ga&&ia;return l}function A(d,e,g,f){var l=!1;if(a.hasOwnProperty(e))for(var i,e=b.get(e+c),n=0,k=e.length;n<k;n++)try{if(i=
|
||||
e[n],(f===s||f>i.priority)&&i.restrict.indexOf(g)!=-1)d.push(i),l=!0}catch(q){m(q)}return l}function H(a,b){var c=b.$attr,d=a.$attr,e=a.$$element;o(a,function(d,e){e.charAt(0)!="$"&&(b[e]&&(d+=(e==="style"?";":" ")+b[e]),a.$set(e,d,!0,c[e]))});o(b,function(b,g){g=="class"?(E(e,b),a["class"]=(a["class"]?a["class"]+" ":"")+b):g=="style"?e.attr("style",e.attr("style")+";"+b):g.charAt(0)!="$"&&!a.hasOwnProperty(g)&&(a[g]=b,d[g]=c[g])})}function F(a,b,c,d,e,g,j){var i=[],n,m,q=c[0],p=a.shift(),ya=y({},
|
||||
p,{controller:null,templateUrl:null,transclude:null,scope:null}),p=I(p.templateUrl)?p.templateUrl(c,d):p.templateUrl;c.html("");k.get(p,{cache:l}).success(function(l){var k,p,l=Jb(l);if(g){p=v("<div>"+S(l)+"</div>").contents();k=p[0];if(p.length!=1||k.nodeType!==1)throw Error(f+l);l={$attr:{}};fa(e,c,k);G(k,a,l);H(d,l)}else k=q,c.html(l);a.unshift(ya);n=R(a,k,d,j);for(m=D(c[0].childNodes,j);i.length;){var B=i.shift(),l=i.shift();p=i.shift();var r=i.shift(),F=k;l!==q&&(F=hb(k),fa(p,v(l),F));n(function(){b(m,
|
||||
B,F,e,r)},B,F,e,r)}i=null}).error(function(a,b,c,d){throw Error("Failed to load template: "+d.url);});return function(a,c,d,e,g){i?(i.push(c),i.push(d),i.push(e),i.push(g)):n(function(){b(m,c,d,e,g)},c,d,e,g)}}function N(a,b){return b.priority-a.priority}function ea(a,b,c,d){if(b)throw Error("Multiple directives ["+b.name+", "+c.name+"] asking for "+a+" on: "+ta(d));}function ga(a,b){var c=g(b,!0);c&&a.push({priority:0,compile:Q(function(a,b){var d=b.parent(),e=d.data("$binding")||[];e.push(c);E(d.data("$binding",
|
||||
e),"ng-binding");a.$watch(c,function(a){b[0].nodeValue=a})})})}function z(a,b,c,d){var e=g(c,!0);e&&b.push({priority:100,compile:Q(function(a,b,c){b=c.$$observers||(c.$$observers={});if(e=g(c[d],!0))c[d]=e(a),(b[d]||(b[d]=[])).$$inter=!0,(c.$$observers&&c.$$observers[d].$$scope||a).$watch(e,function(a){c.$set(d,a)})})})}function fa(a,b,c){var d=b[0],e=d.parentNode,g,f;if(a){g=0;for(f=a.length;g<f;g++)if(a[g]==d){a[g]=c;break}}e&&e.replaceChild(c,d);c[v.expando]=d[v.expando];b[0]=c}var ya=function(a,
|
||||
b){this.$$element=a;this.$attr=b||{}};ya.prototype={$normalize:aa,$set:function(a,b,c,d){var e=Db(this.$$element[0],a),g=this.$$observers;e&&(this.$$element.prop(a,b),d=e);this[a]=b;d?this.$attr[a]=d:(d=this.$attr[a])||(this.$attr[a]=d=db(a,"-"));if(jb(this.$$element[0])==="A"&&a==="href")ba.setAttribute("href",b),e=ba.href,e.match(i)||(this[a]=b="unsafe:"+e);c!==!1&&(b===null||b===s?this.$$element.removeAttr(d):this.$$element.attr(d,b));g&&o(g[a],function(a){try{a(b)}catch(c){m(c)}})},$observe:function(a,
|
||||
b){var c=this,d=c.$$observers||(c.$$observers={}),e=d[a]||(d[a]=[]);e.push(b);B.$evalAsync(function(){e.$$inter||b(c[a])});return b}};var ba=r[0].createElement("a"),T=g.startSymbol(),ia=g.endSymbol(),Jb=T=="{{"||ia=="}}"?pa:function(a){return a.replace(/\{\{/g,T).replace(/}}/g,ia)},ha=/^ngAttr[A-Z]/;return p}]}function aa(b){return Ja(b.replace(Cc,""))}function Dc(){var b={};this.register=function(a,c){L(a)?y(b,a):b[a]=c};this.$get=["$injector","$window",function(a,c){return function(d,e){if(x(d)){var f=
|
||||
d,d=b.hasOwnProperty(f)?b[f]:kb(e.$scope,f,!0)||kb(c,f,!0);va(d,f,!0)}return a.instantiate(d,e)}}]}function Ec(){this.$get=["$window",function(b){return v(b.document)}]}function Fc(){this.$get=["$log",function(b){return function(a,c){b.error.apply(b,arguments)}}]}function Gc(){var b="{{",a="}}";this.startSymbol=function(a){return a?(b=a,this):b};this.endSymbol=function(b){return b?(a=b,this):a};this.$get=["$parse","$exceptionHandler",function(c,d){function e(e,j){for(var g,m,k=0,l=[],q=e.length,n=
|
||||
!1,B=[];k<q;)(g=e.indexOf(b,k))!=-1&&(m=e.indexOf(a,g+f))!=-1?(k!=g&&l.push(e.substring(k,g)),l.push(k=c(n=e.substring(g+f,m))),k.exp=n,k=m+i,n=!0):(k!=q&&l.push(e.substring(k)),k=q);if(!(q=l.length))l.push(""),q=1;if(!j||n)return B.length=q,k=function(a){try{for(var b=0,c=q,g;b<c;b++){if(typeof(g=l[b])=="function")g=g(a),g==null||g==s?g="":typeof g!="string"&&(g=da(g));B[b]=g}return B.join("")}catch(f){d(Error("Error while interpolating: "+e+"\n"+f.toString()))}},k.exp=e,k.parts=l,k}var f=b.length,
|
||||
i=a.length;e.startSymbol=function(){return b};e.endSymbol=function(){return a};return e}]}function Kb(b){for(var b=b.split("/"),a=b.length;a--;)b[a]=cb(b[a]);return b.join("/")}function Aa(b,a){var c=lb.exec(b),c={protocol:c[1],host:c[3],port:K(c[5])||Ba[c[1]]||null,path:c[6]||"/",search:c[8],hash:c[10]};if(a)a.$$protocol=c.protocol,a.$$host=c.host,a.$$port=c.port;return c}function ma(b,a,c){return b+"://"+a+(c==Ba[b]?"":":"+c)}function Hc(b,a,c){var d=Aa(b);return decodeURIComponent(d.path)!=a||
|
||||
u(d.hash)||d.hash.indexOf(c)!==0?b:ma(d.protocol,d.host,d.port)+a.substr(0,a.lastIndexOf("/"))+d.hash.substr(c.length)}function Ic(b,a,c){var d=Aa(b);if(decodeURIComponent(d.path)==a&&!u(d.hash)&&d.hash.indexOf(c)===0)return b;else{var e=d.search&&"?"+d.search||"",f=d.hash&&"#"+d.hash||"",i=a.substr(0,a.lastIndexOf("/")),h=d.path.substr(i.length);if(d.path.indexOf(i)!==0)throw Error('Invalid url "'+b+'", missing path prefix "'+i+'" !');return ma(d.protocol,d.host,d.port)+a+"#"+c+h+e+f}}function mb(b,
|
||||
a,c){a=a||"";this.$$parse=function(b){var c=Aa(b,this);if(c.path.indexOf(a)!==0)throw Error('Invalid url "'+b+'", missing path prefix "'+a+'" !');this.$$path=decodeURIComponent(c.path.substr(a.length));this.$$search=bb(c.search);this.$$hash=c.hash&&decodeURIComponent(c.hash)||"";this.$$compose()};this.$$compose=function(){var b=ub(this.$$search),c=this.$$hash?"#"+cb(this.$$hash):"";this.$$url=Kb(this.$$path)+(b?"?"+b:"")+c;this.$$absUrl=ma(this.$$protocol,this.$$host,this.$$port)+a+this.$$url};this.$$rewriteAppUrl=
|
||||
function(a){if(a.indexOf(c)==0)return a};this.$$parse(b)}function Qa(b,a,c){var d;this.$$parse=function(b){var c=Aa(b,this);if(c.hash&&c.hash.indexOf(a)!==0)throw Error('Invalid url "'+b+'", missing hash prefix "'+a+'" !');d=c.path+(c.search?"?"+c.search:"");c=Jc.exec((c.hash||"").substr(a.length));this.$$path=c[1]?(c[1].charAt(0)=="/"?"":"/")+decodeURIComponent(c[1]):"";this.$$search=bb(c[3]);this.$$hash=c[5]&&decodeURIComponent(c[5])||"";this.$$compose()};this.$$compose=function(){var b=ub(this.$$search),
|
||||
c=this.$$hash?"#"+cb(this.$$hash):"";this.$$url=Kb(this.$$path)+(b?"?"+b:"")+c;this.$$absUrl=ma(this.$$protocol,this.$$host,this.$$port)+d+(this.$$url?"#"+a+this.$$url:"")};this.$$rewriteAppUrl=function(a){if(a.indexOf(c)==0)return a};this.$$parse(b)}function Lb(b,a,c,d){Qa.apply(this,arguments);this.$$rewriteAppUrl=function(b){if(b.indexOf(c)==0)return c+d+"#"+a+b.substr(c.length)}}function Ra(b){return function(){return this[b]}}function Mb(b,a){return function(c){if(u(c))return this[b];this[b]=
|
||||
a(c);this.$$compose();return this}}function Kc(){var b="",a=!1;this.hashPrefix=function(a){return w(a)?(b=a,this):b};this.html5Mode=function(b){return w(b)?(a=b,this):a};this.$get=["$rootScope","$browser","$sniffer","$rootElement",function(c,d,e,f){function i(a){c.$broadcast("$locationChangeSuccess",h.absUrl(),a)}var h,j,g,m=d.url(),k=Aa(m);a?(j=d.baseHref()||"/",g=j.substr(0,j.lastIndexOf("/")),k=ma(k.protocol,k.host,k.port)+g+"/",h=e.history?new mb(Hc(m,j,b),g,k):new Lb(Ic(m,j,b),b,k,j.substr(g.length+
|
||||
1))):(k=ma(k.protocol,k.host,k.port)+(k.path||"")+(k.search?"?"+k.search:"")+"#"+b+"/",h=new Qa(m,b,k));f.bind("click",function(a){if(!a.ctrlKey&&!(a.metaKey||a.which==2)){for(var b=v(a.target);J(b[0].nodeName)!=="a";)if(b[0]===f[0]||!(b=b.parent())[0])return;var d=b.prop("href"),e=h.$$rewriteAppUrl(d);d&&!b.attr("target")&&e&&(h.$$parse(e),c.$apply(),a.preventDefault(),M.angular["ff-684208-preventDefault"]=!0)}});h.absUrl()!=m&&d.url(h.absUrl(),!0);d.onUrlChange(function(a){h.absUrl()!=a&&(c.$evalAsync(function(){var b=
|
||||
h.absUrl();h.$$parse(a);i(b)}),c.$$phase||c.$digest())});var l=0;c.$watch(function(){var a=d.url(),b=h.$$replace;if(!l||a!=h.absUrl())l++,c.$evalAsync(function(){c.$broadcast("$locationChangeStart",h.absUrl(),a).defaultPrevented?h.$$parse(a):(d.url(h.absUrl(),b),i(a))});h.$$replace=!1;return l});return h}]}function Lc(){var b=!0,a=this;this.debugEnabled=function(a){return w(a)?(b=a,this):b};this.$get=["$window",function(c){function d(a){a instanceof Error&&(a.stack?a=a.message&&a.stack.indexOf(a.message)===
|
||||
-1?"Error: "+a.message+"\n"+a.stack:a.stack:a.sourceURL&&(a=a.message+"\n"+a.sourceURL+":"+a.line));return a}function e(a){var b=c.console||{},e=b[a]||b.log||t;return e.apply?function(){var a=[];o(arguments,function(b){a.push(d(b))});return e.apply(b,a)}:function(a,b){e(a,b)}}return{log:e("log"),warn:e("warn"),info:e("info"),error:e("error"),debug:function(){var c=e("debug");return function(){b&&c.apply(a,arguments)}}()}}]}function Mc(b,a){function c(a){return a.indexOf(r)!=-1}function d(a){a=a||
|
||||
1;return n+a<b.length?b.charAt(n+a):!1}function e(a){return"0"<=a&&a<="9"}function f(a){return a==" "||a=="\r"||a=="\t"||a=="\n"||a=="\u000b"||a=="\u00a0"}function i(a){return"a"<=a&&a<="z"||"A"<=a&&a<="Z"||"_"==a||a=="$"}function h(a){return a=="-"||a=="+"||e(a)}function j(a,c,d){d=d||n;throw Error("Lexer Error: "+a+" at column"+(w(c)?"s "+c+"-"+n+" ["+b.substring(c,d)+"]":" "+d)+" in expression ["+b+"].");}function g(){for(var a="",c=n;n<b.length;){var g=J(b.charAt(n));if(g=="."||e(g))a+=g;else{var f=
|
||||
d();if(g=="e"&&h(f))a+=g;else if(h(g)&&f&&e(f)&&a.charAt(a.length-1)=="e")a+=g;else if(h(g)&&(!f||!e(f))&&a.charAt(a.length-1)=="e")j("Invalid exponent");else break}n++}a*=1;l.push({index:c,text:a,json:!0,fn:function(){return a}})}function m(){for(var c="",d=n,g,h,j;n<b.length;){var k=b.charAt(n);if(k=="."||i(k)||e(k))k=="."&&(g=n),c+=k;else break;n++}if(g)for(h=n;h<b.length;){k=b.charAt(h);if(k=="("){j=c.substr(g-d+1);c=c.substr(0,g-d);n=h;break}if(f(k))h++;else break}d={index:d,text:c};if(Ca.hasOwnProperty(c))d.fn=
|
||||
d.json=Ca[c];else{var m=Nb(c,a);d.fn=y(function(a,b){return m(a,b)},{assign:function(a,b){return Ob(a,c,b)}})}l.push(d);j&&(l.push({index:g,text:".",json:!1}),l.push({index:g+1,text:j,json:!1}))}function k(a){var c=n;n++;for(var d="",e=a,g=!1;n<b.length;){var h=b.charAt(n);e+=h;if(g)h=="u"?(h=b.substring(n+1,n+5),h.match(/[\da-f]{4}/i)||j("Invalid unicode escape [\\u"+h+"]"),n+=4,d+=String.fromCharCode(parseInt(h,16))):(g=Nc[h],d+=g?g:h),g=!1;else if(h=="\\")g=!0;else if(h==a){n++;l.push({index:c,
|
||||
text:e,string:d,json:!0,fn:function(){return d}});return}else d+=h;n++}j("Unterminated quote",c)}for(var l=[],q,n=0,B=[],r,p=":";n<b.length;){r=b.charAt(n);if(c("\"'"))k(r);else if(e(r)||c(".")&&e(d()))g();else if(i(r)){if(m(),"{,".indexOf(p)!=-1&&B[0]=="{"&&(q=l[l.length-1]))q.json=q.text.indexOf(".")==-1}else if(c("(){}[].,;:"))l.push({index:n,text:r,json:":[,".indexOf(p)!=-1&&c("{[")||c("}]:,")}),c("{[")&&B.unshift(r),c("}]")&&B.shift(),n++;else if(f(r)){n++;continue}else{var E=r+d(),D=E+d(2),
|
||||
G=Ca[r],o=Ca[E],A=Ca[D];A?(l.push({index:n,text:D,fn:A}),n+=3):o?(l.push({index:n,text:E,fn:o}),n+=2):G?(l.push({index:n,text:r,fn:G,json:"[,:".indexOf(p)!=-1&&c("+-")}),n+=1):j("Unexpected next character ",n,n+1)}p=r}return l}function Oc(b,a,c,d){function e(a,c){throw Error("Syntax Error: Token '"+c.text+"' "+a+" at column "+(c.index+1)+" of the expression ["+b+"] starting at ["+b.substring(c.index)+"].");}function f(){if(F.length===0)throw Error("Unexpected end of expression: "+b);return F[0]}function i(a,
|
||||
b,c,d){if(F.length>0){var e=F[0],g=e.text;if(g==a||g==b||g==c||g==d||!a&&!b&&!c&&!d)return e}return!1}function h(b,c,d,g){return(b=i(b,c,d,g))?(a&&!b.json&&e("is not valid json",b),F.shift(),b):!1}function j(a){h(a)||e("is unexpected, expecting ["+a+"]",i())}function g(a,b){return y(function(c,d){return a(c,d,b)},{constant:b.constant})}function m(a,b,c){return y(function(d,e){return b(d,e,a,c)},{constant:a.constant&&c.constant})}function k(){for(var a=[];;)if(F.length>0&&!i("}",")",";","]")&&a.push(fa()),
|
||||
!h(";"))return a.length==1?a[0]:function(b,c){for(var d,e=0;e<a.length;e++){var g=a[e];g&&(d=g(b,c))}return d}}function l(){for(var a=h(),b=c(a.text),d=[];;)if(a=h(":"))d.push(N());else{var e=function(a,c,e){for(var e=[e],g=0;g<d.length;g++)e.push(d[g](a,c));return b.apply(a,e)};return function(){return e}}}function q(){for(var a=n(),b;;)if(b=h("||"))a=m(a,b.fn,n());else return a}function n(){var a=B(),b;if(b=h("&&"))a=m(a,b.fn,n());return a}function B(){var a=r(),b;if(b=h("==","!=","===","!=="))a=
|
||||
m(a,b.fn,B());return a}function r(){var a;a=p();for(var b;b=h("+","-");)a=m(a,b.fn,p());if(b=h("<",">","<=",">="))a=m(a,b.fn,r());return a}function p(){for(var a=E(),b;b=h("*","/","%");)a=m(a,b.fn,E());return a}function E(){var a;return h("+")?D():(a=h("-"))?m(A,a.fn,E()):(a=h("!"))?g(a.fn,E()):D()}function D(){var a;if(h("("))a=fa(),j(")");else if(h("["))a=G();else if(h("{"))a=o();else{var b=h();(a=b.fn)||e("not a primary expression",b);if(b.json)a.constant=a.literal=!0}for(var c;b=h("(","[",".");)b.text===
|
||||
"("?(a=ea(a,c),c=null):b.text==="["?(c=a,a=z(a)):b.text==="."?(c=a,a=ga(a)):e("IMPOSSIBLE");return a}function G(){var a=[],b=!0;if(f().text!="]"){do{var c=N();a.push(c);c.constant||(b=!1)}while(h(","))}j("]");return y(function(b,c){for(var d=[],e=0;e<a.length;e++)d.push(a[e](b,c));return d},{literal:!0,constant:b})}function o(){var a=[],b=!0;if(f().text!="}"){do{var c=h(),c=c.string||c.text;j(":");var d=N();a.push({key:c,value:d});d.constant||(b=!1)}while(h(","))}j("}");return y(function(b,c){for(var d=
|
||||
{},e=0;e<a.length;e++){var g=a[e],h=g.value(b,c);d[g.key]=h}return d},{literal:!0,constant:b})}var A=Q(0),H,F=Mc(b,d),N=function(){var a=q(),c,d;return(d=h("="))?(a.assign||e("implies assignment but ["+b.substring(0,d.index)+"] can not be assigned to",d),c=q(),function(b,d){return a.assign(b,c(b,d),d)}):a},ea=function(a,b){var c=[];if(f().text!=")"){do c.push(N());while(h(","))}j(")");return function(d,e){for(var g=[],h=b?b(d,e):d,f=0;f<c.length;f++)g.push(c[f](d,e));f=a(d,e)||t;return f.apply?f.apply(h,
|
||||
g):f(g[0],g[1],g[2],g[3],g[4])}},ga=function(a){var b=h().text,c=Nb(b,d);return y(function(b,d){return c(a(b,d),d)},{assign:function(c,d,e){return Ob(a(c,e),b,d)}})},z=function(a){var b=N();j("]");return y(function(c,d){var e=a(c,d),g=b(c,d),h;if(!e)return s;if((e=e[g])&&e.then){h=e;if(!("$$v"in e))h.$$v=s,h.then(function(a){h.$$v=a});e=e.$$v}return e},{assign:function(c,d,e){return a(c,e)[b(c,e)]=d}})},fa=function(){for(var a=N(),b;;)if(b=h("|"))a=m(a,b.fn,l());else return a};a?(N=q,ea=ga=z=fa=function(){e("is not valid json",
|
||||
{text:b,index:0})},H=D()):H=k();F.length!==0&&e("is an unexpected token",F[0]);H.literal=!!H.literal;H.constant=!!H.constant;return H}function Ob(b,a,c){for(var a=a.split("."),d=0;a.length>1;d++){var e=a.shift(),f=b[e];f||(f={},b[e]=f);b=f}return b[a.shift()]=c}function kb(b,a,c){if(!a)return b;for(var a=a.split("."),d,e=b,f=a.length,i=0;i<f;i++)d=a[i],b&&(b=(e=b)[d]);return!c&&I(b)?ab(e,b):b}function Pb(b,a,c,d,e){return function(f,i){var h=i&&i.hasOwnProperty(b)?i:f,j;if(h===null||h===s)return h;
|
||||
if((h=h[b])&&h.then){if(!("$$v"in h))j=h,j.$$v=s,j.then(function(a){j.$$v=a});h=h.$$v}if(!a||h===null||h===s)return h;if((h=h[a])&&h.then){if(!("$$v"in h))j=h,j.$$v=s,j.then(function(a){j.$$v=a});h=h.$$v}if(!c||h===null||h===s)return h;if((h=h[c])&&h.then){if(!("$$v"in h))j=h,j.$$v=s,j.then(function(a){j.$$v=a});h=h.$$v}if(!d||h===null||h===s)return h;if((h=h[d])&&h.then){if(!("$$v"in h))j=h,j.$$v=s,j.then(function(a){j.$$v=a});h=h.$$v}if(!e||h===null||h===s)return h;if((h=h[e])&&h.then){if(!("$$v"in
|
||||
h))j=h,j.$$v=s,j.then(function(a){j.$$v=a});h=h.$$v}return h}}function Nb(b,a){if(nb.hasOwnProperty(b))return nb[b];var c=b.split("."),d=c.length,e;if(a)e=d<6?Pb(c[0],c[1],c[2],c[3],c[4]):function(a,b){var e=0,g;do g=Pb(c[e++],c[e++],c[e++],c[e++],c[e++])(a,b),b=s,a=g;while(e<d);return g};else{var f="var l, fn, p;\n";o(c,function(a,b){f+="if(s === null || s === undefined) return s;\nl=s;\ns="+(b?"s":'((k&&k.hasOwnProperty("'+a+'"))?k:s)')+'["'+a+'"];\nif (s && s.then) {\n if (!("$$v" in s)) {\n p=s;\n p.$$v = undefined;\n p.then(function(v) {p.$$v=v;});\n}\n s=s.$$v\n}\n'});
|
||||
f+="return s;";e=Function("s","k",f);e.toString=function(){return f}}return nb[b]=e}function Pc(){var b={};this.$get=["$filter","$sniffer",function(a,c){return function(d){switch(typeof d){case "string":return b.hasOwnProperty(d)?b[d]:b[d]=Oc(d,!1,a,c.csp);case "function":return d;default:return t}}}]}function Qc(){this.$get=["$rootScope","$exceptionHandler",function(b,a){return Rc(function(a){b.$evalAsync(a)},a)}]}function Rc(b,a){function c(a){return a}function d(a){return i(a)}var e=function(){var h=
|
||||
[],j,g;return g={resolve:function(a){if(h){var c=h;h=s;j=f(a);c.length&&b(function(){for(var a,b=0,d=c.length;b<d;b++)a=c[b],j.then(a[0],a[1])})}},reject:function(a){g.resolve(i(a))},promise:{then:function(b,g){var f=e(),i=function(d){try{f.resolve((b||c)(d))}catch(e){a(e),f.reject(e)}},n=function(b){try{f.resolve((g||d)(b))}catch(c){a(c),f.reject(c)}};h?h.push([i,n]):j.then(i,n);return f.promise}}}},f=function(a){return a&&a.then?a:{then:function(c){var d=e();b(function(){d.resolve(c(a))});return d.promise}}},
|
||||
i=function(a){return{then:function(c,g){var f=e();b(function(){f.resolve((g||d)(a))});return f.promise}}};return{defer:e,reject:i,when:function(h,j,g){var m=e(),k,l=function(b){try{return(j||c)(b)}catch(d){return a(d),i(d)}},q=function(b){try{return(g||d)(b)}catch(c){return a(c),i(c)}};b(function(){f(h).then(function(a){k||(k=!0,m.resolve(f(a).then(l,q)))},function(a){k||(k=!0,m.resolve(q(a)))})});return m.promise},all:function(a){var b=e(),c=0,d=C(a)?[]:{};o(a,function(a,e){c++;f(a).then(function(a){d.hasOwnProperty(e)||
|
||||
(d[e]=a,--c||b.resolve(d))},function(a){d.hasOwnProperty(e)||b.reject(a)})});c===0&&b.resolve(d);return b.promise}}}function Sc(){var b={};this.when=function(a,c){b[a]=y({reloadOnSearch:!0,caseInsensitiveMatch:!1},c);if(a){var d=a[a.length-1]=="/"?a.substr(0,a.length-1):a+"/";b[d]={redirectTo:a}}return this};this.otherwise=function(a){this.when(null,a);return this};this.$get=["$rootScope","$location","$routeParams","$q","$injector","$http","$templateCache",function(a,c,d,e,f,i,h){function j(a,b,c){for(var b=
|
||||
"^"+b.replace(/[-\/\\^$:*+?.()|[\]{}]/g,"\\$&")+"$",d="",e=[],g={},f=/\\([:*])(\w+)/g,h,j=0;(h=f.exec(b))!==null;){d+=b.slice(j,h.index);switch(h[1]){case ":":d+="([^\\/]*)";break;case "*":d+="(.*)"}e.push(h[2]);j=f.lastIndex}d+=b.substr(j);var i=a.match(RegExp(d,c.caseInsensitiveMatch?"i":""));i&&o(e,function(a,b){g[a]=i[b+1]});return i?g:null}function g(){var b=m(),g=q.current;if(b&&g&&b.$$route===g.$$route&&ja(b.pathParams,g.pathParams)&&!b.reloadOnSearch&&!l)g.params=b.params,W(g.params,d),a.$broadcast("$routeUpdate",
|
||||
g);else if(b||g)l=!1,a.$broadcast("$routeChangeStart",b,g),(q.current=b)&&b.redirectTo&&(x(b.redirectTo)?c.path(k(b.redirectTo,b.params)).search(b.params).replace():c.url(b.redirectTo(b.pathParams,c.path(),c.search())).replace()),e.when(b).then(function(){if(b){var a=y({},b.resolve),c;o(a,function(b,c){a[c]=x(b)?f.get(b):f.invoke(b)});if(w(c=b.template))I(c)&&(c=c(b.params));else if(w(c=b.templateUrl))if(I(c)&&(c=c(b.params)),w(c))b.loadedTemplateUrl=c,c=i.get(c,{cache:h}).then(function(a){return a.data});
|
||||
w(c)&&(a.$template=c);return e.all(a)}}).then(function(c){if(b==q.current){if(b)b.locals=c,W(b.params,d);a.$broadcast("$routeChangeSuccess",b,g)}},function(c){b==q.current&&a.$broadcast("$routeChangeError",b,g,c)})}function m(){var a,d;o(b,function(b,e){if(!d&&(a=j(c.path(),e,b)))d=Fa(b,{params:y({},c.search(),a),pathParams:a}),d.$$route=b});return d||b[null]&&Fa(b[null],{params:{},pathParams:{}})}function k(a,b){var c=[];o((a||"").split(":"),function(a,d){if(d==0)c.push(a);else{var e=a.match(/(\w+)(.*)/),
|
||||
g=e[1];c.push(b[g]);c.push(e[2]||"");delete b[g]}});return c.join("")}var l=!1,q={routes:b,reload:function(){l=!0;a.$evalAsync(g)}};a.$on("$locationChangeSuccess",g);return q}]}function Tc(){this.$get=Q({})}function Uc(){var b=10;this.digestTtl=function(a){arguments.length&&(b=a);return b};this.$get=["$injector","$exceptionHandler","$parse",function(a,c,d){function e(){this.$id=Ea();this.$$phase=this.$parent=this.$$watchers=this.$$nextSibling=this.$$prevSibling=this.$$childHead=this.$$childTail=null;
|
||||
this["this"]=this.$root=this;this.$$destroyed=!1;this.$$asyncQueue=[];this.$$listeners={};this.$$isolateBindings={}}function f(a){if(j.$$phase)throw Error(j.$$phase+" already in progress");j.$$phase=a}function i(a,b){var c=d(a);va(c,b);return c}function h(){}e.prototype={$new:function(a){if(I(a))throw Error("API-CHANGE: Use $controller to instantiate controllers.");a?(a=new e,a.$root=this.$root):(a=function(){},a.prototype=this,a=new a,a.$id=Ea());a["this"]=a;a.$$listeners={};a.$parent=this;a.$$watchers=
|
||||
a.$$nextSibling=a.$$childHead=a.$$childTail=null;a.$$prevSibling=this.$$childTail;this.$$childHead?this.$$childTail=this.$$childTail.$$nextSibling=a:this.$$childHead=this.$$childTail=a;return a},$watch:function(a,b,c){var d=i(a,"watch"),e=this.$$watchers,f={fn:b,last:h,get:d,exp:a,eq:!!c};if(!I(b)){var j=i(b||t,"listener");f.fn=function(a,b,c){j(c)}}if(typeof a=="string"&&d.constant){var r=f.fn;f.fn=function(a,b,c){r.call(this,a,b,c);sa(e,f)}}if(!e)e=this.$$watchers=[];e.unshift(f);return function(){sa(e,
|
||||
f)}},$watchCollection:function(a,b){var c=this,e,f,h=0,j=d(a),i=[],p={},o=0;return this.$watch(function(){f=j(c);var a,b;if(L(f))if(C(f)){if(e!==i)e=i,o=e.length=0,h++;a=f.length;if(o!==a)h++,e.length=o=a;for(b=0;b<a;b++)e[b]!==f[b]&&(h++,e[b]=f[b])}else{e!==p&&(e=p={},o=0,h++);a=0;for(b in f)f.hasOwnProperty(b)&&(a++,e.hasOwnProperty(b)?e[b]!==f[b]&&(h++,e[b]=f[b]):(o++,e[b]=f[b],h++));if(o>a)for(b in h++,e)e.hasOwnProperty(b)&&!f.hasOwnProperty(b)&&(o--,delete e[b])}else e!==f&&(e=f,h++);return h},
|
||||
function(){b(f,e,c)})},$digest:function(){var a,d,e,i,q=this.$$asyncQueue,n,o,r=b,p,E=[],D,G;f("$digest");do{o=!1;for(p=this;q.length;)try{p.$eval(q.shift())}catch(s){c(s)}do{if(i=p.$$watchers)for(n=i.length;n--;)try{if(a=i[n],(d=a.get(p))!==(e=a.last)&&!(a.eq?ja(d,e):typeof d=="number"&&typeof e=="number"&&isNaN(d)&&isNaN(e)))o=!0,a.last=a.eq?W(d):d,a.fn(d,e===h?d:e,p),r<5&&(D=4-r,E[D]||(E[D]=[]),G=I(a.exp)?"fn: "+(a.exp.name||a.exp.toString()):a.exp,G+="; newVal: "+da(d)+"; oldVal: "+da(e),E[D].push(G))}catch(A){c(A)}if(!(i=
|
||||
p.$$childHead||p!==this&&p.$$nextSibling))for(;p!==this&&!(i=p.$$nextSibling);)p=p.$parent}while(p=i);if(o&&!r--)throw j.$$phase=null,Error(b+" $digest() iterations reached. Aborting!\nWatchers fired in the last 5 iterations: "+da(E));}while(o||q.length);j.$$phase=null},$destroy:function(){if(!(j==this||this.$$destroyed)){var a=this.$parent;this.$broadcast("$destroy");this.$$destroyed=!0;if(a.$$childHead==this)a.$$childHead=this.$$nextSibling;if(a.$$childTail==this)a.$$childTail=this.$$prevSibling;
|
||||
if(this.$$prevSibling)this.$$prevSibling.$$nextSibling=this.$$nextSibling;if(this.$$nextSibling)this.$$nextSibling.$$prevSibling=this.$$prevSibling;this.$parent=this.$$nextSibling=this.$$prevSibling=this.$$childHead=this.$$childTail=null}},$eval:function(a,b){return d(a)(this,b)},$evalAsync:function(a){this.$$asyncQueue.push(a)},$apply:function(a){try{return f("$apply"),this.$eval(a)}catch(b){c(b)}finally{j.$$phase=null;try{j.$digest()}catch(d){throw c(d),d;}}},$on:function(a,b){var c=this.$$listeners[a];
|
||||
c||(this.$$listeners[a]=c=[]);c.push(b);return function(){c[Ga(c,b)]=null}},$emit:function(a,b){var d=[],e,f=this,h=!1,i={name:a,targetScope:f,stopPropagation:function(){h=!0},preventDefault:function(){i.defaultPrevented=!0},defaultPrevented:!1},j=[i].concat(ka.call(arguments,1)),p,o;do{e=f.$$listeners[a]||d;i.currentScope=f;p=0;for(o=e.length;p<o;p++)if(e[p])try{if(e[p].apply(null,j),h)return i}catch(D){c(D)}else e.splice(p,1),p--,o--;f=f.$parent}while(f);return i},$broadcast:function(a,b){var d=
|
||||
this,e=this,f={name:a,targetScope:this,preventDefault:function(){f.defaultPrevented=!0},defaultPrevented:!1},h=[f].concat(ka.call(arguments,1)),i,j;do{d=e;f.currentScope=d;e=d.$$listeners[a]||[];i=0;for(j=e.length;i<j;i++)if(e[i])try{e[i].apply(null,h)}catch(p){c(p)}else e.splice(i,1),i--,j--;if(!(e=d.$$childHead||d!==this&&d.$$nextSibling))for(;d!==this&&!(e=d.$$nextSibling);)d=d.$parent}while(d=e);return f}};var j=new e;return j}]}function Vc(){this.$get=["$window","$document",function(b,a){var c=
|
||||
{},d=K((/android (\d+)/.exec(J((b.navigator||{}).userAgent))||[])[1]),e=a[0]||{},f,i=/^(Moz|webkit|O|ms)(?=[A-Z])/,h=e.body&&e.body.style,j=!1;if(h){for(var g in h)if(j=i.exec(g)){f=j[0];f=f.substr(0,1).toUpperCase()+f.substr(1);break}j=!!(f+"Transition"in h)}return{history:!(!b.history||!b.history.pushState||d<4),hashchange:"onhashchange"in b&&(!e.documentMode||e.documentMode>7),hasEvent:function(a){if(a=="input"&&X==9)return!1;if(u(c[a])){var b=e.createElement("div");c[a]="on"+a in b}return c[a]},
|
||||
csp:e.securityPolicy?e.securityPolicy.isActive:!1,vendorPrefix:f,supportsTransitions:j}}]}function Wc(){this.$get=Q(M)}function Qb(b){var a={},c,d,e;if(!b)return a;o(b.split("\n"),function(b){e=b.indexOf(":");c=J(S(b.substr(0,e)));d=S(b.substr(e+1));c&&(a[c]?a[c]+=", "+d:a[c]=d)});return a}function Xc(b,a){var c=Yc.exec(b);if(c==null)return!0;var d={protocol:c[2],host:c[4],port:K(c[6])||Ba[c[2]]||null,relativeProtocol:c[2]===s||c[2]===""},c=lb.exec(a),c={protocol:c[1],host:c[3],port:K(c[5])||Ba[c[1]]||
|
||||
null};return(d.protocol==c.protocol||d.relativeProtocol)&&d.host==c.host&&(d.port==c.port||d.relativeProtocol&&c.port==Ba[c.protocol])}function Rb(b){var a=L(b)?b:s;return function(c){a||(a=Qb(b));return c?a[J(c)]||null:a}}function Sb(b,a,c){if(I(c))return c(b,a);o(c,function(c){b=c(b,a)});return b}function Zc(){var b=/^\s*(\[|\{[^\{])/,a=/[\}\]]\s*$/,c=/^\)\]\}',?\n/,d=this.defaults={transformResponse:[function(d){x(d)&&(d=d.replace(c,""),b.test(d)&&a.test(d)&&(d=tb(d,!0)));return d}],transformRequest:[function(a){return L(a)&&
|
||||
Da.apply(a)!=="[object File]"?da(a):a}],headers:{common:{Accept:"application/json, text/plain, */*"},post:{"Content-Type":"application/json;charset=utf-8"},put:{"Content-Type":"application/json;charset=utf-8"}},xsrfCookieName:"XSRF-TOKEN",xsrfHeaderName:"X-XSRF-TOKEN"},e=this.interceptors=[],f=this.responseInterceptors=[];this.$get=["$httpBackend","$browser","$cacheFactory","$rootScope","$q","$injector",function(a,b,c,g,m,k){function l(a){function c(a){var b=y({},a,{data:Sb(a.data,a.headers,e.transformResponse)});
|
||||
return 200<=a.status&&a.status<300?b:m.reject(b)}var e={transformRequest:d.transformRequest,transformResponse:d.transformResponse},g={};y(e,a);e.headers=g;e.method=na(e.method);y(g,d.headers.common,d.headers[J(e.method)],a.headers);(a=Xc(e.url,b.url())?b.cookies()[e.xsrfCookieName||d.xsrfCookieName]:s)&&(g[e.xsrfHeaderName||d.xsrfHeaderName]=a);var f=[function(a){var b=Sb(a.data,Rb(g),a.transformRequest);u(a.data)&&delete g["Content-Type"];if(u(a.withCredentials)&&!u(d.withCredentials))a.withCredentials=
|
||||
d.withCredentials;return q(a,b,g).then(c,c)},s],j=m.when(e);for(o(r,function(a){(a.request||a.requestError)&&f.unshift(a.request,a.requestError);(a.response||a.responseError)&&f.push(a.response,a.responseError)});f.length;)var a=f.shift(),i=f.shift(),j=j.then(a,i);j.success=function(a){j.then(function(b){a(b.data,b.status,b.headers,e)});return j};j.error=function(a){j.then(null,function(b){a(b.data,b.status,b.headers,e)});return j};return j}function q(b,c,e){function f(a,b,c){o&&(200<=a&&a<300?o.put(s,
|
||||
[a,b,Qb(c)]):o.remove(s));h(b,a,c);g.$apply()}function h(a,c,d){c=Math.max(c,0);(200<=c&&c<300?k.resolve:k.reject)({data:a,status:c,headers:Rb(d),config:b})}function j(){var a=Ga(l.pendingRequests,b);a!==-1&&l.pendingRequests.splice(a,1)}var k=m.defer(),q=k.promise,o,r,s=n(b.url,b.params);l.pendingRequests.push(b);q.then(j,j);if((b.cache||d.cache)&&b.cache!==!1&&b.method=="GET")o=L(b.cache)?b.cache:L(d.cache)?d.cache:B;if(o)if(r=o.get(s))if(r.then)return r.then(j,j),r;else C(r)?h(r[1],r[0],W(r[2])):
|
||||
h(r,200,{});else o.put(s,q);r||a(b.method,s,c,f,e,b.timeout,b.withCredentials,b.responseType);return q}function n(a,b){if(!b)return a;var c=[];ic(b,function(a,b){a==null||a==s||(C(a)||(a=[a]),o(a,function(a){L(a)&&(a=da(a));c.push(ua(b)+"="+ua(a))}))});return a+(a.indexOf("?")==-1?"?":"&")+c.join("&")}var B=c("$http"),r=[];o(e,function(a){r.unshift(x(a)?k.get(a):k.invoke(a))});o(f,function(a,b){var c=x(a)?k.get(a):k.invoke(a);r.splice(b,0,{response:function(a){return c(m.when(a))},responseError:function(a){return c(m.reject(a))}})});
|
||||
l.pendingRequests=[];(function(a){o(arguments,function(a){l[a]=function(b,c){return l(y(c||{},{method:a,url:b}))}})})("get","delete","head","jsonp");(function(a){o(arguments,function(a){l[a]=function(b,c,d){return l(y(d||{},{method:a,url:b,data:c}))}})})("post","put");l.defaults=d;return l}]}function $c(){this.$get=["$browser","$window","$document",function(b,a,c){return ad(b,bd,b.defer,a.angular.callbacks,c[0],a.location.protocol.replace(":",""))}]}function ad(b,a,c,d,e,f){function i(a,b){var c=
|
||||
e.createElement("script"),d=function(){e.body.removeChild(c);b&&b()};c.type="text/javascript";c.src=a;X?c.onreadystatechange=function(){/loaded|complete/.test(c.readyState)&&d()}:c.onload=c.onerror=d;e.body.appendChild(c)}return function(e,j,g,m,k,l,q,n){function B(a,c,d,e){c=(j.match(lb)||["",f])[1]=="file"?d?200:404:c;a(c==1223?204:c,d,e);b.$$completeOutstandingRequest(t)}b.$$incOutstandingRequestCount();j=j||b.url();if(J(e)=="jsonp"){var r="_"+(d.counter++).toString(36);d[r]=function(a){d[r].data=
|
||||
a};i(j.replace("JSON_CALLBACK","angular.callbacks."+r),function(){d[r].data?B(m,200,d[r].data):B(m,-2);delete d[r]})}else{var p=new a;p.open(e,j,!0);o(k,function(a,b){a&&p.setRequestHeader(b,a)});var s;p.onreadystatechange=function(){if(p.readyState==4){var a=p.getAllResponseHeaders(),b=["Cache-Control","Content-Language","Content-Type","Expires","Last-Modified","Pragma"];a||(a="",o(b,function(b){var c=p.getResponseHeader(b);c&&(a+=b+": "+c+"\n")}));B(m,s||p.status,p.responseType?p.response:p.responseText,
|
||||
a)}};if(q)p.withCredentials=!0;if(n)p.responseType=n;p.send(g||"");l>0&&c(function(){s=-1;p.abort()},l)}}}function cd(){this.$get=function(){return{id:"en-us",NUMBER_FORMATS:{DECIMAL_SEP:".",GROUP_SEP:",",PATTERNS:[{minInt:1,minFrac:0,maxFrac:3,posPre:"",posSuf:"",negPre:"-",negSuf:"",gSize:3,lgSize:3},{minInt:1,minFrac:2,maxFrac:2,posPre:"\u00a4",posSuf:"",negPre:"(\u00a4",negSuf:")",gSize:3,lgSize:3}],CURRENCY_SYM:"$"},DATETIME_FORMATS:{MONTH:"January,February,March,April,May,June,July,August,September,October,November,December".split(","),
|
||||
SHORTMONTH:"Jan,Feb,Mar,Apr,May,Jun,Jul,Aug,Sep,Oct,Nov,Dec".split(","),DAY:"Sunday,Monday,Tuesday,Wednesday,Thursday,Friday,Saturday".split(","),SHORTDAY:"Sun,Mon,Tue,Wed,Thu,Fri,Sat".split(","),AMPMS:["AM","PM"],medium:"MMM d, y h:mm:ss a","short":"M/d/yy h:mm a",fullDate:"EEEE, MMMM d, y",longDate:"MMMM d, y",mediumDate:"MMM d, y",shortDate:"M/d/yy",mediumTime:"h:mm:ss a",shortTime:"h:mm a"},pluralCat:function(b){return b===1?"one":"other"}}}}function dd(){this.$get=["$rootScope","$browser","$q",
|
||||
"$exceptionHandler",function(b,a,c,d){function e(e,h,j){var g=c.defer(),m=g.promise,k=w(j)&&!j,h=a.defer(function(){try{g.resolve(e())}catch(a){g.reject(a),d(a)}k||b.$apply()},h),j=function(){delete f[m.$$timeoutId]};m.$$timeoutId=h;f[h]=g;m.then(j,j);return m}var f={};e.cancel=function(b){return b&&b.$$timeoutId in f?(f[b.$$timeoutId].reject("canceled"),a.defer.cancel(b.$$timeoutId)):!1};return e}]}function Tb(b){function a(a,e){return b.factory(a+c,e)}var c="Filter";this.register=a;this.$get=["$injector",
|
||||
function(a){return function(b){return a.get(b+c)}}];a("currency",Ub);a("date",Vb);a("filter",ed);a("json",fd);a("limitTo",gd);a("lowercase",hd);a("number",Wb);a("orderBy",Xb);a("uppercase",id)}function ed(){return function(b,a,c){if(!C(b))return b;var d=[];d.check=function(a){for(var b=0;b<d.length;b++)if(!d[b](a))return!1;return!0};switch(typeof c){case "function":break;case "boolean":if(c==!0){c=function(a,b){return Ia.equals(a,b)};break}default:c=function(a,b){b=(""+b).toLowerCase();return(""+
|
||||
a).toLowerCase().indexOf(b)>-1}}var e=function(a,b){if(typeof b=="string"&&b.charAt(0)==="!")return!e(a,b.substr(1));switch(typeof a){case "boolean":case "number":case "string":return c(a,b);case "object":switch(typeof b){case "object":return c(a,b);default:for(var d in a)if(d.charAt(0)!=="$"&&e(a[d],b))return!0}return!1;case "array":for(d=0;d<a.length;d++)if(e(a[d],b))return!0;return!1;default:return!1}};switch(typeof a){case "boolean":case "number":case "string":a={$:a};case "object":for(var f in a)f==
|
||||
"$"?function(){if(a[f]){var b=f;d.push(function(c){return e(c,a[b])})}}():function(){if(a[f]){var b=f;d.push(function(c){return e(kb(c,b),a[b])})}}();break;case "function":d.push(a);break;default:return b}for(var i=[],h=0;h<b.length;h++){var j=b[h];d.check(j)&&i.push(j)}return i}}function Ub(b){var a=b.NUMBER_FORMATS;return function(b,d){if(u(d))d=a.CURRENCY_SYM;return Yb(b,a.PATTERNS[1],a.GROUP_SEP,a.DECIMAL_SEP,2).replace(/\u00A4/g,d)}}function Wb(b){var a=b.NUMBER_FORMATS;return function(b,d){return Yb(b,
|
||||
a.PATTERNS[0],a.GROUP_SEP,a.DECIMAL_SEP,d)}}function Yb(b,a,c,d,e){if(isNaN(b)||!isFinite(b))return"";var f=b<0,b=Math.abs(b),i=b+"",h="",j=[],g=!1;if(i.indexOf("e")!==-1){var m=i.match(/([\d\.]+)e(-?)(\d+)/);m&&m[2]=="-"&&m[3]>e+1?i="0":(h=i,g=!0)}if(!g){i=(i.split(Zb)[1]||"").length;u(e)&&(e=Math.min(Math.max(a.minFrac,i),a.maxFrac));var i=Math.pow(10,e),b=Math.round(b*i)/i,b=(""+b).split(Zb),i=b[0],b=b[1]||"",g=0,m=a.lgSize,k=a.gSize;if(i.length>=m+k)for(var g=i.length-m,l=0;l<g;l++)(g-l)%k===
|
||||
0&&l!==0&&(h+=c),h+=i.charAt(l);for(l=g;l<i.length;l++)(i.length-l)%m===0&&l!==0&&(h+=c),h+=i.charAt(l);for(;b.length<e;)b+="0";e&&e!=="0"&&(h+=d+b.substr(0,e))}j.push(f?a.negPre:a.posPre);j.push(h);j.push(f?a.negSuf:a.posSuf);return j.join("")}function ob(b,a,c){var d="";b<0&&(d="-",b=-b);for(b=""+b;b.length<a;)b="0"+b;c&&(b=b.substr(b.length-a));return d+b}function O(b,a,c,d){return function(e){e=e["get"+b]();if(c>0||e>-c)e+=c;e===0&&c==-12&&(e=12);return ob(e,a,d)}}function Sa(b,a){return function(c,
|
||||
d){var e=c["get"+b](),f=na(a?"SHORT"+b:b);return d[f][e]}}function Vb(b){function a(a){var b;if(b=a.match(c)){var a=new Date(0),f=0,i=0,h=b[8]?a.setUTCFullYear:a.setFullYear,j=b[8]?a.setUTCHours:a.setHours;b[9]&&(f=K(b[9]+b[10]),i=K(b[9]+b[11]));h.call(a,K(b[1]),K(b[2])-1,K(b[3]));j.call(a,K(b[4]||0)-f,K(b[5]||0)-i,K(b[6]||0),K(b[7]||0))}return a}var c=/^(\d{4})-?(\d\d)-?(\d\d)(?:T(\d\d)(?::?(\d\d)(?::?(\d\d)(?:\.(\d+))?)?)?(Z|([+-])(\d\d):?(\d\d))?)?$/;return function(c,e){var f="",i=[],h,j,e=e||
|
||||
"mediumDate",e=b.DATETIME_FORMATS[e]||e;x(c)&&(c=jd.test(c)?K(c):a(c));Za(c)&&(c=new Date(c));if(!qa(c))return c;for(;e;)(j=kd.exec(e))?(i=i.concat(ka.call(j,1)),e=i.pop()):(i.push(e),e=null);o(i,function(a){h=ld[a];f+=h?h(c,b.DATETIME_FORMATS):a.replace(/(^'|'$)/g,"").replace(/''/g,"'")});return f}}function fd(){return function(b){return da(b,!0)}}function gd(){return function(b,a){if(!C(b)&&!x(b))return b;a=K(a);if(x(b))return a?a>=0?b.slice(0,a):b.slice(a,b.length):"";var c=[],d,e;a>b.length?a=
|
||||
b.length:a<-b.length&&(a=-b.length);a>0?(d=0,e=a):(d=b.length+a,e=b.length);for(;d<e;d++)c.push(b[d]);return c}}function Xb(b){return function(a,c,d){function e(a,b){return Ha(b)?function(b,c){return a(c,b)}:a}if(!C(a))return a;if(!c)return a;for(var c=C(c)?c:[c],c=$a(c,function(a){var c=!1,d=a||pa;if(x(a)){if(a.charAt(0)=="+"||a.charAt(0)=="-")c=a.charAt(0)=="-",a=a.substring(1);d=b(a)}return e(function(a,b){var c;c=d(a);var e=d(b),f=typeof c,h=typeof e;f==h?(f=="string"&&(c=c.toLowerCase()),f==
|
||||
"string"&&(e=e.toLowerCase()),c=c===e?0:c<e?-1:1):c=f<h?-1:1;return c},c)}),f=[],i=0;i<a.length;i++)f.push(a[i]);return f.sort(e(function(a,b){for(var d=0;d<c.length;d++){var e=c[d](a,b);if(e!==0)return e}return 0},d))}}function Y(b){I(b)&&(b={link:b});b.restrict=b.restrict||"AC";return Q(b)}function $b(b,a){function c(a,c){c=c?"-"+db(c,"-"):"";b.removeClass((a?Ta:Ua)+c).addClass((a?Ua:Ta)+c)}var d=this,e=b.parent().controller("form")||Va,f=0,i=d.$error={},h=[];d.$name=a.name;d.$dirty=!1;d.$pristine=
|
||||
!0;d.$valid=!0;d.$invalid=!1;e.$addControl(d);b.addClass(oa);c(!0);d.$addControl=function(a){h.push(a);a.$name&&!d.hasOwnProperty(a.$name)&&(d[a.$name]=a)};d.$removeControl=function(a){a.$name&&d[a.$name]===a&&delete d[a.$name];o(i,function(b,c){d.$setValidity(c,!0,a)});sa(h,a)};d.$setValidity=function(a,b,h){var k=i[a];if(b){if(k&&(sa(k,h),!k.length)){f--;if(!f)c(b),d.$valid=!0,d.$invalid=!1;i[a]=!1;c(!0,a);e.$setValidity(a,!0,d)}}else{f||c(b);if(k){if(Ga(k,h)!=-1)return}else i[a]=k=[],f++,c(!1,
|
||||
a),e.$setValidity(a,!1,d);k.push(h);d.$valid=!1;d.$invalid=!0}};d.$setDirty=function(){b.removeClass(oa).addClass(Wa);d.$dirty=!0;d.$pristine=!1;e.$setDirty()};d.$setPristine=function(){b.removeClass(Wa).addClass(oa);d.$dirty=!1;d.$pristine=!0;o(h,function(a){a.$setPristine()})}}function U(b){return u(b)||b===""||b===null||b!==b}function Xa(b,a,c,d,e,f){var i=function(){var e=a.val();if(Ha(c.ngTrim||"T"))e=S(e);d.$viewValue!==e&&b.$apply(function(){d.$setViewValue(e)})};if(e.hasEvent("input"))a.bind("input",
|
||||
i);else{var h;a.bind("keydown",function(a){a=a.keyCode;a===91||15<a&&a<19||37<=a&&a<=40||h||(h=f.defer(function(){i();h=null}))});a.bind("change",i)}d.$render=function(){a.val(U(d.$viewValue)?"":d.$viewValue)};var j=c.ngPattern,g=function(a,b){return U(b)||a.test(b)?(d.$setValidity("pattern",!0),b):(d.$setValidity("pattern",!1),s)};j&&(j.match(/^\/(.*)\/$/)?(j=RegExp(j.substr(1,j.length-2)),e=function(a){return g(j,a)}):e=function(a){var c=b.$eval(j);if(!c||!c.test)throw Error("Expected "+j+" to be a RegExp but was "+
|
||||
c);return g(c,a)},d.$formatters.push(e),d.$parsers.push(e));if(c.ngMinlength){var m=K(c.ngMinlength),e=function(a){return!U(a)&&a.length<m?(d.$setValidity("minlength",!1),s):(d.$setValidity("minlength",!0),a)};d.$parsers.push(e);d.$formatters.push(e)}if(c.ngMaxlength){var k=K(c.ngMaxlength),e=function(a){return!U(a)&&a.length>k?(d.$setValidity("maxlength",!1),s):(d.$setValidity("maxlength",!0),a)};d.$parsers.push(e);d.$formatters.push(e)}}function pb(b,a){b="ngClass"+b;return Y(function(c,d,e){function f(b){if(a===
|
||||
!0||c.$index%2===a)j&&b!==j&&i(j),h(b);j=b}function i(a){L(a)&&!C(a)&&(a=$a(a,function(a,b){if(a)return b}));d.removeClass(C(a)?a.join(" "):a)}function h(a){L(a)&&!C(a)&&(a=$a(a,function(a,b){if(a)return b}));a&&d.addClass(C(a)?a.join(" "):a)}var j=s;c.$watch(e[b],f,!0);e.$observe("class",function(){var a=c.$eval(e[b]);f(a,a)});b!=="ngClass"&&c.$watch("$index",function(d,f){var j=d%2;j!==f%2&&(j==a?h(c.$eval(e[b])):i(c.$eval(e[b])))})})}var J=function(b){return x(b)?b.toLowerCase():b},na=function(b){return x(b)?
|
||||
b.toUpperCase():b},X=K((/msie (\d+)/.exec(J(navigator.userAgent))||[])[1]),v,ca,ka=[].slice,Ya=[].push,Da=Object.prototype.toString,hc=M.angular,Ia=M.angular||(M.angular={}),xa,jb,Z=["0","0","0"];t.$inject=[];pa.$inject=[];jb=X<9?function(b){b=b.nodeName?b:b[0];return b.scopeName&&b.scopeName!="HTML"?na(b.scopeName+":"+b.nodeName):b.nodeName}:function(b){return b.nodeName?b.nodeName:b[0].nodeName};var nc=/[A-Z]/g,md={full:"1.1.4",major:1,minor:1,dot:4,codeName:"quantum-manipulation"},La=P.cache={},
|
||||
Ka=P.expando="ng-"+(new Date).getTime(),rc=1,ac=M.document.addEventListener?function(b,a,c){b.addEventListener(a,c,!1)}:function(b,a,c){b.attachEvent("on"+a,c)},ib=M.document.removeEventListener?function(b,a,c){b.removeEventListener(a,c,!1)}:function(b,a,c){b.detachEvent("on"+a,c)},pc=/([\:\-\_]+(.))/g,qc=/^moz([A-Z])/,za=P.prototype={ready:function(b){function a(){c||(c=!0,b())}var c=!1;V.readyState==="complete"?setTimeout(a):(this.bind("DOMContentLoaded",a),P(M).bind("load",a))},toString:function(){var b=
|
||||
[];o(this,function(a){b.push(""+a)});return"["+b.join(", ")+"]"},eq:function(b){return b>=0?v(this[b]):v(this[this.length+b])},length:0,push:Ya,sort:[].sort,splice:[].splice},Oa={};o("multiple,selected,checked,disabled,readOnly,required,open".split(","),function(b){Oa[J(b)]=b});var Eb={};o("input,select,option,textarea,button,form,details".split(","),function(b){Eb[na(b)]=!0});o({data:zb,inheritedData:Na,scope:function(b){return Na(b,"$scope")},controller:Cb,injector:function(b){return Na(b,"$injector")},
|
||||
removeAttr:function(b,a){b.removeAttribute(a)},hasClass:Ma,css:function(b,a,c){a=Ja(a);if(w(c))b.style[a]=c;else{var d;X<=8&&(d=b.currentStyle&&b.currentStyle[a],d===""&&(d="auto"));d=d||b.style[a];X<=8&&(d=d===""?s:d);return d}},attr:function(b,a,c){var d=J(a);if(Oa[d])if(w(c))c?(b[a]=!0,b.setAttribute(a,d)):(b[a]=!1,b.removeAttribute(d));else return b[a]||(b.attributes.getNamedItem(a)||t).specified?d:s;else if(w(c))b.setAttribute(a,c);else if(b.getAttribute)return b=b.getAttribute(a,2),b===null?
|
||||
s:b},prop:function(b,a,c){if(w(c))b[a]=c;else return b[a]},text:y(X<9?function(b,a){if(b.nodeType==1){if(u(a))return b.innerText;b.innerText=a}else{if(u(a))return b.nodeValue;b.nodeValue=a}}:function(b,a){if(u(a))return b.textContent;b.textContent=a},{$dv:""}),val:function(b,a){if(u(a))return b.value;b.value=a},html:function(b,a){if(u(a))return b.innerHTML;for(var c=0,d=b.childNodes;c<d.length;c++)wa(d[c]);b.innerHTML=a}},function(b,a){P.prototype[a]=function(a,d){var e,f;if((b.length==2&&b!==Ma&&
|
||||
b!==Cb?a:d)===s)if(L(a)){for(e=0;e<this.length;e++)if(b===zb)b(this[e],a);else for(f in a)b(this[e],f,a[f]);return this}else{if(this.length)return b(this[0],a,d)}else{for(e=0;e<this.length;e++)b(this[e],a,d);return this}return b.$dv}});o({removeData:xb,dealoc:wa,bind:function a(c,d,e){var f=$(c,"events"),i=$(c,"handle");f||$(c,"events",f={});i||$(c,"handle",i=sc(c,f));o(d.split(" "),function(d){var j=f[d];if(!j){if(d=="mouseenter"||d=="mouseleave"){var g=0;f.mouseenter=[];f.mouseleave=[];a(c,"mouseover",
|
||||
function(a){g++;g==1&&i(a,"mouseenter")});a(c,"mouseout",function(a){g--;g==0&&i(a,"mouseleave")})}else ac(c,d,i),f[d]=[];j=f[d]}j.push(e)})},unbind:yb,replaceWith:function(a,c){var d,e=a.parentNode;wa(a);o(new P(c),function(c){d?e.insertBefore(c,d.nextSibling):e.replaceChild(c,a);d=c})},children:function(a){var c=[];o(a.childNodes,function(a){a.nodeType===1&&c.push(a)});return c},contents:function(a){return a.childNodes||[]},append:function(a,c){o(new P(c),function(c){(a.nodeType===1||a.nodeType===
|
||||
11)&&a.appendChild(c)})},prepend:function(a,c){if(a.nodeType===1){var d=a.firstChild;o(new P(c),function(c){d?a.insertBefore(c,d):(a.appendChild(c),d=c)})}},wrap:function(a,c){var c=v(c)[0],d=a.parentNode;d&&d.replaceChild(c,a);c.appendChild(a)},remove:function(a){wa(a);var c=a.parentNode;c&&c.removeChild(a)},after:function(a,c){var d=a,e=a.parentNode;o(new P(c),function(a){e.insertBefore(a,d.nextSibling);d=a})},addClass:Bb,removeClass:Ab,toggleClass:function(a,c,d){u(d)&&(d=!Ma(a,c));(d?Bb:Ab)(a,
|
||||
c)},parent:function(a){return(a=a.parentNode)&&a.nodeType!==11?a:null},next:function(a){if(a.nextElementSibling)return a.nextElementSibling;for(a=a.nextSibling;a!=null&&a.nodeType!==1;)a=a.nextSibling;return a},find:function(a,c){return a.getElementsByTagName(c)},clone:hb,triggerHandler:function(a,c){var d=($(a,"events")||{})[c];o(d,function(c){c.call(a,null)})}},function(a,c){P.prototype[c]=function(c,e){for(var f,i=0;i<this.length;i++)f==s?(f=a(this[i],c,e),f!==s&&(f=v(f))):gb(f,a(this[i],c,e));
|
||||
return f==s?this:f}});Pa.prototype={put:function(a,c){this[la(a)]=c},get:function(a){return this[la(a)]},remove:function(a){var c=this[a=la(a)];delete this[a];return c}};var uc=/^function\s*[^\(]*\(\s*([^\)]*)\)/m,vc=/,/,wc=/^\s*(_?)(\S+?)\1\s*$/,tc=/((\/\/.*$)|(\/\*[\s\S]*?\*\/))/mg;Gb.$inject=["$provide"];var nd=function(){this.$get=["$animation","$window","$sniffer",function(a,c,d){function e(a){a.css("display","")}function f(a){a.css("display","none")}function i(a,c,d){d?d.after(a):c.append(a)}
|
||||
function h(a){a.remove()}function j(a,c,d){i(a,c,d)}return function(g,m){function k(e,f,h){var i=l&&g.$eval(l),e=l?L(i)?i[e]:i+"-"+e:"",j=(i=a(e))&&i.setup,k=i&&i.start;if(e){var m=e+"-setup",q=e+"-start";return function(a,e,g){function i(){h(a,e,g);a.removeClass(m);a.removeClass(q)}if(!d.supportsTransitions&&!j&&!k)f(a,e,g),h(a,e,g);else{a.addClass(m);f(a,e,g);if(a.length==0)return i();var l=(j||t)(a);c.setTimeout(function(){a.addClass(q);if(k)k(a,i,l);else if(I(c.getComputedStyle)){var e=d.vendorPrefix+
|
||||
"Transition",f=0;o(a,function(a){a=c.getComputedStyle(a)||{};f=Math.max(parseFloat(a.transitionDuration)||parseFloat(a[e+"Duration"])||0,f)});c.setTimeout(i,f*1E3)}else i()},1)}}}else return function(a,c,d){f(a,c,d);h(a,c,d)}}var l=m.ngAnimate,q={};q.enter=k("enter",i,t);q.leave=k("leave",t,h);q.move=k("move",j,t);q.show=k("show",e,t);q.hide=k("hide",t,f);return q}}]},Ib="Non-assignable model expression: ";Hb.$inject=["$provide"];var Cc=/^(x[\:\-_]|data[\:\-_])/i,lb=/^([^:]+):\/\/(\w+:{0,1}\w*@)?(\{?[\w\.-]*\}?)(:([0-9]+))?(\/[^\?#]*)?(\?([^#]*))?(#(.*))?$/,
|
||||
bc=/^([^\?#]*)?(\?([^#]*))?(#(.*))?$/,Jc=bc,Ba={http:80,https:443,ftp:21};mb.prototype={$$replace:!1,absUrl:Ra("$$absUrl"),url:function(a,c){if(u(a))return this.$$url;var d=bc.exec(a);d[1]&&this.path(decodeURIComponent(d[1]));if(d[2]||d[1])this.search(d[3]||"");this.hash(d[5]||"",c);return this},protocol:Ra("$$protocol"),host:Ra("$$host"),port:Ra("$$port"),path:Mb("$$path",function(a){return a.charAt(0)=="/"?a:"/"+a}),search:function(a,c){if(u(a))return this.$$search;w(c)?c===null?delete this.$$search[a]:
|
||||
this.$$search[a]=c:this.$$search=x(a)?bb(a):a;this.$$compose();return this},hash:Mb("$$hash",pa),replace:function(){this.$$replace=!0;return this}};Qa.prototype=Fa(mb.prototype);Lb.prototype=Fa(Qa.prototype);var Ca={"null":function(){return null},"true":function(){return!0},"false":function(){return!1},undefined:t,"+":function(a,c,d,e){d=d(a,c);e=e(a,c);return w(d)?w(e)?d+e:d:w(e)?e:s},"-":function(a,c,d,e){d=d(a,c);e=e(a,c);return(w(d)?d:0)-(w(e)?e:0)},"*":function(a,c,d,e){return d(a,c)*e(a,c)},
|
||||
"/":function(a,c,d,e){return d(a,c)/e(a,c)},"%":function(a,c,d,e){return d(a,c)%e(a,c)},"^":function(a,c,d,e){return d(a,c)^e(a,c)},"=":t,"===":function(a,c,d,e){return d(a,c)===e(a,c)},"!==":function(a,c,d,e){return d(a,c)!==e(a,c)},"==":function(a,c,d,e){return d(a,c)==e(a,c)},"!=":function(a,c,d,e){return d(a,c)!=e(a,c)},"<":function(a,c,d,e){return d(a,c)<e(a,c)},">":function(a,c,d,e){return d(a,c)>e(a,c)},"<=":function(a,c,d,e){return d(a,c)<=e(a,c)},">=":function(a,c,d,e){return d(a,c)>=e(a,
|
||||
c)},"&&":function(a,c,d,e){return d(a,c)&&e(a,c)},"||":function(a,c,d,e){return d(a,c)||e(a,c)},"&":function(a,c,d,e){return d(a,c)&e(a,c)},"|":function(a,c,d,e){return e(a,c)(a,c,d(a,c))},"!":function(a,c,d){return!d(a,c)}},Nc={n:"\n",f:"\u000c",r:"\r",t:"\t",v:"\u000b","'":"'",'"':'"'},nb={},Yc=/^(([^:]+):)?\/\/(\w+:{0,1}\w*@)?([\w\.-]*)?(:([0-9]+))?(.*)$/,bd=M.XMLHttpRequest||function(){try{return new ActiveXObject("Msxml2.XMLHTTP.6.0")}catch(a){}try{return new ActiveXObject("Msxml2.XMLHTTP.3.0")}catch(c){}try{return new ActiveXObject("Msxml2.XMLHTTP")}catch(d){}throw Error("This browser does not support XMLHttpRequest.");
|
||||
};Tb.$inject=["$provide"];Ub.$inject=["$locale"];Wb.$inject=["$locale"];var Zb=".",ld={yyyy:O("FullYear",4),yy:O("FullYear",2,0,!0),y:O("FullYear",1),MMMM:Sa("Month"),MMM:Sa("Month",!0),MM:O("Month",2,1),M:O("Month",1,1),dd:O("Date",2),d:O("Date",1),HH:O("Hours",2),H:O("Hours",1),hh:O("Hours",2,-12),h:O("Hours",1,-12),mm:O("Minutes",2),m:O("Minutes",1),ss:O("Seconds",2),s:O("Seconds",1),sss:O("Milliseconds",3),EEEE:Sa("Day"),EEE:Sa("Day",!0),a:function(a,c){return a.getHours()<12?c.AMPMS[0]:c.AMPMS[1]},
|
||||
Z:function(a){var a=-1*a.getTimezoneOffset(),c=a>=0?"+":"";c+=ob(Math[a>0?"floor":"ceil"](a/60),2)+ob(Math.abs(a%60),2);return c}},kd=/((?:[^yMdHhmsaZE']+)|(?:'(?:[^']|'')*')|(?:E+|y+|M+|d+|H+|h+|m+|s+|a|Z))(.*)/,jd=/^\d+$/;Vb.$inject=["$locale"];var hd=Q(J),id=Q(na);Xb.$inject=["$parse"];var od=Q({restrict:"E",compile:function(a,c){X<=8&&(!c.href&&!c.name&&c.$set("href",""),a.append(V.createComment("IE fix")));return function(a,c){c.bind("click",function(a){c.attr("href")||a.preventDefault()})}}}),
|
||||
qb={};o(Oa,function(a,c){var d=aa("ng-"+c);qb[d]=function(){return{priority:100,compile:function(){return function(a,f,i){a.$watch(i[d],function(a){i.$set(c,!!a)})}}}}});o(["src","href"],function(a){var c=aa("ng-"+a);qb[c]=function(){return{priority:99,link:function(d,e,f){f.$observe(c,function(c){c&&(f.$set(a,c),X&&e.prop(a,f[a]))})}}}});var Va={$addControl:t,$removeControl:t,$setValidity:t,$setDirty:t,$setPristine:t};$b.$inject=["$element","$attrs","$scope"];var Ya=function(a){return["$timeout",
|
||||
function(c){var d={name:"form",restrict:"E",controller:$b,compile:function(){return{pre:function(a,d,i,h){if(!i.action){var j=function(a){a.preventDefault?a.preventDefault():a.returnValue=!1};ac(d[0],"submit",j);d.bind("$destroy",function(){c(function(){ib(d[0],"submit",j)},0,!1)})}var g=d.parent().controller("form"),m=i.name||i.ngForm;m&&(a[m]=h);g&&d.bind("$destroy",function(){g.$removeControl(h);m&&(a[m]=s);y(h,Va)})}}}};return a?y(W(d),{restrict:"EAC"}):d}]},pd=Ya(),qd=Ya(!0),rd=/^(ftp|http|https):\/\/(\w+:{0,1}\w*@)?(\S+)(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-\/]))?$/,
|
||||
sd=/^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,4}$/,td=/^\s*(\-|\+)?(\d+|(\d*(\.\d*)))\s*$/,cc={text:Xa,number:function(a,c,d,e,f,i){Xa(a,c,d,e,f,i);e.$parsers.push(function(a){var c=U(a);return c||td.test(a)?(e.$setValidity("number",!0),a===""?null:c?a:parseFloat(a)):(e.$setValidity("number",!1),s)});e.$formatters.push(function(a){return U(a)?"":""+a});if(d.min){var h=parseFloat(d.min),a=function(a){return!U(a)&&a<h?(e.$setValidity("min",!1),s):(e.$setValidity("min",!0),a)};e.$parsers.push(a);
|
||||
e.$formatters.push(a)}if(d.max){var j=parseFloat(d.max),d=function(a){return!U(a)&&a>j?(e.$setValidity("max",!1),s):(e.$setValidity("max",!0),a)};e.$parsers.push(d);e.$formatters.push(d)}e.$formatters.push(function(a){return U(a)||Za(a)?(e.$setValidity("number",!0),a):(e.$setValidity("number",!1),s)})},url:function(a,c,d,e,f,i){Xa(a,c,d,e,f,i);a=function(a){return U(a)||rd.test(a)?(e.$setValidity("url",!0),a):(e.$setValidity("url",!1),s)};e.$formatters.push(a);e.$parsers.push(a)},email:function(a,
|
||||
c,d,e,f,i){Xa(a,c,d,e,f,i);a=function(a){return U(a)||sd.test(a)?(e.$setValidity("email",!0),a):(e.$setValidity("email",!1),s)};e.$formatters.push(a);e.$parsers.push(a)},radio:function(a,c,d,e){u(d.name)&&c.attr("name",Ea());c.bind("click",function(){c[0].checked&&a.$apply(function(){e.$setViewValue(d.value)})});e.$render=function(){c[0].checked=d.value==e.$viewValue};d.$observe("value",e.$render)},checkbox:function(a,c,d,e){var f=d.ngTrueValue,i=d.ngFalseValue;x(f)||(f=!0);x(i)||(i=!1);c.bind("click",
|
||||
function(){a.$apply(function(){e.$setViewValue(c[0].checked)})});e.$render=function(){c[0].checked=e.$viewValue};e.$formatters.push(function(a){return a===f});e.$parsers.push(function(a){return a?f:i})},hidden:t,button:t,submit:t,reset:t},dc=["$browser","$sniffer",function(a,c){return{restrict:"E",require:"?ngModel",link:function(d,e,f,i){i&&(cc[J(f.type)]||cc.text)(d,e,f,i,c,a)}}}],Ua="ng-valid",Ta="ng-invalid",oa="ng-pristine",Wa="ng-dirty",ud=["$scope","$exceptionHandler","$attrs","$element","$parse",
|
||||
function(a,c,d,e,f){function i(a,c){c=c?"-"+db(c,"-"):"";e.removeClass((a?Ta:Ua)+c).addClass((a?Ua:Ta)+c)}this.$modelValue=this.$viewValue=Number.NaN;this.$parsers=[];this.$formatters=[];this.$viewChangeListeners=[];this.$pristine=!0;this.$dirty=!1;this.$valid=!0;this.$invalid=!1;this.$name=d.name;var h=f(d.ngModel),j=h.assign;if(!j)throw Error(Ib+d.ngModel+" ("+ta(e)+")");this.$render=t;var g=e.inheritedData("$formController")||Va,m=0,k=this.$error={};e.addClass(oa);i(!0);this.$setValidity=function(a,
|
||||
c){if(k[a]!==!c){if(c){if(k[a]&&m--,!m)i(!0),this.$valid=!0,this.$invalid=!1}else i(!1),this.$invalid=!0,this.$valid=!1,m++;k[a]=!c;i(c,a);g.$setValidity(a,c,this)}};this.$setPristine=function(){this.$dirty=!1;this.$pristine=!0;e.removeClass(Wa).addClass(oa)};this.$setViewValue=function(d){this.$viewValue=d;if(this.$pristine)this.$dirty=!0,this.$pristine=!1,e.removeClass(oa).addClass(Wa),g.$setDirty();o(this.$parsers,function(a){d=a(d)});if(this.$modelValue!==d)this.$modelValue=d,j(a,d),o(this.$viewChangeListeners,
|
||||
function(a){try{a()}catch(d){c(d)}})};var l=this;a.$watch(function(){var c=h(a);if(l.$modelValue!==c){var d=l.$formatters,e=d.length;for(l.$modelValue=c;e--;)c=d[e](c);if(l.$viewValue!==c)l.$viewValue=c,l.$render()}})}],vd=function(){return{require:["ngModel","^?form"],controller:ud,link:function(a,c,d,e){var f=e[0],i=e[1]||Va;i.$addControl(f);c.bind("$destroy",function(){i.$removeControl(f)})}}},wd=Q({require:"ngModel",link:function(a,c,d,e){e.$viewChangeListeners.push(function(){a.$eval(d.ngChange)})}}),
|
||||
ec=function(){return{require:"?ngModel",link:function(a,c,d,e){if(e){d.required=!0;var f=function(a){if(d.required&&(U(a)||a===!1))e.$setValidity("required",!1);else return e.$setValidity("required",!0),a};e.$formatters.push(f);e.$parsers.unshift(f);d.$observe("required",function(){f(e.$viewValue)})}}}},xd=function(){return{require:"ngModel",link:function(a,c,d,e){var f=(a=/\/(.*)\//.exec(d.ngList))&&RegExp(a[1])||d.ngList||",";e.$parsers.push(function(a){var c=[];a&&o(a.split(f),function(a){a&&c.push(S(a))});
|
||||
return c});e.$formatters.push(function(a){return C(a)?a.join(", "):s})}}},yd=/^(true|false|\d+)$/,zd=function(){return{priority:100,compile:function(a,c){return yd.test(c.ngValue)?function(a,c,f){f.$set("value",a.$eval(f.ngValue))}:function(a,c,f){a.$watch(f.ngValue,function(a){f.$set("value",a,!1)})}}}},Ad=Y(function(a,c,d){c.addClass("ng-binding").data("$binding",d.ngBind);a.$watch(d.ngBind,function(a){c.text(a==s?"":a)})}),Bd=["$interpolate",function(a){return function(c,d,e){c=a(d.attr(e.$attr.ngBindTemplate));
|
||||
d.addClass("ng-binding").data("$binding",c);e.$observe("ngBindTemplate",function(a){d.text(a)})}}],Cd=[function(){return function(a,c,d){c.addClass("ng-binding").data("$binding",d.ngBindHtmlUnsafe);a.$watch(d.ngBindHtmlUnsafe,function(a){c.html(a||"")})}}],Dd=pb("",!0),Ed=pb("Odd",0),Fd=pb("Even",1),Gd=Y({compile:function(a,c){c.$set("ngCloak",s);a.removeClass("ng-cloak")}}),Hd=[function(){return{scope:!0,controller:"@"}}],Id=["$sniffer",function(a){return{priority:1E3,compile:function(){a.csp=!0}}}],
|
||||
fc={};o("click dblclick mousedown mouseup mouseover mouseout mousemove mouseenter mouseleave keydown keyup keypress".split(" "),function(a){var c=aa("ng-"+a);fc[c]=["$parse",function(d){return function(e,f,i){var h=d(i[c]);f.bind(J(a),function(a){e.$apply(function(){h(e,{$event:a})})})}}]});var Jd=Y(function(a,c,d){c.bind("submit",function(){a.$apply(d.ngSubmit)})}),Kd=["$http","$templateCache","$anchorScroll","$compile","$animator",function(a,c,d,e,f){return{restrict:"ECA",terminal:!0,compile:function(i,
|
||||
h){var j=h.ngInclude||h.src,g=h.onload||"",m=h.autoscroll;return function(h,i,o){var n=f(h,o),s=0,r,p=function(){r&&(r.$destroy(),r=null);n.leave(i.contents(),i)};h.$watch(j,function(f){var j=++s;f?a.get(f,{cache:c}).success(function(a){j===s&&(r&&r.$destroy(),r=h.$new(),n.leave(i.contents(),i),a=v("<div/>").html(a).contents(),n.enter(a,i),e(a)(r),w(m)&&(!m||h.$eval(m))&&d(),r.$emit("$includeContentLoaded"),h.$eval(g))}).error(function(){j===s&&p()}):p()})}}}}],Ld=Y({compile:function(){return{pre:function(a,
|
||||
c,d){a.$eval(d.ngInit)}}}}),Md=Y({terminal:!0,priority:1E3}),Nd=["$locale","$interpolate",function(a,c){var d=/{}/g;return{restrict:"EA",link:function(e,f,i){var h=i.count,j=f.attr(i.$attr.when),g=i.offset||0,m=e.$eval(j),k={},l=c.startSymbol(),q=c.endSymbol();o(m,function(a,e){k[e]=c(a.replace(d,l+h+"-"+g+q))});e.$watch(function(){var c=parseFloat(e.$eval(h));return isNaN(c)?"":(m[c]||(c=a.pluralCat(c-g)),k[c](e,f,!0))},function(a){f.text(a)})}}}],Od=["$parse","$animator",function(a,c){return{transclude:"element",
|
||||
priority:1E3,terminal:!0,compile:function(d,e,f){return function(d,e,j){var g=c(d,j),m=j.ngRepeat,k=m.match(/^\s*(.+)\s+in\s+(.*?)\s*(\s+track\s+by\s+(.+)\s*)?$/),l,q,n,s,r,p={$id:la};if(!k)throw Error("Expected ngRepeat in form of '_item_ in _collection_[ track by _id_]' but got '"+m+"'.");j=k[1];n=k[2];(k=k[4])?(l=a(k),q=function(a,c,e){r&&(p[r]=a);p[s]=c;p.$index=e;return l(d,p)}):q=function(a,c){return la(c)};k=j.match(/^(?:([\$\w]+)|\(([\$\w]+)\s*,\s*([\$\w]+)\))$/);if(!k)throw Error("'item' in 'item in collection' should be identifier or (key, value) but got '"+
|
||||
j+"'.");s=k[3]||k[1];r=k[2];var y={};d.$watchCollection(n,function(a){var c,j,k=e,l,n={},p,t,v,z,w,u,x=[];if(C(a))w=a;else{w=[];for(v in a)a.hasOwnProperty(v)&&v.charAt(0)!="$"&&w.push(v);w.sort()}p=w.length;j=x.length=w.length;for(c=0;c<j;c++)if(v=a===w?c:w[c],z=a[v],l=q(v,z,c),u=y[l])delete y[l],n[l]=u,x[c]=u;else if(n.hasOwnProperty(l))throw o(x,function(a){a&&a.element&&(y[a.id]=a)}),Error("Duplicates in a repeater are not allowed. Repeater: "+m);else x[c]={id:l};for(v in y)if(y.hasOwnProperty(v))u=
|
||||
y[v],g.leave(u.element),u.element[0].$$NG_REMOVED=!0,u.scope.$destroy();c=0;for(j=w.length;c<j;c++){v=a===w?c:w[c];z=a[v];u=x[c];if(u.element){t=u.scope;l=k[0];do l=l.nextSibling;while(l&&l.$$NG_REMOVED);u.element[0]!=l&&g.move(u.element,null,k);k=u.element}else t=d.$new();t[s]=z;r&&(t[r]=v);t.$index=c;t.$first=c===0;t.$last=c===p-1;t.$middle=!(t.$first||t.$last);u.element||f(t,function(a){g.enter(a,null,k);k=a;u.scope=t;u.element=a;n[u.id]=u})}y=n})}}}}],Pd=["$animator",function(a){return function(c,
|
||||
d,e){var f=a(c,e);c.$watch(e.ngShow,function(a){f[Ha(a)?"show":"hide"](d)})}}],Qd=["$animator",function(a){return function(c,d,e){var f=a(c,e);c.$watch(e.ngHide,function(a){f[Ha(a)?"hide":"show"](d)})}}],Rd=Y(function(a,c,d){a.$watch(d.ngStyle,function(a,d){d&&a!==d&&o(d,function(a,d){c.css(d,"")});a&&c.css(a)},!0)}),Sd=["$animator",function(a){return{restrict:"EA",require:"ngSwitch",controller:["$scope",function(){this.cases={}}],link:function(c,d,e,f){var i=a(c,e),h,j,g=[];c.$watch(e.ngSwitch||
|
||||
e.on,function(a){for(var d=0,l=g.length;d<l;d++)g[d].$destroy(),i.leave(j[d]);j=[];g=[];if(h=f.cases["!"+a]||f.cases["?"])c.$eval(e.change),o(h,function(a){var d=c.$new();g.push(d);a.transclude(d,function(c){var d=a.element;j.push(c);i.enter(c,d.parent(),d)})})})}}}],Td=Y({transclude:"element",priority:500,require:"^ngSwitch",compile:function(a,c,d){return function(a,f,i,h){h.cases["!"+c.ngSwitchWhen]=h.cases["!"+c.ngSwitchWhen]||[];h.cases["!"+c.ngSwitchWhen].push({transclude:d,element:f})}}}),Ud=
|
||||
Y({transclude:"element",priority:500,require:"^ngSwitch",compile:function(a,c,d){return function(a,c,i,h){h.cases["?"]=h.cases["?"]||[];h.cases["?"].push({transclude:d,element:c})}}}),Vd=Y({controller:["$transclude","$element",function(a,c){a(function(a){c.append(a)})}]}),Wd=["$http","$templateCache","$route","$anchorScroll","$compile","$controller","$animator",function(a,c,d,e,f,i,h){return{restrict:"ECA",terminal:!0,link:function(a,c,m){function k(){var h=d.current&&d.current.locals,k=h&&h.$template;
|
||||
if(k){n.leave(c.contents(),c);l&&(l.$destroy(),l=null);n.enter(v("<div></div>").html(k).contents(),c);var k=f(c.contents()),m=d.current;l=m.scope=a.$new();if(m.controller)h.$scope=l,h=i(m.controller,h),c.children().data("$ngControllerController",h);k(l);l.$emit("$viewContentLoaded");l.$eval(o);e()}else n.leave(c.contents(),c),l&&(l.$destroy(),l=null)}var l,o=m.onload||"",n=h(a,m);a.$on("$routeChangeSuccess",k);k()}}}],Xd=["$templateCache",function(a){return{restrict:"E",terminal:!0,compile:function(c,
|
||||
d){d.type=="text/ng-template"&&a.put(d.id,c[0].text)}}}],Yd=Q({terminal:!0}),Zd=["$compile","$parse",function(a,c){var d=/^\s*(.*?)(?:\s+as\s+(.*?))?(?:\s+group\s+by\s+(.*))?\s+for\s+(?:([\$\w][\$\w\d]*)|(?:\(\s*([\$\w][\$\w\d]*)\s*,\s*([\$\w][\$\w\d]*)\s*\)))\s+in\s+(.*)$/,e={$setViewValue:t};return{restrict:"E",require:["select","?ngModel"],controller:["$element","$scope","$attrs",function(a,c,d){var j=this,g={},m=e,k;j.databound=d.ngModel;j.init=function(a,c,d){m=a;k=d};j.addOption=function(c){g[c]=
|
||||
!0;m.$viewValue==c&&(a.val(c),k.parent()&&k.remove())};j.removeOption=function(a){this.hasOption(a)&&(delete g[a],m.$viewValue==a&&this.renderUnknownOption(a))};j.renderUnknownOption=function(c){c="? "+la(c)+" ?";k.val(c);a.prepend(k);a.val(c);k.prop("selected",!0)};j.hasOption=function(a){return g.hasOwnProperty(a)};c.$on("$destroy",function(){j.renderUnknownOption=t})}],link:function(e,i,h,j){function g(a,c,d,e){d.$render=function(){var a=d.$viewValue;e.hasOption(a)?(x.parent()&&x.remove(),c.val(a),
|
||||
a===""&&p.prop("selected",!0)):u(a)&&p?c.val(""):e.renderUnknownOption(a)};c.bind("change",function(){a.$apply(function(){x.parent()&&x.remove();d.$setViewValue(c.val())})})}function m(a,c,d){var e;d.$render=function(){var a=new Pa(d.$viewValue);o(c.find("option"),function(c){c.selected=w(a.get(c.value))})};a.$watch(function(){ja(e,d.$viewValue)||(e=W(d.$viewValue),d.$render())});c.bind("change",function(){a.$apply(function(){var a=[];o(c.find("option"),function(c){c.selected&&a.push(c.value)});d.$setViewValue(a)})})}
|
||||
function k(e,f,h){function g(){var a={"":[]},c=[""],d,i,t,v,u;t=h.$modelValue;v=p(e)||[];var w=l?rb(v):v,z,x,A;x={};u=!1;var B,C;if(n)u=new Pa(t);else if(t===null||r)a[""].push({selected:t===null,id:"",label:""}),u=!0;for(A=0;z=w.length,A<z;A++){x[k]=v[l?x[l]=w[A]:A];d=m(e,x)||"";if(!(i=a[d]))i=a[d]=[],c.push(d);n?d=u.remove(o(e,x))!=s:(d=t===o(e,x),u=u||d);B=j(e,x);B=B===s?"":B;i.push({id:l?w[A]:A,label:B,selected:d})}!n&&!u&&a[""].unshift({id:"?",label:"",selected:!0});x=0;for(w=c.length;x<w;x++){d=
|
||||
c[x];i=a[d];if(q.length<=x)t={element:D.clone().attr("label",d),label:i.label},v=[t],q.push(v),f.append(t.element);else if(v=q[x],t=v[0],t.label!=d)t.element.attr("label",t.label=d);B=null;A=0;for(z=i.length;A<z;A++)if(d=i[A],u=v[A+1]){B=u.element;if(u.label!==d.label)B.text(u.label=d.label);if(u.id!==d.id)B.val(u.id=d.id);if(u.element.selected!==d.selected)B.prop("selected",u.selected=d.selected)}else d.id===""&&r?C=r:(C=y.clone()).val(d.id).attr("selected",d.selected).text(d.label),v.push({element:C,
|
||||
label:d.label,id:d.id,selected:d.selected}),B?B.after(C):t.element.append(C),B=C;for(A++;v.length>A;)v.pop().element.remove()}for(;q.length>x;)q.pop()[0].element.remove()}var i;if(!(i=t.match(d)))throw Error("Expected ngOptions in form of '_select_ (as _label_)? for (_key_,)?_value_ in _collection_' but got '"+t+"'.");var j=c(i[2]||i[1]),k=i[4]||i[6],l=i[5],m=c(i[3]||""),o=c(i[2]?i[1]:k),p=c(i[7]),q=[[{element:f,label:""}]];r&&(a(r)(e),r.removeClass("ng-scope"),r.remove());f.html("");f.bind("change",
|
||||
function(){e.$apply(function(){var a,c=p(e)||[],d={},g,i,j,m,r,t;if(n){i=[];m=0;for(t=q.length;m<t;m++){a=q[m];j=1;for(r=a.length;j<r;j++)if((g=a[j].element)[0].selected)g=g.val(),l&&(d[l]=g),d[k]=c[g],i.push(o(e,d))}}else g=f.val(),g=="?"?i=s:g==""?i=null:(d[k]=c[g],l&&(d[l]=g),i=o(e,d));h.$setViewValue(i)})});h.$render=g;e.$watch(g)}if(j[1]){for(var l=j[0],q=j[1],n=h.multiple,t=h.ngOptions,r=!1,p,y=v(V.createElement("option")),D=v(V.createElement("optgroup")),x=y.clone(),j=0,C=i.children(),A=C.length;j<
|
||||
A;j++)if(C[j].value==""){p=r=C.eq(j);break}l.init(q,r,x);if(n&&(h.required||h.ngRequired)){var H=function(a){q.$setValidity("required",!h.required||a&&a.length);return a};q.$parsers.push(H);q.$formatters.unshift(H);h.$observe("required",function(){H(q.$viewValue)})}t?k(e,i,q):n?m(e,i,q):g(e,i,q,l)}}}}],$d=["$interpolate",function(a){var c={addOption:t,removeOption:t};return{restrict:"E",priority:100,compile:function(d,e){if(u(e.value)){var f=a(d.text(),!0);f||e.$set("value",d.text())}return function(a,
|
||||
d,e){var g=d.parent(),m=g.data("$selectController")||g.parent().data("$selectController");m&&m.databound?d.prop("selected",!1):m=c;f?a.$watch(f,function(a,c){e.$set("value",a);a!==c&&m.removeOption(c);m.addOption(a)}):m.addOption(e.value);d.bind("$destroy",function(){m.removeOption(e.value)})}}}}],ae=Q({restrict:"E",terminal:!0});(ca=M.jQuery)?(v=ca,y(ca.fn,{scope:za.scope,controller:za.controller,injector:za.injector,inheritedData:za.inheritedData}),fb("remove",!0),fb("empty"),fb("html")):v=P;Ia.element=
|
||||
v;(function(a){y(a,{bootstrap:vb,copy:W,extend:y,equals:ja,element:v,forEach:o,injector:wb,noop:t,bind:ab,toJson:da,fromJson:tb,identity:pa,isUndefined:u,isDefined:w,isString:x,isFunction:I,isObject:L,isNumber:Za,isElement:jc,isArray:C,version:md,isDate:qa,lowercase:J,uppercase:na,callbacks:{counter:0},noConflict:gc});xa=oc(M);try{xa("ngLocale")}catch(c){xa("ngLocale",[]).provider("$locale",cd)}xa("ng",["ngLocale"],["$provide",function(a){a.provider("$compile",Hb).directive({a:od,input:dc,textarea:dc,
|
||||
form:pd,script:Xd,select:Zd,style:ae,option:$d,ngBind:Ad,ngBindHtmlUnsafe:Cd,ngBindTemplate:Bd,ngClass:Dd,ngClassEven:Fd,ngClassOdd:Ed,ngCsp:Id,ngCloak:Gd,ngController:Hd,ngForm:qd,ngHide:Qd,ngInclude:Kd,ngInit:Ld,ngNonBindable:Md,ngPluralize:Nd,ngRepeat:Od,ngShow:Pd,ngSubmit:Jd,ngStyle:Rd,ngSwitch:Sd,ngSwitchWhen:Td,ngSwitchDefault:Ud,ngOptions:Yd,ngView:Wd,ngTransclude:Vd,ngModel:vd,ngList:xd,ngChange:wd,required:ec,ngRequired:ec,ngValue:zd}).directive(qb).directive(fc);a.provider({$anchorScroll:xc,
|
||||
$animation:Gb,$animator:nd,$browser:zc,$cacheFactory:Ac,$controller:Dc,$document:Ec,$exceptionHandler:Fc,$filter:Tb,$interpolate:Gc,$http:Zc,$httpBackend:$c,$location:Kc,$log:Lc,$parse:Pc,$route:Sc,$routeParams:Tc,$rootScope:Uc,$q:Qc,$sniffer:Vc,$templateCache:Bc,$timeout:dd,$window:Wc})}])})(Ia);v(V).ready(function(){mc(V,vb)})})(window,document);angular.element(document).find("head").append('<style type="text/css">@charset "UTF-8";[ng\\:cloak],[ng-cloak],[data-ng-cloak],[x-ng-cloak],.ng-cloak,.x-ng-cloak{display:none;}ng\\:form{display:block;}</style>');
|
|
@ -0,0 +1,11 @@
|
|||
/*
|
||||
AngularJS v1.1.4
|
||||
(c) 2010-2012 Google, Inc. http://angularjs.org
|
||||
License: MIT
|
||||
*/
|
||||
(function(B,f,x){'use strict';f.module("ngResource",["ng"]).factory("$resource",["$http","$parse",function(y,z){function v(g,c){this.template=g+"#";this.defaults=c||{};this.urlParams={}}function w(g,c,d){function j(e,b){var p={},b=q({},c,b);k(b,function(a,b){l(a)&&(a=a());var h;a.charAt&&a.charAt(0)=="@"?(h=a.substr(1),h=z(h)(e)):h=a;p[b]=h});return p}function b(b){u(b||{},this)}var m=new v(g),d=q({},A,d);k(d,function(e,c){e.method=f.uppercase(e.method);var p=e.method=="POST"||e.method=="PUT"||e.method==
|
||||
"PATCH";b[c]=function(a,c,h,g){function f(){i.$resolved=!0}var n={},d,o=r,s=null;switch(arguments.length){case 4:s=g,o=h;case 3:case 2:if(l(c)){if(l(a)){o=a;s=c;break}o=c;s=h}else{n=a;d=c;o=h;break}case 1:l(a)?o=a:p?d=a:n=a;break;case 0:break;default:throw"Expected between 0-4 arguments [params, data, success, error], got "+arguments.length+" arguments.";}var i=this instanceof b?this:e.isArray?[]:new b(d),t={};k(e,function(a,b){b!="params"&&b!="isArray"&&(t[b]=u(a))});t.data=d;m.setUrlParams(t,q({},
|
||||
j(d,e.params||{}),n),e.url);n=y(t);i.$resolved=!1;n.then(f,f);i.$then=n.then(function(a){var c=a.data,h=i.$then,d=i.$resolved;if(c)e.isArray?(i.length=0,k(c,function(a){i.push(new b(a))})):(u(c,i),i.$then=h,i.$resolved=d);(o||r)(i,a.headers);a.resource=i;return a},s).then;return i};b.prototype["$"+c]=function(a,e,h){var d=j(this),f=r,g;switch(arguments.length){case 3:d=a;f=e;g=h;break;case 2:case 1:l(a)?(f=a,g=e):(d=a,f=e||r);case 0:break;default:throw"Expected between 1-3 arguments [params, success, error], got "+
|
||||
arguments.length+" arguments.";}b[c].call(this,d,p?this:x,f,g)}});b.bind=function(b){return w(g,q({},c,b),d)};return b}var A={get:{method:"GET"},save:{method:"POST"},query:{method:"GET",isArray:!0},remove:{method:"DELETE"},"delete":{method:"DELETE"}},r=f.noop,k=f.forEach,q=f.extend,u=f.copy,l=f.isFunction;v.prototype={setUrlParams:function(g,c,d){var j=this,b=d||j.template,m,e,l=j.urlParams={};k(b.split(/\W/),function(c){c&&RegExp("(^|[^\\\\]):"+c+"(\\W|$)").test(b)&&(l[c]=!0)});b=b.replace(/\\:/g,
|
||||
":");c=c||{};k(j.urlParams,function(d,a){m=c.hasOwnProperty(a)?c[a]:j.defaults[a];f.isDefined(m)&&m!==null?(e=encodeURIComponent(m).replace(/%40/gi,"@").replace(/%3A/gi,":").replace(/%24/g,"$").replace(/%2C/gi,",").replace(/%20/g,"%20").replace(/%26/gi,"&").replace(/%3D/gi,"=").replace(/%2B/gi,"+"),b=b.replace(RegExp(":"+a+"(\\W|$)","g"),e+"$1")):b=b.replace(RegExp("(/?):"+a+"(\\W|$)","g"),function(a,b,c){return c.charAt(0)=="/"?c:b+c})});g.url=b.replace(/\/?#$/,"").replace(/\/*$/,"");k(c,function(b,
|
||||
a){if(!j.urlParams[a])g.params=g.params||{},g.params[a]=b})}};return w}])})(window,window.angular);
|
|
@ -0,0 +1 @@
|
|||
var dashboardApp = angular.module('dashboardApp', []);
|
|
@ -0,0 +1,197 @@
|
|||
var dashboardApp = angular.module('dashboardApp', []);
|
||||
|
||||
var parsedData = null;
|
||||
var temp = {};
|
||||
var dataArray= [];
|
||||
|
||||
|
||||
|
||||
dashboardApp.controller('TimeLineController', function($scope, $http) {
|
||||
$scope.properties = [];
|
||||
|
||||
$scope.fetchData = function() {
|
||||
$http.get('/data/querystableinstances').success(
|
||||
function(resp, status, headers, config) {
|
||||
$scope.properties = resp;
|
||||
parsedData = resp;
|
||||
drawVisualization('color');
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
$scope.fetchData();
|
||||
});
|
||||
|
||||
//TimeLineController.$inject = ['$scope', '$http'];
|
||||
|
||||
|
||||
function drawVisualization(name) {
|
||||
var data = new google.visualization.DataTable();
|
||||
|
||||
/*Reformat the data
|
||||
|
||||
// TODO use bucket_ids instead of property_name in comparisons.
|
||||
|
||||
// Beginning structure
|
||||
parsedData = [{
|
||||
date: obj,
|
||||
property_name: string,
|
||||
day_percentage: float,
|
||||
bucket_id: int
|
||||
}...
|
||||
]
|
||||
|
||||
// Intermediate structure
|
||||
temp = {
|
||||
date1: {
|
||||
property_name1: hits1
|
||||
property_name2: hits2
|
||||
}
|
||||
}
|
||||
|
||||
// Final structure
|
||||
[
|
||||
[date1, hits1, hits2]
|
||||
[date2, hits3, hits4]
|
||||
]*/
|
||||
|
||||
// Build structure of dates
|
||||
for (var i = 0, item; item = parsedData[i]; ++i) {
|
||||
var date = item.date;
|
||||
if (!temp[date]) {
|
||||
temp[date] = {};
|
||||
}
|
||||
temp[date][item.property_name] = item.day_percentage;
|
||||
}
|
||||
|
||||
var dates = [];
|
||||
for (var date in temp) {
|
||||
dates.push(temp[date]);
|
||||
}
|
||||
|
||||
console.log(dates)
|
||||
|
||||
data.addColumn('date', 'Date');
|
||||
|
||||
var namesArray = getNamesArray(name);
|
||||
|
||||
for (var property_name in dates[0]) {
|
||||
if (namesArray.indexOf(property_name) != -1) {
|
||||
data.addColumn('number', property_name);
|
||||
}
|
||||
}
|
||||
|
||||
var rowArray = [];
|
||||
for (var date in temp) {
|
||||
var r = [];
|
||||
var d = temp[date];
|
||||
var fullDate = new Date((parseInt(date) * 1000));
|
||||
r.push(new Date(fullDate.getYear(), fullDate.getMonth(), fullDate.getDay()));
|
||||
|
||||
for (var property_name in d) {
|
||||
if (namesArray.indexOf(property_name) != -1) {
|
||||
r.push(d[property_name]);
|
||||
}
|
||||
}
|
||||
rowArray.push(r);
|
||||
}
|
||||
|
||||
console.log(rowArray)
|
||||
|
||||
data.addRows(rowArray);
|
||||
|
||||
var options = {
|
||||
vAxis: {title: 'Percentage'},
|
||||
hAxis: {title: 'Time Range'},
|
||||
width: 900,
|
||||
height: 500,
|
||||
chartArea: {width: '50%'},
|
||||
};
|
||||
|
||||
var chart = new google.visualization.LineChart(document.querySelector('#chart'));
|
||||
chart.draw(data, options);
|
||||
}
|
||||
|
||||
function goToChart(id) {
|
||||
var node = document.getElementById(id);
|
||||
|
||||
if (node && node.tagName == "SELECT") {
|
||||
var selectedValue = node.options[node.selectedIndex].innerText;
|
||||
drawVisualization(selectedValue);
|
||||
}
|
||||
}
|
||||
|
||||
function getNamesArray(name) {
|
||||
var namesArray = [name];
|
||||
|
||||
if (name.substr(0, 7) === 'webkit-') {
|
||||
var nameToCheck = name.substr(7);
|
||||
var index = CSS_PROPERTIES_LIST.indexOf(nameToCheck);
|
||||
if (index != -1) {
|
||||
namesArray.push(CSS_PROPERTIES_LIST[index]);
|
||||
}
|
||||
} else {
|
||||
var nameToCheck = 'webkit-' + name;
|
||||
var index = CSS_PROPERTIES_LIST.indexOf(nameToCheck);
|
||||
if (index != -1) {
|
||||
namesArray.push(CSS_PROPERTIES_LIST[index]);
|
||||
}
|
||||
}
|
||||
|
||||
return namesArray;
|
||||
}
|
||||
|
||||
|
||||
var CSS_PROPERTIES_LIST = [];
|
||||
|
||||
function init() {
|
||||
var options = document.querySelector("#property-selector").options;
|
||||
for (var i = 0, opt; opt = options[i]; ++i) {
|
||||
CSS_PROPERTIES_LIST.push(options[i].textContent);
|
||||
}
|
||||
}
|
||||
|
||||
init();
|
||||
|
||||
|
||||
// (function(exports) {
|
||||
|
||||
// var allProperties = [];
|
||||
// var temp = {};
|
||||
// var dataArray= [];
|
||||
|
||||
// google.load('visualization', '1.0', {packages:['corechart']});
|
||||
// //google.setOnLoadCallback(visualizationReady);
|
||||
|
||||
// function getData() {
|
||||
// var xhr = new XMLHttpRequest();
|
||||
// xhr.open('GET', '/data/querystableinstances');
|
||||
|
||||
// xhr.onloadend = function(e) {
|
||||
// if (this.status == 200) {
|
||||
// parsedData = JSON.parse(this.response);
|
||||
|
||||
// console.log(parsedData);
|
||||
|
||||
|
||||
// // Sort by date.
|
||||
// parsedData.sort(function(obj1, obj2) {
|
||||
// return obj1.date.epoch - obj2.date.epoch;
|
||||
// });
|
||||
|
||||
// console.log(parsedData)
|
||||
|
||||
// drawVisualization('color');
|
||||
// }
|
||||
// };
|
||||
|
||||
// xhr.send();
|
||||
// }
|
||||
|
||||
|
||||
|
||||
// getData();
|
||||
|
||||
// exports.goToChart = goToChart;
|
||||
|
||||
// })(window);
|
|
@ -0,0 +1,26 @@
|
|||
(function() {
|
||||
|
||||
var parsedData;
|
||||
var propertyOrder = 'greatestToLeast';
|
||||
|
||||
function getData() {
|
||||
var xhr = new XMLHttpRequest();
|
||||
xhr.open('GET', '/data/querystackrank');
|
||||
|
||||
xhr.onloadend = function(e) {
|
||||
if (this.status == 200) {
|
||||
parsedData = JSON.parse(this.responseText);
|
||||
writeProperties();
|
||||
}
|
||||
}
|
||||
|
||||
xhr.send();
|
||||
}
|
||||
|
||||
function writeProperties() {
|
||||
|
||||
}
|
||||
|
||||
getData();
|
||||
|
||||
})();
|
|
@ -0,0 +1,30 @@
|
|||
var dashboardApp = angular.module('dashboardApp', []);
|
||||
|
||||
dashboardApp.controller('StackRankController', function($scope, $http) {
|
||||
$scope.properties = [];
|
||||
$scope.sortBy = '-day_percentage';
|
||||
|
||||
var desc = true;
|
||||
|
||||
$scope.fetchData = function() {
|
||||
$http.get('/data/querystackrank').success(
|
||||
function(resp, status, headers, config) {
|
||||
// Filter out bad results.
|
||||
$scope.properties = resp.filter(function(el, i) {
|
||||
return el.property_name != 'ERROR' && el.bucket_id != 1;
|
||||
});
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
$scope.sortList = function() {
|
||||
desc = !desc;
|
||||
$scope.sortBy = (desc ? '-' : '') + 'day_percentage';
|
||||
};
|
||||
|
||||
$scope.fetchData();
|
||||
});
|
||||
|
||||
//StackRankController.$inject = ['$scope', '$http'];
|
||||
|
||||
|
|
@ -4,131 +4,15 @@
|
|||
//@import "compass/css3/text-shadow";
|
||||
//@import "compass/css3/background-size";
|
||||
|
||||
$default-font-color: #666666;
|
||||
$light-grey: #eee;
|
||||
|
||||
@mixin display-flex() {
|
||||
display: -webkit-flex;
|
||||
display: -moz-flex;
|
||||
display: -ms-flex;
|
||||
display: -o-flex;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
@mixin flex($val) {
|
||||
-webkit-flex: $val;
|
||||
-moz-flex: $val;
|
||||
-ms-flex: $val;
|
||||
-o-flex: $val;
|
||||
flex: $val;
|
||||
}
|
||||
|
||||
@mixin justify-content($val: center) {
|
||||
-webkit-justify-content: $val;
|
||||
-moz-justify-content: $val;
|
||||
-ms-justify-content: $val;
|
||||
-o-justify-content: $val;
|
||||
justify-content: $val;
|
||||
}
|
||||
|
||||
@mixin align-items($val: center) {
|
||||
-webkit-align-items: $val;
|
||||
-moz-align-items: $val;
|
||||
-ms-align-items: $val;
|
||||
-o-align-items: $val;
|
||||
align-items: $val;
|
||||
}
|
||||
|
||||
@mixin align-self($val: center) {
|
||||
-webkit-align-self: $val;
|
||||
-moz-align-self: $val;
|
||||
-ms-align-self: $val;
|
||||
-o-align-self: $val;
|
||||
align-self: $val;
|
||||
}
|
||||
|
||||
@mixin flex-direction($val) {
|
||||
-webkit-flex-direction: $val;
|
||||
-moz-flex-direction: $val;
|
||||
-ms-flex-direction: $val;
|
||||
-o-flex-direction: $val;
|
||||
flex-direction: $val;
|
||||
}
|
||||
|
||||
@mixin flex-wrap($val: nowrap) {
|
||||
-webkit-flex-wrap: $val;
|
||||
-moz-flex-wrap: $val;
|
||||
-ms-flex-wrap: $val;
|
||||
-o-flex-wrap: $val;
|
||||
flex-wrap: $val;
|
||||
}
|
||||
|
||||
// Old flexbox.
|
||||
@mixin display-box() {
|
||||
display: -webkit-box;
|
||||
display: -moz-box;
|
||||
display: -ms-box;
|
||||
display: -o-box;
|
||||
display: box;
|
||||
}
|
||||
|
||||
@mixin box-orient($val) {
|
||||
-webkit-box-orient: $val;
|
||||
-moz-box-orient: $val;
|
||||
-ms-box-orient: $val;
|
||||
-o-box-orient: $val;
|
||||
box-orient: $val;
|
||||
}
|
||||
|
||||
@mixin box-flex($val) {
|
||||
-webkit-box-flex: $val;
|
||||
-moz-box-flex: $val;
|
||||
-ms-box-flex: $val;
|
||||
-o-box-flex: $val;
|
||||
box-flex: $val;
|
||||
}
|
||||
|
||||
@mixin box-pack($val) {
|
||||
-webkit-box-pack: $val;
|
||||
-moz-box-pack: $val;
|
||||
-ms-box-pack: $val;
|
||||
-o-box-pack: $val;
|
||||
box-pack: $val;
|
||||
}
|
||||
|
||||
@mixin width-max-content() {
|
||||
width: -webkit-max-content;
|
||||
width: -moz-max-content;
|
||||
width: -ms-max-content;
|
||||
width: -o-max-content;
|
||||
width: max-content;
|
||||
}
|
||||
|
||||
@mixin user-select() {
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-o-user-select: none;
|
||||
-ms-user-select: none;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
@mixin calc-width($val) {
|
||||
width: -webkit-calc($val);
|
||||
width: -moz-calc($val);
|
||||
width: -ms-calc($val);
|
||||
width: -o-calc($val);
|
||||
width: calc($val);
|
||||
}
|
||||
@import "vars";
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
a {
|
||||
color: navy;
|
||||
text-decoration: none;
|
||||
}
|
||||
a:hover {
|
||||
color: navy;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
|
|
@ -1,15 +1,6 @@
|
|||
@import "base";
|
||||
@import "compass/css3/images";
|
||||
|
||||
$bar-shadow-color: rgba(0, 0, 0, .065);
|
||||
$bar-border-color: #D4D4D4;
|
||||
$bar-gradient: linear-gradient(top, white, #F2F2F2);
|
||||
|
||||
$chromium-color-dark: #366597;
|
||||
$chromium-color-medium: #85b4df;
|
||||
$chromium-color-light: #bdd6ed;
|
||||
$chromium-color-center: #4580c0;
|
||||
|
||||
html, body {
|
||||
height: 100%;
|
||||
}
|
||||
|
@ -31,6 +22,7 @@ a {
|
|||
color: $chromium-color-center;
|
||||
}
|
||||
a:hover {
|
||||
color: $chromium-color-dark;
|
||||
text-decoration: none
|
||||
}
|
||||
b {
|
||||
|
@ -51,18 +43,18 @@ header, footer {
|
|||
}
|
||||
|
||||
header {
|
||||
margin: 15px 0 30px 0;
|
||||
margin: 15px 0 0 0;
|
||||
@include user-select;
|
||||
|
||||
aside {
|
||||
//@include display-box; //@include display-flex;
|
||||
@include align-items(center);
|
||||
// @include gradient-bar-bg;
|
||||
@include gradient-bar-bg;
|
||||
border-top-right-radius: 5px;
|
||||
border-bottom-right-radius: 5px;
|
||||
/* Uncomment gradient-bar-bg and these 2 to make pretty again */
|
||||
//border-bottom: 1px solid $bar-border-color;
|
||||
//border-right: 1px solid $bar-border-color;
|
||||
border-bottom: 1px solid $bar-border-color;
|
||||
border-right: 1px solid $bar-border-color;
|
||||
background-color: #FAFAFA;
|
||||
padding: 0.75em 1em;
|
||||
|
||||
|
@ -95,7 +87,8 @@ header {
|
|||
|
||||
nav {
|
||||
@include display-box; //@include display-flex;
|
||||
width: 400px;
|
||||
@include box-align(center); // TODO: remove. Needed because Safari and FF don't have new flexbox.
|
||||
//width: 400px;
|
||||
margin-left: 3em;
|
||||
|
||||
a {
|
||||
|
@ -108,6 +101,8 @@ header {
|
|||
border-radius: 3px;
|
||||
border-bottom: 1px solid $bar-border-color;
|
||||
border-right: 1px solid $bar-border-color;
|
||||
padding-left: 3em;
|
||||
text-transform: uppercase;
|
||||
|
||||
&:hover {
|
||||
color: $chromium-color-center;
|
||||
|
@ -146,7 +141,7 @@ header {
|
|||
color: black;
|
||||
@include align-self(center);
|
||||
border-radius: 3px;
|
||||
margin: 0 30px;
|
||||
margin: 0 30px 30px 30px;
|
||||
}
|
||||
|
||||
footer {
|
||||
|
@ -179,8 +174,10 @@ footer {
|
|||
@include display-box; //@include display-flex;
|
||||
@include justify-content(center);
|
||||
@include flex(1 0 0);
|
||||
@include box-orient(vertical);
|
||||
|
||||
margin: 30px;
|
||||
overflow: auto;
|
||||
|
||||
@include box-flex(1); // TODO: remove. Needed because Safari and FF don't have new flexbox.
|
||||
|
||||
|
@ -237,9 +234,24 @@ footer {
|
|||
}
|
||||
|
||||
|
||||
|
||||
@media only screen and (max-width: 700px) {
|
||||
#content { margin: 0; }
|
||||
.warning { margin: 0; }
|
||||
header { margin: 0; }
|
||||
.warning {
|
||||
margin: 15px 0 0 0;
|
||||
}
|
||||
header {
|
||||
margin: 0;
|
||||
display: block;
|
||||
aside {
|
||||
padding-left: 100px;
|
||||
}
|
||||
nav {
|
||||
margin: 10px 0;
|
||||
@include box-pack(center);
|
||||
a {
|
||||
display: inline-block;
|
||||
padding: 5px 10px 5px 40px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,73 @@
|
|||
@import "compass/css3/images";
|
||||
|
||||
@import "vars";
|
||||
|
||||
.line_chart {
|
||||
height: 500px;
|
||||
width: 900px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
#dashboard-table {
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.metric-nav {
|
||||
//border-bottom: 1px solid $bar-border-color;
|
||||
//background: $chromium-color-light;
|
||||
border-bottom: 2px solid $chromium-color-center;
|
||||
text-align: center;
|
||||
|
||||
li {
|
||||
display: inline-block;
|
||||
border-top-left-radius: 3px;
|
||||
border-top-right-radius: 3px;
|
||||
@include background($bar-gradient);
|
||||
box-shadow: 1px 1px 4px $bar-shadow-color;
|
||||
padding: 0.5em;
|
||||
margin: 0 5px;
|
||||
}
|
||||
}
|
||||
|
||||
#content section {
|
||||
padding-top: 25px;
|
||||
|
||||
h1 {
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
p {
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
#stack-rank-list {
|
||||
margin: 1em 0;
|
||||
|
||||
li {
|
||||
padding: 5px 0;
|
||||
|
||||
// &:nth-child(even):hover {
|
||||
// background-color: #ccc;
|
||||
// }
|
||||
|
||||
> span {
|
||||
display: inline-block;
|
||||
|
||||
&:first-of-type {
|
||||
width: 110px;
|
||||
margin-right: 10px;
|
||||
text-align: right;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 700px) {
|
||||
.metric-nav {
|
||||
text-align: center;
|
||||
margin-top: 15px;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,132 @@
|
|||
$default-font-color: #666666;
|
||||
$light-grey: #eee;
|
||||
|
||||
$bar-shadow-color: rgba(0, 0, 0, .065);
|
||||
$bar-border-color: #D4D4D4;
|
||||
$bar-gradient: linear-gradient(top, white, #F2F2F2);
|
||||
|
||||
$chromium-color-dark: #366597;
|
||||
$chromium-color-medium: #85b4df;
|
||||
$chromium-color-light: #bdd6ed;
|
||||
$chromium-color-center: #4580c0;
|
||||
|
||||
@mixin display-flex() {
|
||||
display: -webkit-flex;
|
||||
display: -moz-flex;
|
||||
display: -ms-flex;
|
||||
display: -o-flex;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
@mixin flex($val) {
|
||||
-webkit-flex: $val;
|
||||
-moz-flex: $val;
|
||||
-ms-flex: $val;
|
||||
-o-flex: $val;
|
||||
flex: $val;
|
||||
}
|
||||
|
||||
@mixin justify-content($val: center) {
|
||||
-webkit-justify-content: $val;
|
||||
-moz-justify-content: $val;
|
||||
-ms-justify-content: $val;
|
||||
-o-justify-content: $val;
|
||||
justify-content: $val;
|
||||
}
|
||||
|
||||
@mixin align-items($val: center) {
|
||||
-webkit-align-items: $val;
|
||||
-moz-align-items: $val;
|
||||
-ms-align-items: $val;
|
||||
-o-align-items: $val;
|
||||
align-items: $val;
|
||||
}
|
||||
|
||||
@mixin align-self($val: center) {
|
||||
-webkit-align-self: $val;
|
||||
-moz-align-self: $val;
|
||||
-ms-align-self: $val;
|
||||
-o-align-self: $val;
|
||||
align-self: $val;
|
||||
}
|
||||
|
||||
@mixin flex-direction($val) {
|
||||
-webkit-flex-direction: $val;
|
||||
-moz-flex-direction: $val;
|
||||
-ms-flex-direction: $val;
|
||||
-o-flex-direction: $val;
|
||||
flex-direction: $val;
|
||||
}
|
||||
|
||||
@mixin flex-wrap($val: nowrap) {
|
||||
-webkit-flex-wrap: $val;
|
||||
-moz-flex-wrap: $val;
|
||||
-ms-flex-wrap: $val;
|
||||
-o-flex-wrap: $val;
|
||||
flex-wrap: $val;
|
||||
}
|
||||
|
||||
// Old flexbox.
|
||||
@mixin display-box() {
|
||||
display: -webkit-box;
|
||||
display: -moz-box;
|
||||
display: -ms-box;
|
||||
display: -o-box;
|
||||
display: box;
|
||||
}
|
||||
|
||||
@mixin box-orient($val) {
|
||||
-webkit-box-orient: $val;
|
||||
-moz-box-orient: $val;
|
||||
-ms-box-orient: $val;
|
||||
-o-box-orient: $val;
|
||||
box-orient: $val;
|
||||
}
|
||||
|
||||
@mixin box-align($val) {
|
||||
-webkit-box-align: $val;
|
||||
-moz-box-align: $val;
|
||||
-ms-box-align: $val;
|
||||
-o-box-align: $val;
|
||||
box-align: $val;
|
||||
}
|
||||
|
||||
@mixin box-flex($val) {
|
||||
-webkit-box-flex: $val;
|
||||
-moz-box-flex: $val;
|
||||
-ms-box-flex: $val;
|
||||
-o-box-flex: $val;
|
||||
box-flex: $val;
|
||||
}
|
||||
|
||||
@mixin box-pack($val) {
|
||||
-webkit-box-pack: $val;
|
||||
-moz-box-pack: $val;
|
||||
-ms-box-pack: $val;
|
||||
-o-box-pack: $val;
|
||||
box-pack: $val;
|
||||
}
|
||||
|
||||
@mixin width-max-content() {
|
||||
width: -webkit-max-content;
|
||||
width: -moz-max-content;
|
||||
width: -ms-max-content;
|
||||
width: -o-max-content;
|
||||
width: max-content;
|
||||
}
|
||||
|
||||
@mixin user-select() {
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-o-user-select: none;
|
||||
-ms-user-select: none;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
@mixin calc-width($val) {
|
||||
width: -webkit-calc($val);
|
||||
width: -moz-calc($val);
|
||||
width: -ms-calc($val);
|
||||
width: -o-calc($val);
|
||||
width: calc($val);
|
||||
}
|
|
@ -4,7 +4,7 @@
|
|||
|
||||
<section>
|
||||
<h2>Add a feature:</h2>
|
||||
<form name="feature_form" method="POST" action="/newfeature">
|
||||
<form name="feature_form" method="POST" action="{{current_path}}">
|
||||
<table>
|
||||
<!--<tr><th><label for="id_key_name">key_name:</label></th><td><input type="text" name="key_name" id="id_key_name" required /></td></tr>-->
|
||||
{{ feature_form }}
|
|
@ -30,7 +30,7 @@
|
|||
// Author: Eric Bidelman (ericbidelman@chromium.org)
|
||||
-->
|
||||
<!DOCTYPE html>
|
||||
<html ng-app>
|
||||
<html ng-app="dashboardApp">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>{{ APP_TITLE }}</title>
|
||||
|
@ -38,6 +38,8 @@
|
|||
<link rel="stylesheet" type="text/css" href="//fonts.googleapis.com/css?family=Open+Sans:300,300italic,600,800">
|
||||
<link rel="stylesheet" type="text/css" href="/static/css/main.css?2012-03-06">
|
||||
|
||||
{% block css %} {% endblock %}
|
||||
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0">
|
||||
|
||||
<!-- iOS: run in full-screen mode and display upper status bar as translucent -->
|
||||
|
@ -47,7 +49,7 @@
|
|||
<!--<link rel="apple-touch-icon" href="/static/images/identity/HTML5_Badge_64.png">
|
||||
<link rel="apple-touch-icon-precomposed" href="/static/images/identity/HTML5_Badge_64.png">-->
|
||||
|
||||
<!-- <script src="/static/js/angular.min.js"></script> -->
|
||||
<script src="/static/js/angular.min.js"></script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
@ -60,7 +62,8 @@
|
|||
{% include "footer.html" %}
|
||||
</div>
|
||||
|
||||
<!-- <script src="/static/js/app.js"></script> -->
|
||||
<script src="/static/js/app.js"></script>
|
||||
{% block js %}{% endblock %}
|
||||
<script>
|
||||
var _gaq = _gaq || [];
|
||||
_gaq.push(['_setAccount', 'UA-39048143-1']);
|
||||
|
|
|
@ -1,8 +1,11 @@
|
|||
{% extends "base.html" %}
|
||||
|
||||
|
||||
{% block content %}
|
||||
|
||||
<div class="warning">
|
||||
<b>April 3, 2013</b>: This dashboard is under construction. For an older snapshot of some of this data see <a href="https://sites.google.com/a/chromium.org/dev/developers/web-platform-status" target="_blank">Web Platform Status</a>. For more info, read our <a href="https://groups.google.com/a/chromium.org/d/topic/chromium-dev/E6lCV7diafU/discussion" target="_blank">chromium-dev announcement</a> or visit the <a href="https://docs.google.com/document/d/1jrSlM4Yhae7XCJ8BuasWx71CvDEMMbSKbXwx7hoh1Co/edit?pli=1" target="_blank">FAQ</a>. The raw spreadsheet data is <a href="https://docs.google.com/spreadsheet/ccc?key=0AjGgk26K1Cc-dFdfUlRQRWtjUm5XdjB4cmFGWjhTZmc#gid=0" target="_blank">here</a>.
|
||||
</div>
|
||||
|
||||
<!--
|
||||
{% load verbatim %}
|
||||
|
||||
|
|
|
@ -6,11 +6,8 @@
|
|||
<span>a status of what we're up to</span>
|
||||
</hgroup>
|
||||
</aside>
|
||||
<!-- <nav>
|
||||
<nav>
|
||||
<a href="/features" class="features">Features</a>
|
||||
<a href="/metrics" class="metrics disabled">Metrics</a>
|
||||
</nav> -->
|
||||
<a href="/metrics" class="metrics">Metrics</a>
|
||||
</nav>
|
||||
</header>
|
||||
<div class="warning">
|
||||
<b>April 3, 2013</b>: This dashboard is under construction. For an older snapshot of some of this data see <a href="https://sites.google.com/a/chromium.org/dev/developers/web-platform-status" target="_blank">Web Platform Status</a>. For more info, read our <a href="https://groups.google.com/a/chromium.org/d/topic/chromium-dev/E6lCV7diafU/discussion" target="_blank">chromium-dev announcement</a> or visit the <a href="https://docs.google.com/document/d/1jrSlM4Yhae7XCJ8BuasWx71CvDEMMbSKbXwx7hoh1Co/edit?pli=1" target="_blank">FAQ</a>. The raw spreadsheet data is <a href="https://docs.google.com/spreadsheet/ccc?key=0AjGgk26K1Cc-dFdfUlRQRWtjUm5XdjB4cmFGWjhTZmc#gid=0" target="_blank">here</a>.
|
||||
</div>
|
|
@ -1,11 +1,6 @@
|
|||
|
||||
{% block content %}
|
||||
|
||||
<style>
|
||||
body {
|
||||
overflow: hidden;
|
||||
}
|
||||
</style>
|
||||
<iframe seamless src="//sites.google.com/a/chromium.org/dev/developers/web-platform-status" style="width:100%;height:100%;"></iframe>
|
||||
NOTHING HERE
|
||||
|
||||
{% endblock %}
|
||||
|
|
|
@ -1,7 +1,26 @@
|
|||
{% extends "base.html" %}
|
||||
|
||||
{% block css %}
|
||||
<link rel="stylesheet" type="text/css" href="/static/css/metrics/metrics.css">
|
||||
{% endblock %}
|
||||
|
||||
{% block js %}
|
||||
<script src="/static/js/metrics/mostPopular.js"></script>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
Come back soon :)
|
||||
{% include "metrics/_nav.html" %}
|
||||
|
||||
<section>
|
||||
<h1>CSS Property Usage</h1>
|
||||
<p>CSS Properties in order of calls made to each property. Currently counting all calls, including the user agent style sheet. Per-page stats, excluding the user agent style sheet to come soon!</p>
|
||||
</section>
|
||||
|
||||
<!-- NOT COMPLETE. Depends on stack. -->
|
||||
<!--
|
||||
<section><h1>Most Popular</h1>
|
||||
<p>Top 10 CSS Properties by percentage of pages which use each property. Statistics excluding the user agent style sheet to come soon!</p>
|
||||
</section>-->
|
||||
|
||||
{% endblock %}
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
<ul class="metric-nav">
|
||||
<li><a href="/metrics">Most Popular</a></li>
|
||||
<li><a href="/metrics/featurelevel">Feature Level</a></li>
|
||||
<li><a href="/metrics/stackrank">Stack Rank</a></li>
|
||||
</ul>
|
|
@ -0,0 +1,35 @@
|
|||
{% extends "base.html" %}
|
||||
{% load verbatim %}
|
||||
|
||||
{% block css %}
|
||||
<link rel="stylesheet" type="text/css" href="/static/css/metrics/metrics.css">
|
||||
{% endblock %}
|
||||
|
||||
{% block js %}
|
||||
<!-- <script src='//www.google.com/jsapi?autoload={"modules":[{"name":"visualization","version":"1","packages":["corechart"],"callback": "onGoogLoad"}]}'></script> -->
|
||||
<script src='//www.google.com/jsapi?autoload={"modules":[{"name":"visualization","version":"1","packages":["corechart"]}]}'></script>
|
||||
<!-- <script src="https://www.google.com/jsapi"></script> -->
|
||||
<script src="/static/js/metrics/featurelevel.js"></script>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
{% include "metrics/_nav.html" %}
|
||||
|
||||
<section ng-controller="TimeLineController">
|
||||
<h1>CSS Feature Over Time</h1>
|
||||
<select id="property-selector" onchange="goToChart('property-selector')">
|
||||
{% for key, value in CSS_PROPERTY_BUCKETS.items %}
|
||||
<option value="{{key}}">{{value}}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
<p>Use of CSS properties over time. Statistics excluding the user agent style sheet to come soon!</p>
|
||||
|
||||
{% verbatim %}
|
||||
<div id="chart-container" style="width:900px;margin: 0 auto;">
|
||||
<div id="chart"></div>
|
||||
</div>
|
||||
</section>
|
||||
{% endverbatim %}
|
||||
|
||||
{% endblock %}
|
|
@ -0,0 +1,33 @@
|
|||
{% extends "base.html" %}
|
||||
{% load verbatim %}
|
||||
|
||||
{% block css %}
|
||||
<link rel="stylesheet" type="text/css" href="/static/css/metrics/metrics.css">
|
||||
{% endblock %}
|
||||
|
||||
{% block js %}
|
||||
<script src="/static/js/angular.resource.min.js"></script>
|
||||
<script src="/static/js/metrics/stackrank.js"></script>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
{% include "metrics/_nav.html" %}
|
||||
|
||||
{% verbatim %}
|
||||
<section ng-controller="StackRankController">
|
||||
<h1>Stack Rank</h1>
|
||||
<p>CSS Properties in order of popularity. Values reflect the percentage of
|
||||
surveyed pages which use each property.</p>
|
||||
<p><button ng-click="sortList()">Swap order</button><p>
|
||||
|
||||
<ol id="stack-rank-list">
|
||||
<li ng-show="!properties.length" class="loading">Loading...</li>
|
||||
<li ng-repeat="prop in properties | orderBy:sortBy">
|
||||
<span>{{prop.day_percentage | number }}%</span><span>{{prop.property_name}}</span>
|
||||
</li>
|
||||
</ol>
|
||||
</section>
|
||||
{% endverbatim %}
|
||||
|
||||
{% endblock %}
|
Некоторые файлы не были показаны из-за слишком большого количества измененных файлов Показать больше
Загрузка…
Ссылка в новой задаче