├── api ├── __init__.py ├── app_utils.py └── api_utils.py ├── db ├── __init__.py ├── mysql_utils.py ├── dsidx_dbs.py ├── other_dbs.py ├── api_dbs.py └── sqlite_utils.py ├── dump ├── __init__.py ├── codesign_utils.py ├── class_dump_utils.py └── otool_utils.py ├── utils ├── __init__.py ├── lib │ ├── __init__.py │ └── xlsxwriter │ │ ├── __init__.py │ │ ├── compatibility.py │ │ ├── chart_radar.py │ │ ├── chart_area.py │ │ ├── chart_doughnut.py │ │ ├── chart_line.py │ │ ├── relationships.py │ │ ├── chart_column.py │ │ ├── chart_stock.py │ │ ├── sharedstrings.py │ │ ├── chart_bar.py │ │ ├── chartsheet.py │ │ ├── table.py │ │ ├── comments.py │ │ ├── app.py │ │ ├── core.py │ │ ├── chart_pie.py │ │ ├── contenttypes.py │ │ ├── xmlwriter.py │ │ ├── compat_collections.py │ │ └── theme.py ├── .DS_Store └── utils.py ├── app ├── dbs │ ├── inc │ │ ├── __init__.py │ │ ├── Mongo.py │ │ ├── Redis.py │ │ └── Mysql.py │ ├── __init__.py │ ├── main_dbs.py │ └── test_dbs.py ├── others │ ├── __init__.py │ └── tasks.py ├── utils │ ├── __init__.py │ ├── jinja2_ex │ │ ├── __init__.py │ │ └── template_filter.py │ ├── DateUtil.py │ ├── StringUtil.py │ ├── CJsonEncoder.py │ ├── LogUtil.py │ ├── OtherUtil.py │ ├── PathUtil.py │ ├── RequestUtil.py │ └── IpaParse.py ├── views │ ├── __init__.py │ └── main_views.py ├── wraps │ ├── __init__.py │ ├── allow_request_wrap.py │ ├── singleton_wrap.py │ ├── async_task_wrap.py │ ├── login_wrap.py │ ├── trace_wrap.py │ ├── mysql_escape_warp.py │ └── db_connect_warp.py ├── static │ ├── res │ │ ├── css │ │ │ ├── RAEDME.md │ │ │ ├── styles.css │ │ │ └── dropzone.min.css │ │ ├── img │ │ │ └── RAEDME.md │ │ ├── js │ │ │ ├── RAEDME.md │ │ │ └── ios_private.js │ │ └── favicon.ico │ └── upload │ │ └── RAEDME.md ├── __init__.py └── templates │ └── main │ └── index_page.html ├── requirements.txt ├── .DS_Store ├── class-dump ├── class_dump_z ├── iphone_armv6 │ ├── README │ └── class-dump-z ├── win_x86 │ ├── .DS_Store │ └── class-dump-z.exe ├── linux_x86 │ ├── .DS_Store │ ├── class-dump-z │ └── README ├── mac_x86 │ └── class-dump-z └── README ├── screenshot ├── web_screenshot.png ├── excel_report_detail.png └── excel_report_outline.png ├── run_web.py ├── run_web ├── .project ├── .pydevproject ├── .settings └── org.eclipse.core.resources.prefs ├── .gitignore ├── config.py ├── README.md └── iOS_private.py /api/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /db/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /dump/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /utils/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/dbs/inc/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/others/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/utils/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/views/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/wraps/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /utils/lib/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/static/res/css/RAEDME.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/static/res/img/RAEDME.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/static/res/js/RAEDME.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/static/upload/RAEDME.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/utils/jinja2_ex/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | Flask 2 | gunicorn 3 | gevent 4 | macholib -------------------------------------------------------------------------------- /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NetEaseGame/iOS-private-api-checker/HEAD/.DS_Store -------------------------------------------------------------------------------- /class-dump: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NetEaseGame/iOS-private-api-checker/HEAD/class-dump -------------------------------------------------------------------------------- /app/dbs/__init__.py: -------------------------------------------------------------------------------- 1 | #coding=utf-8 2 | ''' 3 | Created on 2015年6月16日 4 | 5 | @author: atool 6 | ''' 7 | -------------------------------------------------------------------------------- /db/mysql_utils.py: -------------------------------------------------------------------------------- 1 | #coding=utf-8 2 | ''' 3 | Created on 2015年10月27日 4 | 5 | @author: atool 6 | ''' 7 | -------------------------------------------------------------------------------- /utils/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NetEaseGame/iOS-private-api-checker/HEAD/utils/.DS_Store -------------------------------------------------------------------------------- /class_dump_z/iphone_armv6/README: -------------------------------------------------------------------------------- 1 | You must install the "pcre" package from Cydia to run class-dump-z on the iPhoneOS. -------------------------------------------------------------------------------- /app/static/res/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NetEaseGame/iOS-private-api-checker/HEAD/app/static/res/favicon.ico -------------------------------------------------------------------------------- /app/wraps/allow_request_wrap.py: -------------------------------------------------------------------------------- 1 | #coding=utf-8 2 | ''' 3 | Created on 2015年6月17日 4 | 5 | @author: atool 6 | ''' 7 | 8 | -------------------------------------------------------------------------------- /utils/lib/xlsxwriter/__init__.py: -------------------------------------------------------------------------------- 1 | __version__ = '0.7.3' 2 | __VERSION__ = __version__ 3 | from .workbook import Workbook 4 | -------------------------------------------------------------------------------- /class_dump_z/win_x86/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NetEaseGame/iOS-private-api-checker/HEAD/class_dump_z/win_x86/.DS_Store -------------------------------------------------------------------------------- /screenshot/web_screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NetEaseGame/iOS-private-api-checker/HEAD/screenshot/web_screenshot.png -------------------------------------------------------------------------------- /class_dump_z/linux_x86/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NetEaseGame/iOS-private-api-checker/HEAD/class_dump_z/linux_x86/.DS_Store -------------------------------------------------------------------------------- /class_dump_z/mac_x86/class-dump-z: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NetEaseGame/iOS-private-api-checker/HEAD/class_dump_z/mac_x86/class-dump-z -------------------------------------------------------------------------------- /class_dump_z/linux_x86/class-dump-z: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NetEaseGame/iOS-private-api-checker/HEAD/class_dump_z/linux_x86/class-dump-z -------------------------------------------------------------------------------- /screenshot/excel_report_detail.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NetEaseGame/iOS-private-api-checker/HEAD/screenshot/excel_report_detail.png -------------------------------------------------------------------------------- /screenshot/excel_report_outline.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NetEaseGame/iOS-private-api-checker/HEAD/screenshot/excel_report_outline.png -------------------------------------------------------------------------------- /class_dump_z/iphone_armv6/class-dump-z: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NetEaseGame/iOS-private-api-checker/HEAD/class_dump_z/iphone_armv6/class-dump-z -------------------------------------------------------------------------------- /class_dump_z/win_x86/class-dump-z.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NetEaseGame/iOS-private-api-checker/HEAD/class_dump_z/win_x86/class-dump-z.exe -------------------------------------------------------------------------------- /run_web.py: -------------------------------------------------------------------------------- 1 | #coding=utf-8 2 | ''' 3 | Created on 2015年11月05日 4 | iOS private api检查 Web启动入口 5 | @author: atool 6 | ''' 7 | 8 | 9 | from app import app 10 | if __name__ == '__main__': 11 | app.run('0.0.0.0', 9527, debug = True, threaded = True) -------------------------------------------------------------------------------- /class_dump_z/README: -------------------------------------------------------------------------------- 1 | Source code can be found in various directories inside http://code.google.com/p/networkpx/source/browse/trunk/hk.kennytm.Peace/. 2 | 3 | This program is license in GPLv3. Read the LICENSE file for detail. 4 | 5 | Platform-specific info can be found in the README files in the respective directories. -------------------------------------------------------------------------------- /app/utils/DateUtil.py: -------------------------------------------------------------------------------- 1 | #coding=utf-8 2 | ''' 3 | Created on 2015年8月24日 4 | 5 | @author: atool 6 | ''' 7 | import datetime 8 | 9 | 10 | #当前时间,可用于mysql datetime 11 | def now_datetime(): 12 | return datetime.datetime.now().strftime("%y-%m-%d %H:%M:%S") 13 | 14 | 15 | if __name__ == '__main__': 16 | print now_datetime() -------------------------------------------------------------------------------- /app/dbs/main_dbs.py: -------------------------------------------------------------------------------- 1 | #coding=utf-8 2 | ''' 3 | Created on 2015年6月16日 4 | main模块涉及的数据库操作 5 | 可以是mysql,也可以是mongodb 6 | 使用什么数据库,什么orm均不限制 7 | @author: atool 8 | ''' 9 | 10 | 11 | def get_user_by_id(u_id): 12 | ''' 13 | get the user information 14 | ''' 15 | #执行sql,获得数据,返回给views层使用 16 | return {'name':'atool', 'sex': 1} -------------------------------------------------------------------------------- /run_web: -------------------------------------------------------------------------------- 1 | pids=$(ps -ef|grep gunicorn|grep run_web|awk {'print $2,$3'}) 2 | pidsArray=($pids) 3 | 4 | len=${#pidsArray[*]} 5 | for ((index=0;index<$len;index+=1));do 6 | tmp=`expr $index+1` 7 | echo ${pidsArray[$index]} 8 | kill -9 ${pidsArray[$index]} 9 | done 10 | sleep 1 11 | gunicorn --worker-class=gevent -w 4 -b 0.0.0.0:9527 run_web:app 12 | -------------------------------------------------------------------------------- /app/wraps/singleton_wrap.py: -------------------------------------------------------------------------------- 1 | #coding=utf-8 2 | ''' 3 | Created on 2015年8月21日 4 | 单利模式装饰器 5 | @author: atool 6 | ''' 7 | def singleton(cls, *args, **kw): 8 | instances = {} 9 | def _singleton(): 10 | if cls not in instances: 11 | instances[cls] = cls(*args, **kw) 12 | return instances[cls] 13 | return _singleton -------------------------------------------------------------------------------- /app/others/tasks.py: -------------------------------------------------------------------------------- 1 | #coding=utf-8 2 | ''' 3 | Created on 2015年6月16日 4 | all tasks 5 | @author: atool 6 | ''' 7 | from app.wraps.async_task_wrap import async_task 8 | 9 | 10 | @async_task 11 | def count_to_10000(): 12 | ''' 13 | test async_task, count to 1w 14 | 注意:在uwsgi部署情况下,不能执行异步任务,所有任务在一次请求完成之后,全部释放 15 | ''' 16 | for i in xrange(10000): 17 | print i 18 | 19 | -------------------------------------------------------------------------------- /.project: -------------------------------------------------------------------------------- 1 | 2 | 3 | iOS-private-api-checker 4 | 5 | 6 | 7 | 8 | 9 | org.python.pydev.PyDevBuilder 10 | 11 | 12 | 13 | 14 | 15 | org.python.pydev.pythonNature 16 | 17 | 18 | -------------------------------------------------------------------------------- /app/wraps/async_task_wrap.py: -------------------------------------------------------------------------------- 1 | #coding=utf-8 2 | ''' 3 | Created on 2015年6月16日 4 | 5 | @author: atool 6 | ''' 7 | from functools import wraps 8 | from threading import Thread 9 | 10 | 11 | def async_task(f): 12 | ''' 13 | wrap with this, the function will be async 14 | use at task which need long time to finish 15 | ''' 16 | @wraps(f) 17 | def wrapper(*args, **kwargs): 18 | thr = Thread(target=f, args=args, kwargs=kwargs) 19 | thr.start() 20 | return wrapper -------------------------------------------------------------------------------- /.pydevproject: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | /${PROJECT_DIR_NAME} 5 | /${PROJECT_DIR_NAME}/tmp 6 | 7 | python 2.7 8 | Default 9 | 10 | -------------------------------------------------------------------------------- /app/utils/StringUtil.py: -------------------------------------------------------------------------------- 1 | #coding=utf-8 2 | ''' 3 | Created on 2015年6月16日 4 | 5 | @author: atool 6 | ''' 7 | import time 8 | import datetime 9 | import random 10 | 11 | def is_empty(s): 12 | if s == None or s == '': 13 | return True 14 | return False 15 | 16 | 17 | def get_unique_str(): 18 | #随机的名字,可以用于上传文件等等不重复,但有一定时间意义的名字 19 | datetime_str = time.strftime('%Y%m%d%H%M%S',time.localtime()) 20 | return datetime_str + str(datetime.datetime.now().microsecond / 1000) + str(random.randint(0, 1000)) -------------------------------------------------------------------------------- /app/utils/CJsonEncoder.py: -------------------------------------------------------------------------------- 1 | #coding=utf-8 2 | ''' 3 | Created on 2015年1月27日 4 | 5 | @author: atool 6 | ''' 7 | from datetime import date 8 | from datetime import datetime 9 | import json 10 | 11 | 12 | class CJsonEncoder(json.JSONEncoder): 13 | def default(self, obj): 14 | if isinstance(obj, datetime): 15 | return obj.strftime('%Y-%m-%d %H:%M:%S') 16 | elif isinstance(obj, date): 17 | return obj.strftime('%Y-%m-%d') 18 | else: 19 | return json.JSONEncoder.default(self, obj) -------------------------------------------------------------------------------- /class_dump_z/linux_x86/README: -------------------------------------------------------------------------------- 1 | The Linux version of class-dump-z is compiled in Linux 2.6.31-14-generic i686 with gcc-4.2.4. The following are the dynamic libraries needed: 2 | 3 | linux-gate.so.1 => (0x00bd6000) 4 | libstdc++.so.6 => /usr/lib/libstdc++.so.6 (0x00a29000) 5 | libm.so.6 => /lib/tls/i686/cmov/libm.so.6 (0x00ea1000) 6 | libgcc_s.so.1 => /lib/libgcc_s.so.1 (0x005f1000) 7 | libc.so.6 => /lib/tls/i686/cmov/libc.so.6 (0x007f9000) 8 | /lib/ld-linux.so.2 (0x0034f000) 9 | 10 | Usually it will just run fine on x86 and amd64 systems. -------------------------------------------------------------------------------- /app/wraps/login_wrap.py: -------------------------------------------------------------------------------- 1 | #coding=utf-8 2 | ''' 3 | Created on 2015年1月28日 4 | 5 | @author: atool 6 | ''' 7 | from functools import wraps 8 | 9 | from flask import request, redirect, url_for 10 | from flask.globals import session 11 | 12 | 13 | 14 | def login_required(f): 15 | ''' 16 | need login wrap 17 | ''' 18 | @wraps(f) 19 | def decorated_function(*args, **kwargs): 20 | if 'u_id' not in session or session['u_id'] is None or session['u_id'] == '': 21 | return redirect(url_for('login', next = request.url)) 22 | return f(*args, **kwargs) 23 | return decorated_function -------------------------------------------------------------------------------- /app/wraps/trace_wrap.py: -------------------------------------------------------------------------------- 1 | #coding=utf-8 2 | ''' 3 | Created on 2015年8月21日 4 | 5 | @author: atool 6 | ''' 7 | 8 | from functools import wraps 9 | import traceback 10 | 11 | #定义一个trace监控的装饰器 12 | def log_traceback(f): 13 | @wraps(f) 14 | def decorated_function(*args, **kwargs): 15 | #先执行方法,然后写日志 16 | try: 17 | func = f(*args, **kwargs) 18 | return func 19 | except: 20 | #如果出现trace异常,发送到服务器 21 | trace = traceback.format_exc() 22 | print trace 23 | #TODO 将trace写入到文件或者post到服务器中 24 | return func 25 | return decorated_function -------------------------------------------------------------------------------- /app/dbs/test_dbs.py: -------------------------------------------------------------------------------- 1 | #coding=utf-8 2 | ''' 3 | Created on 2015年8月25日 4 | 5 | @author: atool 6 | ''' 7 | from app.dbs.inc.Redis import RedisMysqlCache 8 | from app.dbs.inc.Mysql import Mysql 9 | 10 | 11 | #获取tid的测试信息 12 | def get_test_by_id(test_id, use_redis_cache = True): 13 | sql = "select * from abtest_tests where test_id = %s" 14 | params = (test_id, ) 15 | 16 | #该方法你用redis缓存 17 | if use_redis_cache: 18 | return RedisMysqlCache().select_one(sql, params) 19 | else: 20 | return Mysql().exec_select_one(sql, params) 21 | 22 | if __name__ == '__main__': 23 | print get_test_by_id(1) 24 | print get_test_by_id("1", False) -------------------------------------------------------------------------------- /dump/codesign_utils.py: -------------------------------------------------------------------------------- 1 | #coding=utf-8 2 | ''' 3 | Created on 2015年11月17日 4 | 5 | @author: atool 6 | ''' 7 | 8 | import commands 9 | 10 | 11 | 12 | def codesignapp(app_path): 13 | """ 14 | Get codesign informatiob included in app 15 | About codesign: https://developer.apple.com/legacy/library/technotes/tn2250/_index.html#//apple_ref/doc/uid/DTS40009933 16 | Args: 17 | Mach-o path 18 | Returns: 19 | the content of codesign 20 | """ 21 | cmd = "/usr/bin/codesign -dvvv %s" % app_path 22 | out = commands.getstatusoutput(cmd) 23 | if out and len(out) == 2 and out[0] == 0: 24 | out = out[1] 25 | else: 26 | out = '' 27 | return out -------------------------------------------------------------------------------- /app/utils/LogUtil.py: -------------------------------------------------------------------------------- 1 | #coding=utf-8 2 | ''' 3 | Created on 2015年8月24日 4 | 5 | @author: atool 6 | ''' 7 | from app.utils import PathUtil, DateUtil 8 | 9 | def append_log(log_file, data): 10 | ''' 11 | append data to file pathing with filePath 12 | ''' 13 | if data: 14 | file_handler = open(PathUtil.log_dir() + log_file, 'a') 15 | file_handler.write(data + '\n') 16 | file_handler.close() 17 | 18 | return True 19 | 20 | #记录非法用户的日志,一般这些用户都是尝试模拟请求的方式往数据库写入信息 21 | def log_invalid(request, ext_text): 22 | log = '%s - - [%s] %s %s %s' % (request.remote_addr, DateUtil.now_datetime(), request.method, request.path, ext_text) 23 | append_log('record_invaild_log.log', log) -------------------------------------------------------------------------------- /app/utils/OtherUtil.py: -------------------------------------------------------------------------------- 1 | #coding=utf-8 2 | ''' 3 | Created on 2015年8月21日 4 | 5 | @author: atool 6 | ''' 7 | import hashlib 8 | import json 9 | from CJsonEncoder import CJsonEncoder 10 | 11 | def md5_salt(s, salt = "ab_test"): 12 | ''' 13 | md5 + 盐:即便两个用户使用了同一个密码,由于系统为它们生成的salt值不同,他们的散列值也是不同的。 14 | ''' 15 | if s: 16 | return md5(s + salt) 17 | else: 18 | return '' 19 | 20 | def md5(s): 21 | ''' 22 | md5 23 | ''' 24 | m = hashlib.md5() 25 | m.update(s) 26 | return m.hexdigest() 27 | 28 | def object_2_dict(obj): 29 | ''' 30 | py obj to dict 31 | ''' 32 | if obj == None: 33 | return {} 34 | return json.dumps(obj, cls = CJsonEncoder) -------------------------------------------------------------------------------- /app/utils/PathUtil.py: -------------------------------------------------------------------------------- 1 | #coding=utf-8 2 | ''' 3 | Created on 2015年2月5日 4 | 5 | @author: atool 6 | ''' 7 | import os 8 | import sys 9 | 10 | def upload_dir(): 11 | return _cur_file_dir() + '/app/static/upload/' 12 | 13 | def log_dir(): 14 | return _cur_file_dir() + '/log/' 15 | 16 | def default_tmp_dir(): 17 | return _cur_file_dir() + '/app/static/tmp/' 18 | 19 | def _cur_file_dir(): 20 | #获取脚本路径 21 | path = sys.path[0] 22 | #判断为脚本文件还是py2exe编译后的文件,如果是脚本文件,则返回的是脚本的目录,如果是py2exe编译后的文件,则返回的是编译后的文件路径 23 | if os.path.isdir(path): 24 | return path 25 | elif os.path.isfile(path): 26 | return os.path.dirname(path) 27 | 28 | if __name__ == '__main__': 29 | print _cur_file_dir() 30 | -------------------------------------------------------------------------------- /app/__init__.py: -------------------------------------------------------------------------------- 1 | #coding=utf-8 2 | ''' 3 | Created on 2015年6月16日 4 | 5 | @author: atool 6 | ''' 7 | from flask import Flask 8 | 9 | app = Flask(__name__) 10 | app.secret_key = 'your_session_key_ab_test' 11 | app.config['MAX_CONTENT_LENGTH'] = 1024 * 1024 * 1024 12 | 13 | db_config = { 14 | 'DB_USER': 'dev', 15 | 'DB_PSW': 'dev', 16 | 'DB_NAME': 'ab_test', 17 | 'DB_HOST': '10.246.14.121', 18 | 'DB_PORT': 3306, 19 | 'DB_CHARSET': 'utf8' 20 | } 21 | 22 | redis_config = { 23 | 'RD_PSW': None, 24 | 'RD_HOST': '10.246.14.121', 25 | 'RD_PORT': 6379, 26 | 'RD_CHARSET': 'utf8', 27 | 'TEST_DB': 0, 28 | 'TEMP_DB': 1, #缓存db 29 | 'RECORD_DB': 2 #用户访问db 30 | } 31 | 32 | from app.views import main_views -------------------------------------------------------------------------------- /dump/class_dump_utils.py: -------------------------------------------------------------------------------- 1 | #coding=utf-8 2 | ''' 3 | Created on 2015年10月27日 4 | class-dump操作类 5 | @author: atool 6 | ''' 7 | import subprocess 8 | from utils import utils 9 | class_dump_path = utils.get_clas_dump_path() 10 | 11 | dump_cmd = class_dump_path + " -H %s -o %s" # dump cmd模板字符串 12 | 13 | def dump_framework(frame_path, out_path): 14 | ''' 15 | info:使用class-dump来解开framework中的api 16 | ''' 17 | cmd = dump_cmd % (frame_path, out_path) 18 | ret = subprocess.call(cmd.split()) 19 | if ret != 0: 20 | return frame_path 21 | return "" 22 | 23 | def dump_app(app_path): 24 | ''' 25 | get all private variables, properties, and interface name 26 | ''' 27 | class_dump = class_dump_path + " %s" % app_path 28 | dump_result = subprocess.check_output(class_dump.split()) 29 | return dump_result -------------------------------------------------------------------------------- /app/utils/RequestUtil.py: -------------------------------------------------------------------------------- 1 | #coding=utf-8 2 | ''' 3 | Created on 2015年8月21日 4 | 5 | @author: atool 6 | ''' 7 | #获得参数,post或者get 8 | def get_parameter(request, key, default = None): 9 | ''' 10 | info:获得请求参数,包括get和post,其他类型的访问不管 11 | ''' 12 | #post参数 13 | if request.method == 'POST': 14 | param = request.form.get(key, default) 15 | #get 16 | elif request.method == 'GET': 17 | param = request.args.get(key, default) 18 | else: 19 | return default 20 | 21 | return param 22 | 23 | #用户IP 24 | def get_request_ip(request): 25 | return request.remote_addr 26 | 27 | #获得用户访问方式 28 | def get_request_method(request): 29 | return request.method 30 | 31 | def get_request_ua(request): 32 | return request.headers.get('User-Agent', '') 33 | 34 | def get_request_accept_lang(request): 35 | request.environ.get('HTTP_ACCEPT_LANGUAGE', '') 36 | -------------------------------------------------------------------------------- /app/utils/jinja2_ex/template_filter.py: -------------------------------------------------------------------------------- 1 | #coding=utf-8 2 | ''' 3 | Created on 2015年5月6日 4 | 5 | @author: atool 6 | ''' 7 | from app import app 8 | 9 | @app.context_processor 10 | def ext_jinja2_processor(): 11 | ''' 12 | ps:扩展jinja2的内置方法 13 | ''' 14 | def str_sub(s, start, end, suffix = None): 15 | ''' 16 | str_sub;字符串截断 17 | ''' 18 | if suffix: 19 | return s[start:end] + suffix 20 | return s[start:end] 21 | 22 | def str_len(s): 23 | ''' 24 | str_len:字符串长度 25 | ''' 26 | return len(s) 27 | 28 | def to_str(i): 29 | ''' 30 | to_str:将数字转字符串,一些比较的时候会使用到 31 | ''' 32 | return str(i) 33 | 34 | def to_round(f, d = 3): 35 | ''' 36 | to_round:浮点数小数位数 37 | ''' 38 | return round(f, d) 39 | 40 | return dict(str_sub = str_sub, str_len = str_len, to_str = to_str, to_round = to_round) 41 | -------------------------------------------------------------------------------- /dump/otool_utils.py: -------------------------------------------------------------------------------- 1 | #coding=utf-8 2 | ''' 3 | Created on 2015年10月29日 4 | 5 | @author: atool 6 | ''' 7 | 8 | import subprocess 9 | import re 10 | 11 | otool_path = "otool" #otool所在的位置 12 | 13 | otool_cmd = otool_path + " -L %s" # otool cmd模板字符串 14 | 15 | def otool_app(app_path): 16 | """ 17 | Get framework included in app 18 | Args: 19 | Mach-o path 20 | Returns: 21 | two sets, one is public framework, one is private framework 22 | """ 23 | cmd = otool_cmd % app_path 24 | 25 | out = subprocess.check_output(cmd.split()) 26 | pattern = re.compile("PrivateFrameworks\/(\w*)\.framework") 27 | pub_pattern = re.compile("Frameworks\/([\.\w]*)") 28 | 29 | private = set() 30 | public = set() 31 | 32 | for r in re.finditer(pattern, out): 33 | private.add(r.group(1)) 34 | 35 | for r in re.finditer(pub_pattern, out): 36 | public.add(r.group(1)) 37 | 38 | return private, public -------------------------------------------------------------------------------- /.settings/org.eclipse.core.resources.prefs: -------------------------------------------------------------------------------- 1 | eclipse.preferences.version=1 2 | encoding//api/api_helpers.py=utf-8 3 | encoding//api/api_utils.py=utf-8 4 | encoding//api/app_utils.py=utf-8 5 | encoding//app/__init__.py=utf-8 6 | encoding//app/dbs/__init__.py=utf-8 7 | encoding//app/others/tasks.py=utf-8 8 | encoding//app/utils/LogUtil.py=utf-8 9 | encoding//app/utils/OtherUtil.py=utf-8 10 | encoding//app/utils/PathUtil.py=utf-8 11 | encoding//app/utils/RequestUtil.py=utf-8 12 | encoding//app/utils/StringUtil.py=utf-8 13 | encoding//app/views/main_views.py=utf-8 14 | encoding//db/api_dbs.py=utf-8 15 | encoding//db/dsidx_dbs.py=utf-8 16 | encoding//db/mysql_utils.py=utf-8 17 | encoding//db/other_dbs.py=utf-8 18 | encoding//db/sqlite_utils.py=utf-8 19 | encoding//dump/class_dump_utils.py=utf-8 20 | encoding//dump/otool_utils.py=utf-8 21 | encoding//utils/report_utils.py=utf-8 22 | encoding//utils/utils.py=utf-8 23 | encoding/build_api_db.py=utf-8 24 | encoding/config.py=utf-8 25 | encoding/iOS_private.py=utf-8 26 | encoding/run_web.py=utf-8 27 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by https://www.gitignore.io/api/python 2 | 3 | ### Python ### 4 | # Byte-compiled / optimized / DLL files 5 | __pycache__/ 6 | *.py[cod] 7 | *$py.class 8 | 9 | # C extensions 10 | *.so 11 | 12 | # Distribution / packaging 13 | .Python 14 | env/ 15 | build/ 16 | develop-eggs/ 17 | dist/ 18 | downloads/ 19 | eggs/ 20 | .eggs/ 21 | lib64/ 22 | parts/ 23 | sdist/ 24 | var/ 25 | *.egg-info/ 26 | .installed.cfg 27 | *.egg 28 | 29 | # PyInstaller 30 | # Usually these files are written by a python script from a template 31 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 32 | *.manifest 33 | *.spec 34 | 35 | # Installer logs 36 | pip-log.txt 37 | pip-delete-this-directory.txt 38 | 39 | # Unit test / coverage reports 40 | Ghtmlcov/ 41 | .tox/ 42 | .coverage 43 | .coverage.* 44 | .cache 45 | nosetests.xml 46 | coverage.xml 47 | *,cover 48 | 49 | # Translations 50 | *.mo 51 | *.pot 52 | 53 | # Django stuff: 54 | *.log 55 | 56 | # Sphinx documentation 57 | docs/_build/ 58 | 59 | # PyBuilder 60 | target/ 61 | 62 | 63 | # custom 64 | tmp/ 65 | app/static/tmp/ 66 | app/static/upload/*.ipa 67 | *.db 68 | docSet* 69 | utils/*.xlsx 70 | *.DS_Store 71 | 72 | -------------------------------------------------------------------------------- /utils/utils.py: -------------------------------------------------------------------------------- 1 | #coding=utf-8 2 | ''' 3 | Created on 2015年11月3日 4 | 5 | @author: atool 6 | ''' 7 | import os, sys 8 | from config import class_dump_z_path 9 | import time 10 | import datetime 11 | import random 12 | 13 | 14 | def get_system(): 15 | ''' 16 | get system platform, to define use which class-dump-z 17 | ''' 18 | system_platform = sys.platform 19 | if system_platform.startswith('linux'): 20 | return 'linux' 21 | elif system_platform.startswith('win32'): 22 | return 'win' 23 | elif system_platform.startswith('darwin'): 24 | return 'mac' 25 | else: 26 | return 'iphone' 27 | 28 | 29 | def get_clas_dump_path(use_what = 'class-dump'): 30 | ''' 31 | get class-dump-z path 32 | ''' 33 | if use_what == 'class-dump': 34 | cur_dir = os.getcwd() 35 | return os.path.join(cur_dir, 'class-dump') 36 | else: 37 | system = get_system() 38 | return class_dump_z_path.get(system, 'class-dump-z') 39 | 40 | def get_unique_str(): 41 | #随机的名字,可以用于上传文件等等不重复,但有一定时间意义的名字 42 | datetime_str = time.strftime('%Y%m%d%H%M%S',time.localtime()) 43 | return datetime_str + str(datetime.datetime.now().microsecond / 1000) + str(random.randint(0, 1000)) -------------------------------------------------------------------------------- /db/dsidx_dbs.py: -------------------------------------------------------------------------------- 1 | #coding=utf-8 2 | ''' 3 | Created on 2015年10月27日 4 | .dsidx 文件解读 5 | @author: atool 6 | ''' 7 | from db.sqlite_utils import SqliteHandler 8 | def get_dsidx_apis(db_path): 9 | ''' 10 | has document apis 11 | info:获得带文档的api。(/Users/Test/Library/Developer/Shared/Documentation/DocSets/com.apple.adc.documentation.AppleiOS7.0.iOSLibrary.docset/Contents/Resources),在里面有个docSet.dsidx的文件,这就是Xcode针对api做的数据库,从这里可以获得带文档的api的各种信息了,从而有了带文档的api集合set_B。 12 | 13 | ''' 14 | sql = "SELECT T.Z_PK, T.ZTOKENNAME, T.ZTOKENTYPE, T.ZCONTAINER, M.ZDECLAREDIN FROM ZTOKEN AS T, ZTOKENMETAINFORMATION AS M WHERE ZTOKENTYPE IN (3,9,12,13,16) AND T.Z_PK = M.ZTOKEN" 15 | apiset = SqliteHandler(db = db_path).exec_select(sql) 16 | return apiset 17 | 18 | 19 | def get_container_name(Z_PK, db_path): 20 | sql = "SELECT ZCONTAINERNAME FROM ZCONTAINER WHERE Z_PK = ?;" 21 | container = SqliteHandler(db = db_path).exec_select_one(sql, (Z_PK, )) 22 | if container: 23 | return container['ZCONTAINERNAME'] 24 | return None 25 | 26 | 27 | def get_framework_and_header(Z_PK, db_path): 28 | sql = "SELECT ZFRAMEWORKNAME, ZHEADERPATH FROM ZHEADER WHERE Z_PK = ?;" 29 | rst = SqliteHandler(db = db_path).exec_select_one(sql, (Z_PK, )) 30 | return rst 31 | -------------------------------------------------------------------------------- /utils/lib/xlsxwriter/compatibility.py: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # 3 | # Python 2/3 compatibility functions for XlsxWriter. 4 | # 5 | # Copyright (c), 2013-2015, John McNamara, jmcnamara@cpan.org 6 | # 7 | 8 | import sys 9 | from decimal import Decimal 10 | 11 | try: 12 | # For compatibility between Python 2 and 3. 13 | from StringIO import StringIO 14 | except ImportError: 15 | from io import StringIO 16 | 17 | try: 18 | # For Python 2.6+. 19 | from fractions import Fraction 20 | except ImportError: 21 | Fraction = float 22 | 23 | try: 24 | # For Python 2.6+. 25 | from collections import defaultdict 26 | from collections import namedtuple 27 | except ImportError: 28 | # For Python 2.5 support. 29 | from .compat_collections import defaultdict 30 | from .compat_collections import namedtuple 31 | 32 | # Types to check in Python 2/3. 33 | if sys.version_info[0] == 2: 34 | num_types = (float, int, long, Decimal, Fraction) 35 | str_types = basestring 36 | else: 37 | num_types = (float, int, Decimal, Fraction) 38 | str_types = str 39 | 40 | 41 | if sys.version_info < (2, 6, 0): 42 | from StringIO import StringIO as BytesIO 43 | else: 44 | from io import BytesIO as BytesIO 45 | -------------------------------------------------------------------------------- /config.py: -------------------------------------------------------------------------------- 1 | #coding=utf-8 2 | ''' 3 | Created on 2015年10月27日 4 | 5 | @author: atool 6 | ''' 7 | import os 8 | 9 | mysql_info = { 10 | 'HOST': '127.0.0.1', 11 | 'PORT': 3306, 12 | 'USERNAME': 'root', 13 | 'PASSWORD': 'root', 14 | 'CHARTSET': 'UTF8', 15 | 'DB': 'ios_private', 16 | } 17 | 18 | sqlite_info = { 19 | 'DB': 'ios_private.db', 20 | } 21 | 22 | #class_dump_z的路径 23 | cur_dir = os.getcwd() 24 | 25 | class_dump_z_path = { 26 | 'iphone': os.path.join(cur_dir, 'class_dump_z/iphone_armv6/class-dump-z'), 27 | 'linux': os.path.join(cur_dir, 'class_dump_z/linux_x86/class-dump-z'), 28 | 'mac': os.path.join(cur_dir, 'class_dump_z/mac_x86/class-dump-z'), 29 | 'win': os.path.join(cur_dir, 'class_dump_z/win_x86/class-dump-z') 30 | } 31 | 32 | #配置各个不同sdk版本的framework目录, 33 | sdks_config = [] 34 | 35 | sdks_config.append({ 36 | 'sdk': '8.1', 37 | 'framework': '/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator8.1.sdk/System/Library/Frameworks/', 38 | 'private_framework': '/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator8.1.sdk/System/Library/PrivateFrameworks/', 39 | 'docset': '/Applications/Xcode.app/Contents/Developer/Documentation/DocSets/com.apple.adc.documentation.AppleiOS8.1.iOSLibrary.docset/Contents/Resources/docSet.dsidx' 40 | }) -------------------------------------------------------------------------------- /app/wraps/mysql_escape_warp.py: -------------------------------------------------------------------------------- 1 | #coding=utf-8 2 | ''' 3 | Created on 2015年3月30日 4 | 5 | @author: atool 6 | ''' 7 | 8 | from functools import wraps 9 | import types 10 | 11 | import MySQLdb 12 | 13 | #对字典经典转义 14 | def _str_escape(s, d): 15 | if s == None: 16 | return '' 17 | return MySQLdb.escape_string(s) 18 | 19 | def _no_escape(s, d): 20 | if s == None: 21 | return '' 22 | return s 23 | 24 | def mysql_escape(f): 25 | @wraps(f) 26 | def decorated_function(*args, **kwargs): 27 | newargs = [] 28 | #先转义参数,再执行方法 29 | for arg in args: 30 | #字符串,包括中文 31 | if type(arg) is types.StringType or type(arg) is types.UnicodeType: 32 | newargs.append(MySQLdb.escape_string(arg)) 33 | 34 | #字典 35 | elif isinstance(arg, dict): 36 | newargs.append(MySQLdb.escape_dict(arg, { 37 | types.StringType: _str_escape, 38 | types.UnicodeType: _str_escape, 39 | types.IntType: _no_escape, 40 | types.FloatType: _no_escape 41 | })) 42 | #其他类型不转义 43 | else: 44 | newargs.append(arg) 45 | 46 | newargs = tuple(newargs) 47 | 48 | func = f(*newargs, **kwargs) 49 | 50 | return func 51 | return decorated_function -------------------------------------------------------------------------------- /app/wraps/db_connect_warp.py: -------------------------------------------------------------------------------- 1 | #coding=utf-8 2 | ''' 3 | Created on 2015年8月21日 4 | 数据库、redis连接装饰器,用于view上,给需要数据连接的加上相应的装饰器即可 5 | 保证在一次http连接过程中只需要进行一次mysql连接,减少内存占用,减少连接时间 6 | TODO 暂时不用 7 | @author: atool 8 | ''' 9 | 10 | from functools import wraps 11 | 12 | import MySQLdb 13 | from flask import g 14 | from app import db_config 15 | 16 | 17 | def _connect_db(): 18 | conn = MySQLdb.connect(host = db_config['DB_HOST'], 19 | user = db_config['DB_USER'], 20 | passwd = db_config['DB_PSW'], 21 | db = db_config['DB_NAME'], 22 | charset = db_config['DB_CHARSET']) 23 | #字典形式 24 | cursor = conn.cursor(cursorclass = MySQLdb.cursors.DictCursor) 25 | return (conn, cursor) 26 | 27 | #获取数据库连接装饰器 28 | def require_db_connection(f): 29 | @wraps(f) 30 | def decorated_function(*args, **kwargs): 31 | ####连接数据库 32 | if hasattr(g, 'conn') and g.conn != None and hasattr(g, 'cursor') and g.cursor != None: 33 | print 'has db connect, do nothing' 34 | else: 35 | (g.conn, g.cursor) = _connect_db() 36 | print 'create new db connect' 37 | 38 | #执行方法 39 | func = f(*args, **kwargs) 40 | 41 | ###关闭数据库连接 42 | if hasattr(g, 'conn') and g.conn != None and hasattr(g, 'cursor') and g.cursor != None: 43 | g.cursor.close() 44 | g.cursor = None 45 | g.conn.close() 46 | g.conn = None 47 | print 'close db connect' 48 | else: 49 | print 'no db connect, no need to close...' 50 | 51 | return func 52 | return decorated_function 53 | 54 | -------------------------------------------------------------------------------- /app/static/res/js/ios_private.js: -------------------------------------------------------------------------------- 1 | Dropzone.autoDiscover = false; 2 | var myDropzone = new Dropzone("#ipa_file", { 3 | url: "/ipa_post", 4 | maxFilesize: 1024, 5 | acceptedFiles: '.ipa', 6 | maxFiles: 5, 7 | success: function(d, data) { 8 | data = JSON.parse(data); 9 | if (data.success == 1) { 10 | //显示app信息 11 | $('#app_name').text(data.data.name); 12 | $('#version').text(data.data.version); 13 | $('#bundle_identifier').text(data.data.bundle_id); 14 | $('#target_os_version').text(data.data.tar_version); 15 | $('#minimum_os_version').text(data.data.min_version); 16 | //显示ipa的架构信息 17 | $('#app_arcs').text(data.data.arcs.join(' / ')); 18 | $('#minimum_os_version').text(data.data.min_version); 19 | $('#profile_type').text(data.data.profile_type); 20 | $('#expiration').text(data.data.expiration); 21 | //显示私有api信息 22 | $('#api_in_app div.api_section').remove(); 23 | for (var i = 0; i < data.data.methods_in_app.length; i++) { 24 | var api = data.data.methods_in_app[i]; 25 | var html = '
' + 26 | '
' + (i + 1) + '、' + api.api_name + '
' + 27 | 'api is ' + api.type + ', IN sdk ' + api.sdk + '、' + api.framework + ' -> ' + api.header_file + ' -> ' + api.class_name + ' -> '+ api.api_name + 28 | '
'; 29 | $('#api_append_div').append(html); 30 | }; 31 | $('#framework_in_app div.api_section').remove(); 32 | for (var i = 0; i < data.data.private_framework.length; i++) { 33 | var framework = data.data.private_framework[i]; 34 | var html = '
' + 35 | '
' + (i + 1) + '、' + framework + '
' + 36 | '
'; 37 | $('#framework_append_div').append(html); 38 | }; 39 | } 40 | else { 41 | alert(data.data); 42 | } 43 | } 44 | }); -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## iOS私有API检查工具 ## 2 | 3 | 私有API检查的原因在于:苹果在app提审的时候,会检查app使用私有api的情况,对于使用了私有api的app,不予通过,这个工具的目地就是在提审之前检查一下,提高通过率。 4 | 5 | ***PS:代码写的一般,主要是在做工具功能,欢迎PR。*** 6 | 7 | ### 一、功能 ### 8 | 9 | 目前功能主要有以下: 10 | 11 | 1. 从ipa中提取一些基本信息,例如app名字,sdk版本,包名等,可以辅助QA日常工作。 12 | 2. ipa架构检查,可以看出是否支持64位架构,可以辅助AppStore提审。 13 | 3. ipa使用私有api情况,可以辅助AppStore提审。 14 | 4. ipa info和provision配置项的检查,获得授权设备udid(参考项目iOS-checkIPA)。 15 | 5. 获取签名信息。 16 | 6. 批量检查APP,并生成excel报告,截图见下方。 17 | 18 | ### 二、如何使用 ### 19 | 20 | #### 1. 构建私有api库 #### 21 | 22 | - db/dsidx_dbs.py文件为解析docSet.dsidx的库,请实现将docSet.dsidx内容导出到sqlite中。docSet.dsidx是xcode作为代码提示的数据库,表示是apple公开的公有api。 23 | 24 | - 修改config.py中sdks_config字典,增加各个version的sdk路径,然后运行build_api_db.py,会自动解析私有api,存存储到sqlite中。 25 | 26 | - (项目中的数据库内容是我编译sdk7.0的数据,可以直接用。) 27 | 28 | 29 | #### 2. 检查ipa私有api #### 30 | 31 | 运行方式有二,建议第二种web方式: 32 | 33 | 1. 修改iOS_private.py main方法中的ipa路径,运行即可。 34 | 35 | 2. 使用Web上传运行的方式,运行python run_web.py(请先配置flask运行环境),然后浏览器输入127.0.0.1:9527 将ipa拖入上传框等待即可看到检查结果。 36 | 37 | 3. 使用batch_check方法批量运行目录中的ipa,并生成excel报告。 38 | 39 | ### 三、Screenshot ### 40 | 41 | - 网页检查展示 42 | 43 | ![web_screenshot](screenshot/web_screenshot.png) 44 | 45 | - 批量检测生成excel报告概要 46 | 47 | ![excel_report_outline](screenshot/excel_report_outline.png) 48 | 49 | - excel报告详细页 50 | 51 | ![excel_report_detail](screenshot/excel_report_detail.png) 52 | 53 | ### 四、参考项目 ### 54 | 55 | - [iOS-private-api-scanner](https://github.com/mrmign/iOS-private-api-scanner) 56 | - [RuntimeBrowser](https://github.com/nst/RuntimeBrowser/tree/master/tools/ios_headers_history) 57 | - [XlsxWriter](https://github.com/jmcnamara/XlsxWriter) 58 | - [iOS-checkIPA](https://github.com/apperian/iOS-checkIPA) 59 | - [iOS-api-scan.md](https://github.com/mrmign/iOS-private-api-scanner/blob/master/iOS-api-scan.md) 60 | 61 | 62 | ### 五、Note ### 63 | 64 | 1. `私有的api = (class-dump Framework下的库生成的头文件中的api - (Framework下的头文件里的api = 有文档的api + 没有文档的api)) + PrivateFramework下的api`。 65 | 2. 私有api在公开的Framework及私有的PrivateFramework都有。 66 | 3. 请暂时暂mac上运行,linux上暂时没有找到合适的、代替otool的工具,求推荐^^! 67 | 68 | 69 | ## License 70 | 71 | This code is distributed under the terms and conditions of the GPL v2 license. 72 | -------------------------------------------------------------------------------- /app/dbs/inc/Mongo.py: -------------------------------------------------------------------------------- 1 | #coding=utf-8 2 | ''' 3 | Created on 2015年8月21日 4 | Mongo db 操作类 5 | TODO Test 6 | @author: atool 7 | ''' 8 | from app import db_config 9 | import pymongo 10 | 11 | 12 | class Mongo(): 13 | 14 | #类变量 15 | DESC = pymongo.DESCENDING 16 | ASC = pymongo.ASCENDING 17 | 18 | def __init__(self, host = db_config['DB_HOST'], 19 | port = db_config['DB_PORT'], 20 | user = db_config['DB_USER'], 21 | passwd = db_config['DB_PSW'], 22 | db = db_config['DB_NAME'], 23 | charset = db_config['DB_CHARSET']): 24 | 25 | self.connection = pymongo.Connection(host, port) 26 | self.db = self.connection[db] 27 | self.db.authenticate(user, passwd) 28 | 29 | 30 | def insert(self, table, params): 31 | return self.db[table].insert(params) 32 | 33 | def save(self, table, params): 34 | return self.db[table].save(params) 35 | 36 | def find(self, table, params = {}, sort = {}, skip = 0, limit = 25): 37 | if skip == -1: 38 | skip = 0 39 | 40 | if limit < 0: 41 | return self.db[table].find(params).sort(sort).skip(skip) 42 | return self.db[table].find(params).sort(sort).limit(limit).skip(skip) 43 | 44 | def count(self, table, params): 45 | return self.db[table].find(params).count() 46 | 47 | def find_one(self, table, params = {}): 48 | return self.db[table].find_one(params) 49 | 50 | def remove(self, table, params): 51 | return self.db[table].remove(params) 52 | 53 | def update(self, table, params): 54 | return self.db[table].update(params) 55 | 56 | #高级应用 57 | #1. 分页 58 | def get_page_count(self, table, params = {}, page_size = 25): 59 | if page_size < 0: 60 | page_size = 25 61 | 62 | total_cnt = self.count(table, params) 63 | return (total_cnt - 1) / 25 + 1 64 | 65 | def find_page(self, table, params = {}, sort = {}, page = 1, page_size = 25): 66 | page_count = self.get_page_count(table, params, page_size) 67 | limit = page_size 68 | 69 | if page <= 0: 70 | page = 1 71 | 72 | if page > page_count: 73 | page = page_count 74 | 75 | skip = (page - 1) * page_size 76 | 77 | return self.find(table, params, sort, skip, limit) 78 | 79 | 80 | #test 81 | if __name__ == '__main__': 82 | #TODO 83 | pass -------------------------------------------------------------------------------- /db/other_dbs.py: -------------------------------------------------------------------------------- 1 | #coding=utf-8 2 | ''' 3 | Created on 2015年10月27日 4 | 5 | @author: atool 6 | ''' 7 | from db.sqlite_utils import SqliteHandler 8 | def create_some_table(): 9 | #从public framework 中dump出来的所有api,其中包含部分私有api(sql7) 10 | sql1 = ("create table framework_dump_apis(" 11 | "api_name varchar," 12 | "class_name varchar," 13 | "type varchar," 14 | "header_file varchar," 15 | "sdk varchar," 16 | "framework varchar)") 17 | 18 | #从public framework .h文件中解析代码解析出来的api 19 | sql2 = ("create table framework_header_apis(" 20 | "api_name varchar," 21 | "class_name varchar," 22 | "type varchar," 23 | "header_file varchar," 24 | "sdk varchar," 25 | "framework varchar)") 26 | 27 | #有文档的pi 28 | sql3 = ("create table document_apis(" 29 | "api_name varchar," 30 | "class_name varchar," 31 | "type varchar," 32 | "header_file varchar," 33 | "sdk varchar," 34 | "framework varchar)") 35 | 36 | #sql2 - sql3 37 | sql4 = ("create table undocument_apis(" 38 | "api_name varchar," 39 | "class_name varchar," 40 | "type varchar," 41 | "header_file varchar," 42 | "sdk varchar," 43 | "framework varchar)") 44 | 45 | #包括sql6,sql7的所有内容 46 | sql5 = ("create table private_apis(" 47 | "api_name varchar," 48 | "class_name varchar," 49 | "type varchar," 50 | "header_file varchar," 51 | "sdk varchar," 52 | "framework varchar)") 53 | 54 | #private framework dump出来的api,全部为私有api 55 | sql6 = ("create table private_framework_dump_apis(" 56 | "api_name varchar," 57 | "class_name varchar," 58 | "type varchar," 59 | "header_file varchar," 60 | "sdk varchar," 61 | "framework varchar)") 62 | 63 | sql7 = ("create table framework_private_apis(" 64 | "api_name varchar," 65 | "class_name varchar," 66 | "type varchar," 67 | "header_file varchar," 68 | "sdk varchar," 69 | "framework varchar)") 70 | 71 | sql8 = ("create table whitelist(" 72 | "api_name varchar," 73 | "class_name varchar," 74 | "type varchar," 75 | "header_file varchar," 76 | "sdk varchar," 77 | "framework varchar)") 78 | SqliteHandler().exec_sql(sql1, ()) 79 | SqliteHandler().exec_sql(sql2, ()) 80 | SqliteHandler().exec_sql(sql3, ()) 81 | SqliteHandler().exec_sql(sql4, ()) 82 | SqliteHandler().exec_sql(sql5, ()) 83 | SqliteHandler().exec_sql(sql6, ()) 84 | SqliteHandler().exec_sql(sql7, ()) 85 | SqliteHandler().exec_sql(sql8, ()) 86 | 87 | -------------------------------------------------------------------------------- /app/views/main_views.py: -------------------------------------------------------------------------------- 1 | #coding=utf-8 2 | ''' 3 | Created on 2015年6月16日 4 | 5 | @author: atool 6 | ''' 7 | from app import app 8 | from app.utils import StringUtil, PathUtil, OtherUtil 9 | import flask 10 | from flask.globals import request 11 | from werkzeug.utils import secure_filename 12 | import os, shutil 13 | import iOS_private 14 | 15 | @app.route('/', methods=['GET']) 16 | def index_page(): 17 | return flask.render_template('main/index_page.html') 18 | 19 | 20 | allow_ext = ['ipa'] 21 | #ipa上传 22 | @app.route('/ipa_post', methods=['POST']) 23 | def ipa_post(): 24 | rst = {} 25 | pid = StringUtil.get_unique_str() 26 | ipa_path = None 27 | try: 28 | upload_file = request.files['file'] 29 | fname = secure_filename(upload_file.filename) 30 | suffix_name = fname.split('.')[-1] 31 | #文件后缀名不对时,不做存储处理 32 | if not suffix_name in allow_ext: 33 | rst['success'] = 0 34 | rst['success'] = 'file ext is not allowed' 35 | else: 36 | #为图片名称添加时间戳,防止不同文件同名 37 | fname = pid + '.' + suffix_name 38 | ipa_path = os.path.join(PathUtil.upload_dir(), fname) 39 | upload_file.save(ipa_path) 40 | rst['success'] = 1 41 | rst['data'] = {} 42 | #获得ipa信息 43 | rsts = iOS_private.check_app_info_and_provision(ipa_path) 44 | for key in rsts.keys(): 45 | rst['data'][key] = rsts[key] 46 | # ipa_parse = IpaParse.IpaParse(ipa_path) 47 | # rst['data']['version'] = ipa_parse.version() 48 | # rst['data']['bundle_identifier'] = ipa_parse.bundle_identifier() 49 | # rst['data']['target_os_version'] = ipa_parse.target_os_version() 50 | # rst['data']['minimum_os_version'] = ipa_parse.minimum_os_version() 51 | #检查ios私有api 52 | app = iOS_private.get_executable_path(ipa_path, pid) 53 | print 'app', app 54 | methods_in_app, methods_not_in_app, private = iOS_private.check_private_api(app, pid) 55 | rst['data']['methods_in_app'] = methods_in_app 56 | rst['data']['private_framework'] = list(private) 57 | #检查ipa 64支持情况 58 | arcs = iOS_private.check_architectures(app) 59 | rst['data']['arcs'] = arcs 60 | 61 | except Exception, e: 62 | print e 63 | rst['success'] = 0 64 | rst['data'] = '检查失败,也许上传的包并非真正的ipa,或者系统出现错误!' 65 | 66 | if ipa_path and os.path.exists(ipa_path): 67 | os.remove(ipa_path) #删除上传的包 68 | 69 | cur_dir = os.getcwd() #删除检查临时目录 70 | dest_tmp = os.path.join(cur_dir, 'tmp/' + pid) 71 | if os.path.exists(dest_tmp): 72 | shutil.rmtree(dest_tmp) 73 | #print rst 74 | return OtherUtil.object_2_dict(rst) 75 | 76 | #定义404页面 77 | @app.errorhandler(404) 78 | def page_not_found(error): 79 | return '404' 80 | 81 | @app.errorhandler(502) 82 | def server_502_error(error): 83 | return '502' 84 | 85 | 86 | @app.route('/not_allow', methods=['GET']) 87 | def deny(error): 88 | return 'You IP address is not in white list...' -------------------------------------------------------------------------------- /utils/lib/xlsxwriter/chart_radar.py: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # 3 | # ChartRadar - A class for writing the Excel XLSX Radar charts. 4 | # 5 | # Copyright 2013-2015, John McNamara, jmcnamara@cpan.org 6 | # 7 | 8 | from . import chart 9 | 10 | 11 | class ChartRadar(chart.Chart): 12 | """ 13 | A class for writing the Excel XLSX Radar charts. 14 | 15 | 16 | """ 17 | 18 | ########################################################################### 19 | # 20 | # Public API. 21 | # 22 | ########################################################################### 23 | 24 | def __init__(self, options=None): 25 | """ 26 | Constructor. 27 | 28 | """ 29 | super(ChartRadar, self).__init__() 30 | 31 | if options is None: 32 | options = {} 33 | 34 | self.subtype = options.get('subtype') 35 | 36 | if not self.subtype: 37 | self.subtype = 'marker' 38 | self.default_marker = {'type': 'none'} 39 | 40 | # Override and reset the default axis values. 41 | self.x_axis['defaults']['major_gridlines'] = {'visible': 1} 42 | self.set_x_axis({}) 43 | 44 | # Set the available data label positions for this chart type. 45 | self.label_position_default = 'center' 46 | self.label_positions = {'center': 'ctr'} 47 | 48 | # Hardcode major_tick_mark for now until there is an accessor. 49 | self.y_axis['major_tick_mark'] = 'cross' 50 | 51 | ########################################################################### 52 | # 53 | # Private API. 54 | # 55 | ########################################################################### 56 | 57 | def _write_chart_type(self, args): 58 | # Write the c:radarChart element. 59 | self._write_radar_chart(args) 60 | 61 | ########################################################################### 62 | # 63 | # XML methods. 64 | # 65 | ########################################################################### 66 | 67 | def _write_radar_chart(self, args): 68 | # Write the element. 69 | 70 | if args['primary_axes']: 71 | series = self._get_primary_axes_series() 72 | else: 73 | series = self._get_secondary_axes_series() 74 | 75 | if not len(series): 76 | return 77 | 78 | self._xml_start_tag('c:radarChart') 79 | 80 | # Write the c:radarStyle element. 81 | self._write_radar_style() 82 | 83 | # Write the series elements. 84 | for data in series: 85 | self._write_ser(data) 86 | 87 | # Write the c:axId elements 88 | self._write_axis_ids(args) 89 | 90 | self._xml_end_tag('c:radarChart') 91 | 92 | def _write_radar_style(self): 93 | # Write the element. 94 | val = 'marker' 95 | 96 | if self.subtype == 'filled': 97 | val = 'filled' 98 | 99 | attributes = [('val', val)] 100 | 101 | self._xml_empty_tag('c:radarStyle', attributes) 102 | -------------------------------------------------------------------------------- /utils/lib/xlsxwriter/chart_area.py: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # 3 | # ChartArea - A class for writing the Excel XLSX Area charts. 4 | # 5 | # Copyright 2013-2015, John McNamara, jmcnamara@cpan.org 6 | # 7 | 8 | from . import chart 9 | 10 | 11 | class ChartArea(chart.Chart): 12 | """ 13 | A class for writing the Excel XLSX Area charts. 14 | 15 | 16 | """ 17 | 18 | ########################################################################### 19 | # 20 | # Public API. 21 | # 22 | ########################################################################### 23 | 24 | def __init__(self, options=None): 25 | """ 26 | Constructor. 27 | 28 | """ 29 | super(ChartArea, self).__init__() 30 | 31 | if options is None: 32 | options = {} 33 | 34 | self.subtype = options.get('subtype') 35 | 36 | if not self.subtype: 37 | self.subtype = 'standard' 38 | 39 | self.cross_between = 'midCat' 40 | self.show_crosses = 0 41 | 42 | # Override and reset the default axis values. 43 | if self.subtype == 'percent_stacked': 44 | self.y_axis['defaults']['num_format'] = '0%' 45 | 46 | # Set the available data label positions for this chart type. 47 | self.label_position_default = 'center' 48 | self.label_positions = {'center': 'ctr'} 49 | 50 | self.set_y_axis({}) 51 | 52 | ########################################################################### 53 | # 54 | # Private API. 55 | # 56 | ########################################################################### 57 | 58 | def _write_chart_type(self, args): 59 | # Override the virtual superclass method with a chart specific method. 60 | # Write the c:areaChart element. 61 | self._write_area_chart(args) 62 | 63 | ########################################################################### 64 | # 65 | # XML methods. 66 | # 67 | ########################################################################### 68 | # 69 | def _write_area_chart(self, args): 70 | # Write the element. 71 | 72 | if args['primary_axes']: 73 | series = self._get_primary_axes_series() 74 | else: 75 | series = self._get_secondary_axes_series() 76 | 77 | if not len(series): 78 | return 79 | 80 | subtype = self.subtype 81 | 82 | if subtype == 'percent_stacked': 83 | subtype = 'percentStacked' 84 | 85 | self._xml_start_tag('c:areaChart') 86 | 87 | # Write the c:grouping element. 88 | self._write_grouping(subtype) 89 | 90 | # Write the series elements. 91 | for data in series: 92 | self._write_ser(data) 93 | 94 | # Write the c:dropLines element. 95 | self._write_drop_lines() 96 | 97 | # Write the c:marker element. 98 | self._write_marker_value() 99 | 100 | # Write the c:axId elements 101 | self._write_axis_ids(args) 102 | 103 | self._xml_end_tag('c:areaChart') 104 | -------------------------------------------------------------------------------- /utils/lib/xlsxwriter/chart_doughnut.py: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # 3 | # ChartDoughnut - A class for writing the Excel XLSX Doughnut charts. 4 | # 5 | # Copyright 2013-2015, John McNamara, jmcnamara@cpan.org 6 | # 7 | 8 | from warnings import warn 9 | from . import chart_pie 10 | 11 | 12 | class ChartDoughnut(chart_pie.ChartPie): 13 | """ 14 | A class for writing the Excel XLSX Doughnut charts. 15 | 16 | 17 | """ 18 | 19 | ########################################################################### 20 | # 21 | # Public API. 22 | # 23 | ########################################################################### 24 | 25 | def __init__(self, options=None): 26 | """ 27 | Constructor. 28 | 29 | """ 30 | super(ChartDoughnut, self).__init__() 31 | 32 | if options is None: 33 | options = {} 34 | 35 | self.vary_data_color = 1 36 | self.rotation = 0 37 | self.hole_size = 50 38 | 39 | def set_hole_size(self, size): 40 | """ 41 | Set the Doughnut chart hole size. 42 | 43 | Args: 44 | size: 10 <= size <= 90. 45 | 46 | Returns: 47 | Nothing. 48 | 49 | """ 50 | if size is None: 51 | return 52 | 53 | # Ensure the size is in Excel's range. 54 | if size < 10 or size > 90: 55 | warn("Chart hole size %d outside Excel range: 10 <= size <= 90" 56 | % size) 57 | return 58 | 59 | self.hole_size = int(size) 60 | 61 | ########################################################################### 62 | # 63 | # Private API. 64 | # 65 | ########################################################################### 66 | 67 | def _write_chart_type(self, args): 68 | # Override the virtual superclass method with a chart specific method. 69 | # Write the c:doughnutChart element. 70 | self._write_doughnut_chart(args) 71 | 72 | ########################################################################### 73 | # 74 | # XML methods. 75 | # 76 | ########################################################################### 77 | 78 | def _write_doughnut_chart(self, args): 79 | # Write the element. Over-ridden method to remove 80 | # axis_id code since Doughnut charts don't require val and cat axes. 81 | self._xml_start_tag('c:doughnutChart') 82 | 83 | # Write the c:varyColors element. 84 | self._write_vary_colors() 85 | 86 | # Write the series elements. 87 | for data in self.series: 88 | self._write_ser(data) 89 | 90 | # Write the c:firstSliceAng element. 91 | self._write_first_slice_ang() 92 | 93 | # Write the c:holeSize element. 94 | self._write_c_hole_size() 95 | 96 | self._xml_end_tag('c:doughnutChart') 97 | 98 | def _write_c_hole_size(self): 99 | # Write the element. 100 | attributes = [('val', self.hole_size)] 101 | 102 | self._xml_empty_tag('c:holeSize', attributes) 103 | -------------------------------------------------------------------------------- /db/api_dbs.py: -------------------------------------------------------------------------------- 1 | #coding=utf-8 2 | ''' 3 | Created on 2015年10月27日 4 | 5 | @author: atool 6 | ''' 7 | from db.sqlite_utils import SqliteHandler 8 | 9 | #批量插入有文档的api 10 | def insert_apis(table_name, datas): 11 | sql = "insert into " + table_name + "(api_name, class_name, type, header_file, sdk, framework) values(:api_name, :class_name, :type, :header_file, :sdk, :framework)" 12 | return SqliteHandler().exec_insert_many(sql, datas) 13 | 14 | 15 | def delete_apis_by_sdk(table_name, sdk): 16 | sql = "delete from " + table_name + " where sdk = ?;" 17 | return SqliteHandler().exec_update(sql, (sdk, )) 18 | 19 | # private_apis = [] 20 | 21 | # def get_private_api_list(): 22 | # ''' 23 | # 缓存数据,批量检查的时候,减少sql io 24 | # ''' 25 | # global private_apis 26 | # if not private_apis: 27 | # sql = "select * from private_apis group by api_name having private_apis.api_name not in (select api_name from whitelist group by api_name);" 28 | # params = () 29 | # private_apis = SqliteHandler().exec_select(sql, params) 30 | 31 | # return private_apis 32 | 33 | def get_private_api_list(framework = None): 34 | framework_str = None 35 | if framework and len(framework) > 0: 36 | framework_str = '(' 37 | for f in framework: 38 | framework_str = framework_str + "'" + f + "', " 39 | 40 | framework_str = framework_str[0:-2] 41 | framework_str = framework_str + ')' 42 | 43 | #有frame过滤条件 44 | if framework_str: 45 | sql = "select * from private_apis group by api_name having private_apis.framework in " + framework_str + " and private_apis.api_name not in (select api_name from whitelist group by api_name);" 46 | params = () 47 | else: 48 | sql = "select * from private_apis group by api_name having private_apis.api_name not in (select api_name from whitelist group by api_name);" 49 | params = () 50 | private_apis = SqliteHandler().exec_select(sql, params) 51 | return private_apis 52 | 53 | white_apis = [] 54 | def get_white_api_list(): 55 | ''' 56 | 白名单中的api 57 | 缓存数据,批量检查的时候,减少sql io 58 | ''' 59 | global white_apis 60 | if not white_apis: 61 | sql = "select * from whitelist group by api_name;" 62 | params = () 63 | private_apis = SqliteHandler().exec_select(sql, params) 64 | 65 | return private_apis 66 | 67 | #获得所有的私有框架dump出来的api 68 | def get_private_framework_dump_apis(sdk): 69 | sql = "select * from private_framework_dump_apis where sdk = ?" 70 | params = (sdk, ) 71 | return SqliteHandler().exec_select(sql, params) 72 | 73 | #获得所有的共有框架dump出来的api 74 | def get_framework_dump_apis(sdk): 75 | sql = "select * from framework_dump_apis where sdk = ?" 76 | params = (sdk, ) 77 | return SqliteHandler().exec_select(sql, params) 78 | 79 | def get_framework_private_apis(): 80 | sql = "select * from framework_private_apis group by api_name;" 81 | params = () 82 | return SqliteHandler().exec_select(sql, params) 83 | 84 | 85 | def is_api_exist_in(table_name, api): 86 | sql = "select * from " + table_name + " where api_name = ? and class_name = ? and sdk = ?;" 87 | params = (api['api_name'], api['class_name'], api['sdk']) 88 | return SqliteHandler().exec_select_one(sql, params) -------------------------------------------------------------------------------- /db/sqlite_utils.py: -------------------------------------------------------------------------------- 1 | #coding=utf-8 2 | ''' 3 | Created on 2015年10月27日 4 | 数据库操作类 5 | @author: atool 6 | ''' 7 | import sqlite3 8 | from config import sqlite_info 9 | 10 | def dict_factory(cursor, row): 11 | d = {} 12 | for idx, col in enumerate(cursor.description): 13 | d[col[0]] = row[idx] 14 | return d 15 | 16 | 17 | class SqliteHandler(): 18 | #对象属性 19 | #连接 20 | conn = None 21 | #数据游标 22 | cursor = None 23 | 24 | #构造函数 25 | def __init__(self, db = sqlite_info['DB']): 26 | self.db = db 27 | self.__connect() 28 | 29 | def __connect(self): 30 | try: 31 | self.conn = sqlite3.connect(self.db) 32 | self.conn.row_factory = dict_factory 33 | self.cursor = self.conn.cursor() 34 | except Exception, e: 35 | print e 36 | 37 | def close(self): 38 | try: 39 | self.cursor.close() 40 | self.conn.close() 41 | except: 42 | pass 43 | 44 | def exec_select(self, sql, params = ()): 45 | ''' 46 | ps:执行查询类型的sql语句 47 | ''' 48 | try: 49 | self.cursor.execute(sql, params) 50 | result_set = self.cursor.fetchall() 51 | return result_set 52 | except Exception, e: 53 | print e 54 | return False 55 | 56 | def exec_select_one(self, sql, params = ()): 57 | ''' 58 | ps:执行查询类型的sql语句 59 | ''' 60 | try: 61 | self.cursor.execute(sql, params) 62 | result_set = self.cursor.fetchone() 63 | return result_set 64 | except Exception, e: 65 | print e 66 | return False 67 | 68 | def exec_insert(self, sql, params = ()): 69 | ''' 70 | ps:执行插入类sql语句 71 | ''' 72 | try: 73 | # 执行sql语句 74 | self.cursor.execute(sql, params) 75 | # 提交到数据库执行 76 | insert_id = self.cursor.lastrowid 77 | self.conn.commit() 78 | return insert_id 79 | except Exception as e: 80 | print e 81 | self.conn.rollback() 82 | return False 83 | 84 | def exec_insert_many(self, sql, datas): 85 | try: 86 | # 执行sql语句 87 | self.cursor.executemany(sql, datas) 88 | # 提交到数据库执行 89 | row_count = self.cursor.rowcount 90 | self.conn.commit() 91 | return row_count 92 | except Exception, e: 93 | print e 94 | self.conn.rollback() 95 | return False 96 | 97 | def exec_update(self, sql, params = ()): 98 | ''' 99 | ps:执行更新类sql语句 100 | ''' 101 | try: 102 | # 执行sql语句 103 | self.cursor.execute(sql, params) 104 | row_count = self.cursor.rowcount 105 | # 提交到数据库执行 106 | self.conn.commit() 107 | if row_count == False: 108 | row_count = True 109 | return row_count 110 | except Exception, e: 111 | print e 112 | self.conn.rollback() 113 | return False 114 | 115 | def exec_sql(self, sql, params = ()): 116 | try: 117 | # 执行sql语句 118 | self.cursor.execute(sql, params) 119 | # 提交到数据库执行 120 | self.conn.commit() 121 | return True 122 | except Exception, e: 123 | print e 124 | self.conn.rollback() 125 | return False -------------------------------------------------------------------------------- /utils/lib/xlsxwriter/chart_line.py: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # 3 | # ChartLine - A class for writing the Excel XLSX Line charts. 4 | # 5 | # Copyright 2013-2015, John McNamara, jmcnamara@cpan.org 6 | # 7 | 8 | from . import chart 9 | 10 | 11 | class ChartLine(chart.Chart): 12 | """ 13 | A class for writing the Excel XLSX Line charts. 14 | 15 | 16 | """ 17 | 18 | ########################################################################### 19 | # 20 | # Public API. 21 | # 22 | ########################################################################### 23 | 24 | def __init__(self, options=None): 25 | """ 26 | Constructor. 27 | 28 | """ 29 | super(ChartLine, self).__init__() 30 | 31 | if options is None: 32 | options = {} 33 | 34 | self.default_marker = {'type': 'none'} 35 | self.smooth_allowed = True 36 | 37 | # Set the available data label positions for this chart type. 38 | self.label_position_default = 'right' 39 | self.label_positions = { 40 | 'center': 'ctr', 41 | 'right': 'r', 42 | 'left': 'l', 43 | 'above': 't', 44 | 'below': 'b', 45 | # For backward compatibility. 46 | 'top': 't', 47 | 'bottom': 'b'} 48 | 49 | ########################################################################### 50 | # 51 | # Private API. 52 | # 53 | ########################################################################### 54 | 55 | def _write_chart_type(self, args): 56 | # Override the virtual superclass method with a chart specific method. 57 | # Write the c:lineChart element. 58 | self._write_line_chart(args) 59 | 60 | ########################################################################### 61 | # 62 | # XML methods. 63 | # 64 | ########################################################################### 65 | 66 | def _write_line_chart(self, args): 67 | # Write the element. 68 | 69 | if args['primary_axes']: 70 | series = self._get_primary_axes_series() 71 | else: 72 | series = self._get_secondary_axes_series() 73 | 74 | if not len(series): 75 | return 76 | 77 | self._xml_start_tag('c:lineChart') 78 | 79 | # Write the c:grouping element. 80 | self._write_grouping('standard') 81 | 82 | # Write the series elements. 83 | for data in series: 84 | self._write_ser(data) 85 | 86 | # Write the c:dropLines element. 87 | self._write_drop_lines() 88 | 89 | # Write the c:hiLowLines element. 90 | self._write_hi_low_lines() 91 | 92 | # Write the c:upDownBars element. 93 | self._write_up_down_bars() 94 | 95 | # Write the c:marker element. 96 | self._write_marker_value() 97 | 98 | # Write the c:axId elements 99 | self._write_axis_ids(args) 100 | 101 | self._xml_end_tag('c:lineChart') 102 | 103 | def _write_d_pt_point(self, index, point): 104 | # Write an individual element. Override the parent method to 105 | # add markers. 106 | 107 | self._xml_start_tag('c:dPt') 108 | 109 | # Write the c:idx element. 110 | self._write_idx(index) 111 | 112 | self._xml_start_tag('c:marker') 113 | 114 | # Write the c:spPr element. 115 | self._write_sp_pr(point) 116 | 117 | self._xml_end_tag('c:marker') 118 | 119 | self._xml_end_tag('c:dPt') 120 | -------------------------------------------------------------------------------- /utils/lib/xlsxwriter/relationships.py: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # 3 | # Relationships - A class for writing the Excel XLSX Worksheet file. 4 | # 5 | # Copyright 2013-2015, John McNamara, jmcnamara@cpan.org 6 | # 7 | 8 | # Package imports. 9 | from . import xmlwriter 10 | 11 | # Long namespace strings used in the class. 12 | schema_root = 'http://schemas.openxmlformats.org' 13 | package_schema = schema_root + '/package/2006/relationships' 14 | document_schema = schema_root + '/officeDocument/2006/relationships' 15 | 16 | 17 | class Relationships(xmlwriter.XMLwriter): 18 | """ 19 | A class for writing the Excel XLSX Relationships file. 20 | 21 | 22 | """ 23 | 24 | ########################################################################### 25 | # 26 | # Public API. 27 | # 28 | ########################################################################### 29 | 30 | def __init__(self): 31 | """ 32 | Constructor. 33 | 34 | """ 35 | 36 | super(Relationships, self).__init__() 37 | 38 | self.relationships = [] 39 | self.id = 1 40 | 41 | ########################################################################### 42 | # 43 | # Private API. 44 | # 45 | ########################################################################### 46 | 47 | def _assemble_xml_file(self): 48 | # Assemble and write the XML file. 49 | 50 | # Write the XML declaration. 51 | self._xml_declaration() 52 | 53 | self._write_relationships() 54 | 55 | # Close the file. 56 | self._xml_close() 57 | 58 | def _add_document_relationship(self, rel_type, target, target_mode=None): 59 | # Add container relationship to XLSX .rels xml files. 60 | rel_type = document_schema + rel_type 61 | 62 | self.relationships.append((rel_type, target, target_mode)) 63 | 64 | def _add_package_relationship(self, rel_type, target): 65 | # Add container relationship to XLSX .rels xml files. 66 | rel_type = package_schema + rel_type 67 | 68 | self.relationships.append((rel_type, target, None)) 69 | 70 | def _add_ms_package_relationship(self, rel_type, target): 71 | # Add container relationship to XLSX .rels xml files. Uses MS schema. 72 | schema = 'http://schemas.microsoft.com/office/2006/relationships' 73 | rel_type = schema + rel_type 74 | 75 | self.relationships.append((rel_type, target, None)) 76 | 77 | def _add_worksheet_relationship(self, rel_type, target, target_mode=None): 78 | # Add worksheet relationship to sheet.rels xml files. 79 | rel_type = document_schema + rel_type 80 | 81 | self.relationships.append((rel_type, target, target_mode)) 82 | 83 | ########################################################################### 84 | # 85 | # XML methods. 86 | # 87 | ########################################################################### 88 | 89 | def _write_relationships(self): 90 | # Write the element. 91 | attributes = [('xmlns', package_schema,)] 92 | 93 | self._xml_start_tag('Relationships', attributes) 94 | 95 | for relationship in self.relationships: 96 | self._write_relationship(relationship) 97 | 98 | self._xml_end_tag('Relationships') 99 | 100 | def _write_relationship(self, relationship): 101 | # Write the element. 102 | rel_type, target, target_mode = relationship 103 | 104 | attributes = [ 105 | ('Id', 'rId' + str(self.id)), 106 | ('Type', rel_type), 107 | ('Target', target), 108 | ] 109 | 110 | self.id += 1 111 | 112 | if target_mode: 113 | attributes.append(('TargetMode', target_mode)) 114 | 115 | self._xml_empty_tag('Relationship', attributes) 116 | -------------------------------------------------------------------------------- /utils/lib/xlsxwriter/chart_column.py: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # 3 | # ChartColumn - A class for writing the Excel XLSX Column charts. 4 | # 5 | # Copyright 2013-2015, John McNamara, jmcnamara@cpan.org 6 | # 7 | 8 | from . import chart 9 | 10 | 11 | class ChartColumn(chart.Chart): 12 | """ 13 | A class for writing the Excel XLSX Column charts. 14 | 15 | 16 | """ 17 | 18 | ########################################################################### 19 | # 20 | # Public API. 21 | # 22 | ########################################################################### 23 | 24 | def __init__(self, options=None): 25 | """ 26 | Constructor. 27 | 28 | """ 29 | super(ChartColumn, self).__init__() 30 | 31 | if options is None: 32 | options = {} 33 | 34 | self.subtype = options.get('subtype') 35 | 36 | if not self.subtype: 37 | self.subtype = 'clustered' 38 | 39 | self.horiz_val_axis = 0 40 | 41 | if self.subtype == 'percent_stacked': 42 | self.y_axis['defaults']['num_format'] = '0%' 43 | 44 | # Set the available data label positions for this chart type. 45 | self.label_position_default = 'outside_end' 46 | self.label_positions = { 47 | 'center': 'ctr', 48 | 'inside_base': 'inBase', 49 | 'inside_end': 'inEnd', 50 | 'outside_end': 'outEnd'} 51 | 52 | self.set_y_axis({}) 53 | 54 | ########################################################################### 55 | # 56 | # Private API. 57 | # 58 | ########################################################################### 59 | 60 | def _write_chart_type(self, args): 61 | # Override the virtual superclass method with a chart specific method. 62 | 63 | # Write the c:barChart element. 64 | self._write_bar_chart(args) 65 | 66 | def _write_bar_chart(self, args): 67 | # Write the element. 68 | 69 | if args['primary_axes']: 70 | series = self._get_primary_axes_series() 71 | else: 72 | series = self._get_secondary_axes_series() 73 | 74 | if not len(series): 75 | return 76 | 77 | subtype = self.subtype 78 | if subtype == 'percent_stacked': 79 | subtype = 'percentStacked' 80 | 81 | # Set a default overlap for stacked charts. 82 | if 'stacked' in self.subtype: 83 | if self.series_overlap_1 is None: 84 | self.series_overlap_1 = 100 85 | 86 | self._xml_start_tag('c:barChart') 87 | 88 | # Write the c:barDir element. 89 | self._write_bar_dir() 90 | 91 | # Write the c:grouping element. 92 | self._write_grouping(subtype) 93 | 94 | # Write the c:ser elements. 95 | for data in series: 96 | self._write_ser(data) 97 | 98 | # Write the c:marker element. 99 | self._write_marker_value() 100 | 101 | # Write the c:gapWidth element. 102 | if args['primary_axes']: 103 | self._write_gap_width(self.series_gap_1) 104 | else: 105 | self._write_gap_width(self.series_gap_2) 106 | 107 | # Write the c:overlap element. 108 | if args['primary_axes']: 109 | self._write_overlap(self.series_overlap_1) 110 | else: 111 | self._write_overlap(self.series_overlap_2) 112 | 113 | # Write the c:axId elements 114 | self._write_axis_ids(args) 115 | 116 | self._xml_end_tag('c:barChart') 117 | 118 | ########################################################################### 119 | # 120 | # XML methods. 121 | # 122 | ########################################################################### 123 | 124 | def _write_bar_dir(self): 125 | # Write the element. 126 | val = 'col' 127 | 128 | attributes = [('val', val)] 129 | 130 | self._xml_empty_tag('c:barDir', attributes) 131 | 132 | def _write_err_dir(self, val): 133 | # Overridden from Chart class since it is not used in Column charts. 134 | pass 135 | -------------------------------------------------------------------------------- /utils/lib/xlsxwriter/chart_stock.py: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # 3 | # ChartStock - A class for writing the Excel XLSX Stock charts. 4 | # 5 | # Copyright 2013-2015, John McNamara, jmcnamara@cpan.org 6 | # 7 | 8 | from . import chart 9 | 10 | 11 | class ChartStock(chart.Chart): 12 | """ 13 | A class for writing the Excel XLSX Stock charts. 14 | 15 | """ 16 | 17 | ########################################################################### 18 | # 19 | # Public API. 20 | # 21 | ########################################################################### 22 | 23 | def __init__(self, options=None): 24 | """ 25 | Constructor. 26 | 27 | """ 28 | super(ChartStock, self).__init__() 29 | 30 | if options is None: 31 | options = {} 32 | 33 | self.show_crosses = 0 34 | self.hi_low_lines = {} 35 | self.date_category = True 36 | 37 | # Override and reset the default axis values. 38 | self.x_axis['defaults']['num_format'] = 'dd/mm/yyyy' 39 | self.x2_axis['defaults']['num_format'] = 'dd/mm/yyyy' 40 | 41 | # Set the available data label positions for this chart type. 42 | self.label_position_default = 'right' 43 | self.label_positions = { 44 | 'center': 'ctr', 45 | 'right': 'r', 46 | 'left': 'l', 47 | 'above': 't', 48 | 'below': 'b', 49 | # For backward compatibility. 50 | 'top': 't', 51 | 'bottom': 'b'} 52 | 53 | self.set_x_axis({}) 54 | self.set_x2_axis({}) 55 | 56 | ########################################################################### 57 | # 58 | # Private API. 59 | # 60 | ########################################################################### 61 | 62 | def _write_chart_type(self, args): 63 | # Override the virtual superclass method with a chart specific method. 64 | # Write the c:stockChart element. 65 | self._write_stock_chart(args) 66 | 67 | ########################################################################### 68 | # 69 | # XML methods. 70 | # 71 | ########################################################################### 72 | 73 | def _write_stock_chart(self, args): 74 | # Write the element. 75 | # Overridden to add hi_low_lines(). 76 | 77 | if args['primary_axes']: 78 | series = self._get_primary_axes_series() 79 | else: 80 | series = self._get_secondary_axes_series() 81 | 82 | if not len(series): 83 | return 84 | 85 | # Add default formatting to the series data. 86 | self._modify_series_formatting() 87 | 88 | self._xml_start_tag('c:stockChart') 89 | 90 | # Write the series elements. 91 | for data in series: 92 | self._write_ser(data) 93 | 94 | # Write the c:dropLines element. 95 | self._write_drop_lines() 96 | 97 | # Write the c:hiLowLines element. 98 | if args.get('primary_axes'): 99 | self._write_hi_low_lines() 100 | 101 | # Write the c:upDownBars element. 102 | self._write_up_down_bars() 103 | 104 | # Write the c:marker element. 105 | self._write_marker_value() 106 | 107 | # Write the c:axId elements 108 | self._write_axis_ids(args) 109 | 110 | self._xml_end_tag('c:stockChart') 111 | 112 | def _modify_series_formatting(self): 113 | # Add default formatting to the series data. 114 | 115 | index = 0 116 | 117 | for series in self.series: 118 | if index % 4 != 3: 119 | if not series['line']['defined']: 120 | series['line'] = {'width': 2.25, 121 | 'none': 1, 122 | 'defined': 1} 123 | 124 | if series['marker'] is None: 125 | if index % 4 == 2: 126 | series['marker'] = {'type': 'dot', 'size': 3} 127 | else: 128 | series['marker'] = {'type': 'none'} 129 | 130 | index += 1 131 | -------------------------------------------------------------------------------- /utils/lib/xlsxwriter/sharedstrings.py: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # 3 | # SharedStrings - A class for writing the Excel XLSX sharedStrings file. 4 | # 5 | # Copyright 2013-2015, John McNamara, jmcnamara@cpan.org 6 | # 7 | 8 | # Standard packages. 9 | import re 10 | 11 | # Package imports. 12 | from . import xmlwriter 13 | 14 | 15 | class SharedStrings(xmlwriter.XMLwriter): 16 | """ 17 | A class for writing the Excel XLSX sharedStrings file. 18 | 19 | """ 20 | 21 | ########################################################################### 22 | # 23 | # Public API. 24 | # 25 | ########################################################################### 26 | 27 | def __init__(self): 28 | """ 29 | Constructor. 30 | 31 | """ 32 | 33 | super(SharedStrings, self).__init__() 34 | 35 | self.string_table = None 36 | 37 | ########################################################################### 38 | # 39 | # Private API. 40 | # 41 | ########################################################################### 42 | 43 | def _assemble_xml_file(self): 44 | # Assemble and write the XML file. 45 | 46 | # Write the XML declaration. 47 | self._xml_declaration() 48 | 49 | # Write the sst element. 50 | self._write_sst() 51 | 52 | # Write the sst strings. 53 | self._write_sst_strings() 54 | 55 | # Close the sst tag. 56 | self._xml_end_tag('sst') 57 | 58 | # Close the file. 59 | self._xml_close() 60 | 61 | ########################################################################### 62 | # 63 | # XML methods. 64 | # 65 | ########################################################################### 66 | 67 | def _write_sst(self): 68 | # Write the element. 69 | xmlns = 'http://schemas.openxmlformats.org/spreadsheetml/2006/main' 70 | 71 | attributes = [ 72 | ('xmlns', xmlns), 73 | ('count', self.string_table.count), 74 | ('uniqueCount', self.string_table.unique_count), 75 | ] 76 | 77 | self._xml_start_tag('sst', attributes) 78 | 79 | def _write_sst_strings(self): 80 | # Write the sst string elements. 81 | 82 | for string in (self.string_table._get_strings()): 83 | self._write_si(string) 84 | 85 | def _write_si(self, string): 86 | # Write the element. 87 | attributes = [] 88 | 89 | # Excel escapes control characters with _xHHHH_ and also escapes any 90 | # literal strings of that type by encoding the leading underscore. 91 | # So "\0" -> _x0000_ and "_x0000_" -> _x005F_x0000_. 92 | # The following substitutions deal with those cases. 93 | 94 | # Escape the escape. 95 | string = re.sub('(_x[0-9a-fA-F]{4}_)', r'_x005F\1', string) 96 | 97 | # Convert control character to the _xHHHH_ escape. 98 | string = re.sub(r'([\x00-\x08\x0B-\x1F])', 99 | lambda match: "_x%04X_" % 100 | ord(match.group(1)), string) 101 | 102 | # Add attribute to preserve leading or trailing whitespace. 103 | if re.search('^\s', string) or re.search('\s$', string): 104 | attributes.append(('xml:space', 'preserve')) 105 | 106 | # Write any rich strings without further tags. 107 | if re.search('^', string) and re.search('$', string): 108 | self._xml_rich_si_element(string) 109 | else: 110 | self._xml_si_element(string, attributes) 111 | 112 | 113 | # A metadata class to store Excel strings between worksheets. 114 | class SharedStringTable(object): 115 | """ 116 | A class to track Excel shared strings between worksheets. 117 | 118 | """ 119 | 120 | def __init__(self): 121 | self.count = 0 122 | self.unique_count = 0 123 | self.string_table = {} 124 | self.string_array = [] 125 | 126 | def _get_shared_string_index(self, string): 127 | """" Get the index of the string in the Shared String table. """ 128 | if string not in self.string_table: 129 | # String isn't already stored in the table so add it. 130 | index = self.unique_count 131 | self.string_table[string] = index 132 | self.count += 1 133 | self.unique_count += 1 134 | return index 135 | else: 136 | # String exists in the table. 137 | index = self.string_table[string] 138 | self.count += 1 139 | return index 140 | 141 | def _get_shared_string(self, index): 142 | """" Get a shared string from the index. """ 143 | return self.string_array[index] 144 | 145 | def _sort_string_data(self): 146 | """" Sort the shared string data and convert from dict to list. """ 147 | self.string_array = sorted(self.string_table, 148 | key=self.string_table.__getitem__) 149 | self.string_table = {} 150 | 151 | def _get_strings(self): 152 | """" Return the sorted string list. """ 153 | return self.string_array 154 | -------------------------------------------------------------------------------- /app/dbs/inc/Redis.py: -------------------------------------------------------------------------------- 1 | #coding=utf-8 2 | ''' 3 | Created on 2015年8月21日 4 | Redis操作的库 5 | @author: atool 6 | ''' 7 | #数据库配置 8 | import redis 9 | from app import redis_config 10 | import time 11 | from app.utils import OtherUtil 12 | from app.dbs.inc.Mysql import Mysql 13 | 14 | class RedisMysqlCache(object): 15 | ''' 16 | a redis cache for mysql 17 | ''' 18 | def __init__(self, timeout = 60 * 60, # one hour 19 | host = redis_config['RD_HOST'], 20 | port = redis_config['RD_PORT'], 21 | password = redis_config['RD_PSW'], 22 | db = redis_config['TEMP_DB'], 23 | charset = redis_config['RD_CHARSET']): 24 | self.__db = redis.Redis(host = host, port = port, password = password, db = db, charset = charset) 25 | self.timeout = timeout 26 | 27 | def __cal_key(self, sql, params, t = "select"): 28 | key = sql + t 29 | for p in params: 30 | key = key + str(p) 31 | 32 | key = OtherUtil.md5(key) 33 | return key 34 | 35 | def select_one(self, sql, params): 36 | ''' 37 | ps:从redis中获取数据,如果数据不存在,则从数据库取出来放到redis中 38 | ''' 39 | key = self.__cal_key(sql, params, t = "select_one") 40 | value = self.__db.get(key) 41 | 42 | expire = True 43 | try: 44 | value = eval(value) 45 | if time.time() - value['timestamp'] <= self.timeout and value['data']: 46 | #还没有过期 47 | expire = False 48 | rst = value['data'] 49 | except: 50 | expire = True 51 | 52 | if expire: 53 | #cache过期,则重新从数据库加载 54 | rst = Mysql().exec_select_one(sql, params) 55 | if rst: 56 | #查询到结果,则缓存到redis 57 | value = {'timestamp': time.time(), 'data': rst} 58 | self.__db.set(key, value) 59 | 60 | return rst 61 | 62 | def select(self, sql, params): 63 | ''' 64 | ps:从redis中获取数据,如果数据不存在,则从数据库取出来放到redis中 65 | ''' 66 | key = self.__cal_key(sql, params) 67 | 68 | value = self.__db.get(key) 69 | 70 | expire = True 71 | try: 72 | value = eval(value) 73 | if time.time() - value['timestamp'] <= self.timeout: 74 | #还没有过期 75 | expire = False 76 | rst = value['data'] 77 | except: 78 | expire = True 79 | 80 | if expire: 81 | #cache过期,则重新从数据库加载 82 | rst = Mysql().exec_select(sql, params) 83 | if rst: 84 | #查询到结果,则缓存到redis 85 | value = {'timestamp': time.time(), 'data': rst} 86 | self.__db.set(key, value) 87 | 88 | return rst 89 | 90 | class RedisQueue(object): 91 | """Simple Queue with Redis Backend""" 92 | def __init__(self, name, 93 | namespace = 'queue', 94 | host = redis_config['RD_HOST'], 95 | port = redis_config['RD_PORT'], 96 | password = redis_config['RD_PSW'], 97 | db = redis_config['TEST_DB'], 98 | charset = redis_config['RD_CHARSET']): 99 | 100 | """The default connection parameters are: host='localhost', port=6379, db=0""" 101 | self.__db = redis.Redis(host = host, port = port, password = password, db = db, charset = charset) 102 | self.key = '%s:%s' %(namespace, name) 103 | 104 | def qsize(self): 105 | """Return the approximate size of the queue.""" 106 | return self.__db.llen(self.key) 107 | 108 | def empty(self): 109 | """Return True if the queue is empty, False otherwise.""" 110 | return self.qsize() == 0 111 | 112 | def put(self, item): 113 | """Put item into the queue.""" 114 | return self.__db.rpush(self.key, item) 115 | 116 | def get(self, block = True, timeout = None): 117 | """Remove and return an item from the queue. 118 | 119 | If optional args block is true and timeout is None (the default), block 120 | if necessary until an item is available.""" 121 | if block: 122 | item = self.__db.blpop(self.key, timeout = timeout) 123 | else: 124 | item = self.__db.lpop(self.key) 125 | 126 | if item: 127 | item = item[1] 128 | return item 129 | 130 | #TODO has bug 131 | def get_nowait(self): 132 | """Equivalent to get(False).""" 133 | return self.get(False) 134 | 135 | def flush(self): 136 | self.__db.delete(self.key) 137 | 138 | if __name__ == '__main__': 139 | q = RedisQueue('test') 140 | # q.flush() 141 | start = time.time() 142 | key = '' 143 | #仅仅set,22972.6616659 条每秒 144 | #set、get、del,大概为8000条每秒 145 | #几乎不随数据量大小而变慢 146 | for i in xrange(100): 147 | key = 'key' + str(i) 148 | q.put({'key': key}) 149 | print q.get(block = False, timeout = 1) 150 | end = time.time() 151 | t = end - start 152 | print t 153 | print 100000 / t 154 | print q.qsize() 155 | # q.flush() 156 | # print q.keys() 157 | # r.flushdb() 158 | # print r.keys() -------------------------------------------------------------------------------- /utils/lib/xlsxwriter/chart_bar.py: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # 3 | # ChartBar - A class for writing the Excel XLSX Bar charts. 4 | # 5 | # Copyright 2013-2015, John McNamara, jmcnamara@cpan.org 6 | # 7 | 8 | from . import chart 9 | from warnings import warn 10 | 11 | 12 | class ChartBar(chart.Chart): 13 | """ 14 | A class for writing the Excel XLSX Bar charts. 15 | 16 | 17 | """ 18 | 19 | ########################################################################### 20 | # 21 | # Public API. 22 | # 23 | ########################################################################### 24 | 25 | def __init__(self, options=None): 26 | """ 27 | Constructor. 28 | 29 | """ 30 | super(ChartBar, self).__init__() 31 | 32 | if options is None: 33 | options = {} 34 | 35 | self.subtype = options.get('subtype') 36 | 37 | if not self.subtype: 38 | self.subtype = 'clustered' 39 | 40 | self.cat_axis_position = 'l' 41 | self.val_axis_position = 'b' 42 | self.horiz_val_axis = 0 43 | self.horiz_cat_axis = 1 44 | self.show_crosses = 0 45 | 46 | # Override and reset the default axis values. 47 | self.x_axis['defaults']['major_gridlines'] = {'visible': 1} 48 | self.y_axis['defaults']['major_gridlines'] = {'visible': 0} 49 | 50 | if self.subtype == 'percent_stacked': 51 | self.x_axis['defaults']['num_format'] = '0%' 52 | 53 | # Set the available data label positions for this chart type. 54 | self.label_position_default = 'outside_end' 55 | self.label_positions = { 56 | 'center': 'ctr', 57 | 'inside_base': 'inBase', 58 | 'inside_end': 'inEnd', 59 | 'outside_end': 'outEnd'} 60 | 61 | self.set_x_axis({}) 62 | self.set_y_axis({}) 63 | 64 | def combine(self, chart=None): 65 | """ 66 | Create a combination chart with a secondary chart. 67 | 68 | Note: Override parent method to add an extra check that is required 69 | for Bar charts to ensure that their combined chart is on a secondary 70 | axis. 71 | 72 | Args: 73 | chart: The secondary chart to combine with the primary chart. 74 | 75 | Returns: 76 | Nothing. 77 | 78 | """ 79 | if chart is None: 80 | return 81 | 82 | if not chart.is_secondary: 83 | warn('Charts combined with Bar charts must be on a secondary axis') 84 | 85 | self.combined = chart 86 | 87 | ########################################################################### 88 | # 89 | # Private API. 90 | # 91 | ########################################################################### 92 | 93 | def _write_chart_type(self, args): 94 | # Override the virtual superclass method with a chart specific method. 95 | if args['primary_axes']: 96 | # Reverse X and Y axes for Bar charts. 97 | tmp = self.y_axis 98 | self.y_axis = self.x_axis 99 | self.x_axis = tmp 100 | 101 | if self.y2_axis['position'] == 'r': 102 | self.y2_axis['position'] = 't' 103 | 104 | # Write the c:barChart element. 105 | self._write_bar_chart(args) 106 | 107 | def _write_bar_chart(self, args): 108 | # Write the element. 109 | 110 | if args['primary_axes']: 111 | series = self._get_primary_axes_series() 112 | else: 113 | series = self._get_secondary_axes_series() 114 | 115 | if not len(series): 116 | return 117 | 118 | subtype = self.subtype 119 | if subtype == 'percent_stacked': 120 | subtype = 'percentStacked' 121 | 122 | # Set a default overlap for stacked charts. 123 | if 'stacked' in self.subtype: 124 | if self.series_overlap_1 is None: 125 | self.series_overlap_1 = 100 126 | 127 | self._xml_start_tag('c:barChart') 128 | 129 | # Write the c:barDir element. 130 | self._write_bar_dir() 131 | 132 | # Write the c:grouping element. 133 | self._write_grouping(subtype) 134 | 135 | # Write the c:ser elements. 136 | for data in series: 137 | self._write_ser(data) 138 | 139 | # Write the c:marker element. 140 | self._write_marker_value() 141 | 142 | # Write the c:gapWidth element. 143 | if args['primary_axes']: 144 | self._write_gap_width(self.series_gap_1) 145 | else: 146 | self._write_gap_width(self.series_gap_2) 147 | 148 | # Write the c:overlap element. 149 | if args['primary_axes']: 150 | self._write_overlap(self.series_overlap_1) 151 | else: 152 | self._write_overlap(self.series_overlap_2) 153 | 154 | # Write the c:axId elements 155 | self._write_axis_ids(args) 156 | 157 | self._xml_end_tag('c:barChart') 158 | 159 | ########################################################################### 160 | # 161 | # XML methods. 162 | # 163 | ########################################################################### 164 | 165 | def _write_bar_dir(self): 166 | # Write the element. 167 | val = 'bar' 168 | 169 | attributes = [('val', val)] 170 | 171 | self._xml_empty_tag('c:barDir', attributes) 172 | 173 | def _write_err_dir(self, val): 174 | # Overridden from Chart class since it is not used in Bar charts. 175 | pass 176 | -------------------------------------------------------------------------------- /app/static/res/css/styles.css: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2015 Google Inc. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | html, body { 18 | font-family: 'Roboto', 'Helvetica', sans-serif; 19 | margin: 0; 20 | padding: 0; 21 | } 22 | .mdl-demo .mdl-layout__header-row { 23 | padding-left: 40px; 24 | } 25 | .mdl-demo .mdl-layout.is-small-screen .mdl-layout__header-row h3 { 26 | font-size: inherit; 27 | } 28 | .mdl-demo .mdl-layout__tab-bar-button { 29 | display: none; 30 | } 31 | .mdl-demo .mdl-layout.is-small-screen .mdl-layout__tab-bar .mdl-button { 32 | display: none; 33 | } 34 | .mdl-demo .mdl-layout:not(.is-small-screen) .mdl-layout__tab-bar, 35 | .mdl-demo .mdl-layout:not(.is-small-screen) .mdl-layout__tab-bar-container { 36 | overflow: visible; 37 | } 38 | .mdl-demo .mdl-layout__tab-bar-container { 39 | height: 64px; 40 | } 41 | .mdl-demo .mdl-layout__tab-bar { 42 | padding: 0; 43 | padding-left: 16px; 44 | box-sizing: border-box; 45 | height: 100%; 46 | width: 100%; 47 | } 48 | .mdl-demo .mdl-layout__tab-bar .mdl-layout__tab { 49 | height: 64px; 50 | line-height: 64px; 51 | } 52 | .mdl-demo .mdl-layout__tab-bar .mdl-layout__tab.is-active::after { 53 | background-color: white; 54 | height: 4px; 55 | } 56 | .mdl-demo main > .mdl-layout__tab-panel { 57 | padding: 8px; 58 | padding-top: 48px; 59 | } 60 | .mdl-demo .mdl-card { 61 | height: auto; 62 | display: flex; 63 | flex-direction: column; 64 | } 65 | .mdl-demo .mdl-card > * { 66 | height: auto; 67 | } 68 | .mdl-demo .mdl-card .mdl-card__supporting-text { 69 | margin: 40px; 70 | flex-grow: 1; 71 | padding: 0; 72 | color: inherit; 73 | width: calc(100% - 80px); 74 | } 75 | .mdl-demo.mdl-demo .mdl-card__supporting-text h4 { 76 | margin-top: 0; 77 | margin-bottom: 20px; 78 | } 79 | .mdl-demo .mdl-card__actions { 80 | margin: 0; 81 | padding: 4px 40px; 82 | color: inherit; 83 | } 84 | .mdl-demo .mdl-card__actions a { 85 | color: #00BCD4; 86 | margin: 0; 87 | } 88 | .mdl-demo .mdl-card__actions a:hover, 89 | .mdl-demo .mdl-card__actions a:active { 90 | color: inherit; 91 | background-color: transparent; 92 | } 93 | .mdl-demo .mdl-card__supporting-text + .mdl-card__actions { 94 | border-top: 1px solid rgba(0, 0, 0, 0.12); 95 | } 96 | .mdl-demo #add { 97 | position: absolute; 98 | right: 40px; 99 | top: 36px; 100 | z-index: 999; 101 | } 102 | 103 | .mdl-demo .mdl-layout__content section:not(:last-of-type) { 104 | position: relative; 105 | margin-bottom: 48px; 106 | } 107 | .mdl-demo section.section--center { 108 | max-width: 860px; 109 | } 110 | .mdl-demo #features section.section--center { 111 | max-width: 620px; 112 | } 113 | .mdl-demo section > header{ 114 | display: flex; 115 | align-items: center; 116 | justify-content: center; 117 | } 118 | .mdl-demo section > .section__play-btn { 119 | min-height: 200px; 120 | } 121 | .mdl-demo section > header > .material-icons { 122 | font-size: 3rem; 123 | } 124 | .mdl-demo section > button { 125 | position: absolute; 126 | z-index: 99; 127 | top: 8px; 128 | right: 8px; 129 | } 130 | .mdl-demo section .section__circle { 131 | display: flex; 132 | align-items: center; 133 | justify-content: flex-start; 134 | flex-grow: 0; 135 | flex-shrink: 1; 136 | } 137 | .mdl-demo section .section__text { 138 | flex-grow: 1; 139 | flex-shrink: 0; 140 | padding-top: 8px; 141 | } 142 | .mdl-demo section .section__text h5 { 143 | font-size: inherit; 144 | margin: 0; 145 | margin-bottom: 0.5em; 146 | } 147 | .mdl-demo section .section__text a { 148 | text-decoration: none; 149 | } 150 | .mdl-demo section .section__circle-container > .section__circle-container__circle { 151 | width: 64px; 152 | height: 64px; 153 | border-radius: 32px; 154 | margin: 8px 0; 155 | } 156 | .mdl-demo section.section--footer .section__circle--big { 157 | width: 100px; 158 | height: 100px; 159 | border-radius: 50px; 160 | margin: 8px 32px; 161 | } 162 | .mdl-demo .is-small-screen section.section--footer .section__circle--big { 163 | width: 50px; 164 | height: 50px; 165 | border-radius: 25px; 166 | margin: 8px 16px; 167 | } 168 | .mdl-demo section.section--footer { 169 | padding: 64px 0; 170 | margin: 0 -8px -8px -8px; 171 | } 172 | .mdl-demo section.section--center .section__text:not(:last-child) { 173 | border-bottom: 1px solid rgba(0,0,0,.13); 174 | } 175 | .mdl-demo .mdl-card .mdl-card__supporting-text > h3:first-child { 176 | margin-bottom: 24px; 177 | } 178 | .mdl-demo .mdl-layout__tab-panel:not(#overview) { 179 | background-color: white; 180 | } 181 | .mdl-demo #features section { 182 | margin-bottom: 72px; 183 | } 184 | .mdl-demo #features h4, #features h5 { 185 | margin-bottom: 16px; 186 | } 187 | .mdl-demo .toc { 188 | border-left: 4px solid #C1EEF4; 189 | margin: 24px; 190 | padding: 0; 191 | padding-left: 8px; 192 | display: flex; 193 | flex-direction: column; 194 | } 195 | .mdl-demo .toc h4 { 196 | font-size: 0.9rem; 197 | margin-top: 0; 198 | } 199 | .mdl-demo .toc a { 200 | color: #4DD0E1; 201 | text-decoration: none; 202 | font-size: 16px; 203 | line-height: 28px; 204 | display: block; 205 | } 206 | .mdl-demo .mdl-menu__container { 207 | z-index: 99; 208 | } 209 | -------------------------------------------------------------------------------- /utils/lib/xlsxwriter/chartsheet.py: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # 3 | # Chartsheet - A class for writing the Excel XLSX Worksheet file. 4 | # 5 | # Copyright 2013-2015, John McNamara, jmcnamara@cpan.org 6 | # 7 | 8 | from . import worksheet 9 | from .drawing import Drawing 10 | 11 | 12 | class Chartsheet(worksheet.Worksheet): 13 | """ 14 | A class for writing the Excel XLSX Chartsheet file. 15 | 16 | 17 | """ 18 | 19 | ########################################################################### 20 | # 21 | # Public API. 22 | # 23 | ########################################################################### 24 | 25 | def __init__(self): 26 | """ 27 | Constructor. 28 | 29 | """ 30 | 31 | super(Chartsheet, self).__init__() 32 | 33 | self.is_chartsheet = True 34 | self.drawing = None 35 | self.chart = None 36 | self.charts = [] 37 | self.zoom_scale_normal = 0 38 | self.orientation = 0 39 | self.protection = False 40 | 41 | def set_chart(self, chart): 42 | """ 43 | Set the chart object for the chartsheet. 44 | Args: 45 | chart: Chart object. 46 | Returns: 47 | chart: A reference to the chart object. 48 | """ 49 | chart.embedded = False 50 | chart.protection = self.protection 51 | self.chart = chart 52 | self.charts.append([0, 0, chart, 0, 0, 1, 1]) 53 | return chart 54 | 55 | def protect(self, password='', options=None): 56 | """ 57 | Set the password and protection options of the worksheet. 58 | 59 | Args: 60 | password: An optional password string. 61 | options: A dictionary of worksheet objects to protect. 62 | 63 | Returns: 64 | Nothing. 65 | 66 | """ 67 | # Overridden from parent worksheet class. 68 | if self.chart: 69 | self.chart.protection = True 70 | else: 71 | self.protection = True 72 | 73 | if not options: 74 | options = {} 75 | 76 | options = options.copy() 77 | 78 | options['sheet'] = False 79 | options['content'] = True 80 | options['scenarios'] = True 81 | 82 | # Call the parent method. 83 | super(Chartsheet, self).protect(password, options) 84 | 85 | ########################################################################### 86 | # 87 | # Private API. 88 | # 89 | ########################################################################### 90 | def _assemble_xml_file(self): 91 | # Assemble and write the XML file. 92 | 93 | # Write the XML declaration. 94 | self._xml_declaration() 95 | 96 | # Write the root worksheet element. 97 | self._write_chartsheet() 98 | 99 | # Write the worksheet properties. 100 | self._write_sheet_pr() 101 | 102 | # Write the sheet view properties. 103 | self._write_sheet_views() 104 | 105 | # Write the sheetProtection element. 106 | self._write_sheet_protection() 107 | 108 | # Write the printOptions element. 109 | self._write_print_options() 110 | 111 | # Write the worksheet page_margins. 112 | self._write_page_margins() 113 | 114 | # Write the worksheet page setup. 115 | self._write_page_setup() 116 | 117 | # Write the headerFooter element. 118 | self._write_header_footer() 119 | 120 | # Write the drawing element. 121 | self._write_drawings() 122 | 123 | # Close the worksheet tag. 124 | self._xml_end_tag('chartsheet') 125 | 126 | # Close the file. 127 | self._xml_close() 128 | 129 | def _prepare_chart(self, index, chart_id, drawing_id): 130 | # Set up chart/drawings. 131 | 132 | self.chart.id = chart_id - 1 133 | 134 | self.drawing = Drawing() 135 | self.drawing.orientation = self.orientation 136 | 137 | self.external_drawing_links.append(['/drawing', 138 | '../drawings/drawing' 139 | + str(drawing_id) 140 | + '.xml']) 141 | 142 | self.drawing_links.append(['/chart', 143 | '../charts/chart' 144 | + str(chart_id) 145 | + '.xml']) 146 | 147 | ########################################################################### 148 | # 149 | # XML methods. 150 | # 151 | ########################################################################### 152 | 153 | def _write_chartsheet(self): 154 | # Write the element. This is the root element. 155 | 156 | schema = 'http://schemas.openxmlformats.org/' 157 | xmlns = schema + 'spreadsheetml/2006/main' 158 | xmlns_r = schema + 'officeDocument/2006/relationships' 159 | 160 | attributes = [ 161 | ('xmlns', xmlns), 162 | ('xmlns:r', xmlns_r)] 163 | 164 | self._xml_start_tag('chartsheet', attributes) 165 | 166 | def _write_sheet_pr(self): 167 | # Write the element for Sheet level properties. 168 | attributes = [] 169 | 170 | if self.filter_on: 171 | attributes.append(('filterMode', 1)) 172 | 173 | if (self.fit_page or self.tab_color): 174 | self._xml_start_tag('sheetPr', attributes) 175 | self._write_tab_color() 176 | self._write_page_set_up_pr() 177 | self._xml_end_tag('sheetPr') 178 | else: 179 | self._xml_empty_tag('sheetPr', attributes) 180 | -------------------------------------------------------------------------------- /api/app_utils.py: -------------------------------------------------------------------------------- 1 | #coding=utf-8 2 | ''' 3 | Created on 2015年10月29日 4 | 5 | @author: atool 6 | ''' 7 | import re 8 | import os, time, datetime, random 9 | import subprocess 10 | from api import api_helpers 11 | from dump import class_dump_utils 12 | import zipfile 13 | from hashlib import md5 14 | 15 | def unzip_ipa(ipa_path, dest_path): 16 | ''' 17 | unzip a ipa, and return the zip folder 18 | ''' 19 | file_zip = zipfile.ZipFile(ipa_path, 'r') 20 | for f in file_zip.namelist(): 21 | file_zip.extract(f, dest_path) 22 | file_zip.close() 23 | return os.path.join(dest_path, 'Payload') 24 | 25 | def get_executable_file(path): 26 | ''' 27 | info:从ipa中解压出Payload目录中的xxx.app,扫描其中的文件,寻找 Mach-O 文件的路径 28 | ''' 29 | 30 | cmd = u"python -mmacholib find %s" % (path) 31 | out = subprocess.check_output(cmd.split()) 32 | if out: 33 | out = out.split() 34 | if out and len(out) > 0: 35 | return os.path.join(path, out[0]) 36 | return False 37 | 38 | 39 | 40 | def get_app_strings(app_path, pid): 41 | """ 42 | Args: 43 | app : the full path of the Mach-O file in app 44 | Returns: 45 | output : the result of the strings app 46 | 47 | info:strings - 显示文件中的可打印字符 48 | strings 的主要用途是确定非文本文件的包含的文本内容。 49 | """ 50 | cmd = "/usr/bin/strings %s" % app_path 51 | output = subprocess.check_output(cmd.split()) 52 | 53 | return set(output.split()) 54 | 55 | 56 | def get_dump_result(app): 57 | """ 58 | get app class-dump result, and cache it 59 | """ 60 | dump_result = class_dump_utils.dump_app(app) 61 | return dump_result 62 | 63 | def get_app_variables(dump_result, pid): 64 | "get all variables, properties, and interface name" 65 | interface = re.compile("^@interface (\w*).*") 66 | protocol = re.compile("@protocoli (\w*)") 67 | private = re.compile("^\s*[\w <>]* [*]?(\w*)[\[\]\d]*;") 68 | prop = re.compile("@property\([\w, ]*\) (?:\w+ )*[*]?(\w+); // @synthesize \w*(?:=([\w]*))?;") 69 | res = set() 70 | lines = dump_result.split("\n") 71 | wait_end = False 72 | for line in lines: 73 | l = line.strip() 74 | if l.startswith("}"): 75 | wait_end = False 76 | continue 77 | if wait_end: 78 | r = private.search(l) 79 | if r: 80 | res.add(r.groups()[0]) 81 | continue 82 | r = interface.search(l) 83 | if r: 84 | res.add(r.groups()[0]) 85 | wait_end = True 86 | continue 87 | r = protocol.search(l) 88 | if r: 89 | res.add(r.groups()[0]) 90 | wait_end = True 91 | continue 92 | r = prop.search(l) 93 | if r: 94 | m = r.groups() 95 | res.add(m[0]) 96 | res.add("set" + m[0].title() + ":") 97 | #print "set" + m[0].title() + ":" 98 | if m[1] != None: 99 | # res.add("V"+m[1]) 100 | res.add(m[1]) 101 | return res 102 | 103 | 104 | def get_app_methods(dump_result, pid): 105 | ''' 106 | info:获得app中的方法 107 | ''' 108 | # dump_result = class_dump_utils.dump_app(app) 109 | methods = api_helpers.extract(dump_result) 110 | #for m in methods: 111 | # ret_methods = ret_methods.union(set(m["methods"])) 112 | #保留class_name信息 113 | return methods 114 | 115 | 116 | def check_architectures(app): 117 | ''' 118 | info检查是否支持64位 119 | demo:armv7, arm64, armv7s 120 | ''' 121 | from macholib import MachO, mach_o 122 | 123 | m = MachO.MachO(app) 124 | arcs = [] 125 | for header in m.headers: 126 | cpu_type = header.header.cputype 127 | cpu_subtype = header.header.cpusubtype 128 | arch = str(mach_o.CPU_TYPE_NAMES.get(cpu_type, cpu_type)).lower() 129 | if cpu_type == 12: 130 | if cpu_subtype == 0: 131 | arch = 'armall' 132 | elif cpu_subtype == 5: 133 | arch = 'armv4t' 134 | elif cpu_subtype == 6: 135 | arch = 'armv6' 136 | elif cpu_subtype == 7: 137 | arch = 'armv5tej' 138 | elif cpu_subtype == 8: 139 | arch = 'arm_xscale' 140 | elif cpu_subtype == 9: 141 | arch = 'armv7' 142 | elif cpu_subtype == 10: 143 | arch = 'armv7f' 144 | elif cpu_subtype == 11: 145 | arch = 'armv7s' 146 | elif cpu_subtype == 12: 147 | arch = 'armv7k' 148 | elif cpu_subtype == 13: 149 | arch = 'armv8' 150 | elif cpu_subtype == 14: 151 | arch = 'armv6m' 152 | elif cpu_subtype == 15: 153 | arch = 'armv7m' 154 | elif cpu_subtype == 16: 155 | arch = 'armv7em' 156 | 157 | elif cpu_type == 16777228: 158 | arch = 'arm64' 159 | 160 | arcs.append(arch) 161 | return arcs 162 | 163 | 164 | #检查app是否被xcode ghost感染 165 | xcode_ghost_keyword = "icloud-analysis.com" 166 | def check_xcode_ghost(app): 167 | cmd = "/usr/bin/strings %s" % app 168 | output = subprocess.check_output(cmd.split()) 169 | output = output.replace("\n", "") 170 | output = output.replace(" ", "") 171 | output = output.replace("\t", "") 172 | 173 | return xcode_ghost_keyword in output 174 | 175 | #获得文件的md5值,便于在检测之后,判断检测文件和上传文件是否对应 176 | def file_md5(file_path): 177 | m = md5() 178 | f = open(file_path, 'rb') 179 | m.update(f.read()) 180 | return m.hexdigest() 181 | 182 | def get_unique_str(): 183 | #随机的名字,可以用于上传文件等等不重复,但有一定时间意义的名字 184 | datetime_str = time.strftime('%Y%m%d%H%M%S',time.localtime()) 185 | return datetime_str + str(datetime.datetime.now().microsecond / 1000) + str(random.randint(0, 1000)) 186 | -------------------------------------------------------------------------------- /utils/lib/xlsxwriter/table.py: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # 3 | # Table - A class for writing the Excel XLSX Worksheet file. 4 | # 5 | # Copyright 2013-2015, John McNamara, jmcnamara@cpan.org 6 | # 7 | 8 | from . import xmlwriter 9 | 10 | 11 | class Table(xmlwriter.XMLwriter): 12 | """ 13 | A class for writing the Excel XLSX Table file. 14 | 15 | 16 | """ 17 | 18 | ########################################################################### 19 | # 20 | # Public API. 21 | # 22 | ########################################################################### 23 | 24 | def __init__(self): 25 | """ 26 | Constructor. 27 | 28 | """ 29 | 30 | super(Table, self).__init__() 31 | 32 | self.properties = {} 33 | 34 | ########################################################################### 35 | # 36 | # Private API. 37 | # 38 | ########################################################################### 39 | 40 | def _assemble_xml_file(self): 41 | # Assemble and write the XML file. 42 | 43 | # Write the XML declaration. 44 | self._xml_declaration() 45 | 46 | # Write the table element. 47 | self._write_table() 48 | 49 | # Write the autoFilter element. 50 | self._write_auto_filter() 51 | 52 | # Write the tableColumns element. 53 | self._write_table_columns() 54 | 55 | # Write the tableStyleInfo element. 56 | self._write_table_style_info() 57 | 58 | # Close the table tag. 59 | self._xml_end_tag('table') 60 | 61 | # Close the file. 62 | self._xml_close() 63 | 64 | def _set_properties(self, properties): 65 | # Set the document properties. 66 | self.properties = properties 67 | 68 | ########################################################################### 69 | # 70 | # XML methods. 71 | # 72 | ########################################################################### 73 | 74 | def _write_table(self): 75 | # Write the element. 76 | schema = 'http://schemas.openxmlformats.org/' 77 | xmlns = schema + 'spreadsheetml/2006/main' 78 | table_id = self.properties['id'] 79 | name = self.properties['name'] 80 | display_name = self.properties['name'] 81 | ref = self.properties['range'] 82 | totals_row_shown = self.properties['totals_row_shown'] 83 | header_row_count = self.properties['header_row_count'] 84 | 85 | attributes = [ 86 | ('xmlns', xmlns), 87 | ('id', table_id), 88 | ('name', name), 89 | ('displayName', display_name), 90 | ('ref', ref), 91 | ] 92 | 93 | if not header_row_count: 94 | attributes.append(('headerRowCount', 0)) 95 | 96 | if totals_row_shown: 97 | attributes.append(('totalsRowCount', 1)) 98 | else: 99 | attributes.append(('totalsRowShown', 0)) 100 | 101 | self._xml_start_tag('table', attributes) 102 | 103 | def _write_auto_filter(self): 104 | # Write the element. 105 | autofilter = self.properties.get('autofilter', 0) 106 | 107 | if not autofilter: 108 | return 109 | 110 | attributes = [('ref', autofilter,)] 111 | 112 | self._xml_empty_tag('autoFilter', attributes) 113 | 114 | def _write_table_columns(self): 115 | # Write the element. 116 | columns = self.properties['columns'] 117 | 118 | count = len(columns) 119 | 120 | attributes = [('count', count)] 121 | 122 | self._xml_start_tag('tableColumns', attributes) 123 | 124 | for col_data in columns: 125 | # Write the tableColumn element. 126 | self._write_table_column(col_data) 127 | 128 | self._xml_end_tag('tableColumns') 129 | 130 | def _write_table_column(self, col_data): 131 | # Write the element. 132 | attributes = [ 133 | ('id', col_data['id']), 134 | ('name', col_data['name']), 135 | ] 136 | 137 | if col_data.get('total_string'): 138 | attributes.append(('totalsRowLabel', col_data['total_string'])) 139 | elif col_data.get('total_function'): 140 | attributes.append(('totalsRowFunction', 141 | col_data['total_function'])) 142 | 143 | if 'format' in col_data and col_data['format'] is not None: 144 | attributes.append(('dataDxfId', col_data['format'])) 145 | 146 | if col_data.get('formula'): 147 | self._xml_start_tag('tableColumn', attributes) 148 | 149 | # Write the calculatedColumnFormula element. 150 | self._write_calculated_column_formula(col_data['formula']) 151 | 152 | self._xml_end_tag('tableColumn') 153 | else: 154 | self._xml_empty_tag('tableColumn', attributes) 155 | 156 | def _write_table_style_info(self): 157 | # Write the element. 158 | props = self.properties 159 | 160 | name = props['style'] 161 | show_first_column = 0 + props['show_first_col'] 162 | show_last_column = 0 + props['show_last_col'] 163 | show_row_stripes = 0 + props['show_row_stripes'] 164 | show_column_stripes = 0 + props['show_col_stripes'] 165 | 166 | attributes = [ 167 | ('name', name), 168 | ('showFirstColumn', show_first_column), 169 | ('showLastColumn', show_last_column), 170 | ('showRowStripes', show_row_stripes), 171 | ('showColumnStripes', show_column_stripes), 172 | ] 173 | 174 | self._xml_empty_tag('tableStyleInfo', attributes) 175 | 176 | def _write_calculated_column_formula(self, formula): 177 | # Write the element. 178 | self._xml_data_element('calculatedColumnFormula', formula) 179 | -------------------------------------------------------------------------------- /utils/lib/xlsxwriter/comments.py: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # 3 | # Comments - A class for writing the Excel XLSX Worksheet file. 4 | # 5 | # Copyright 2013-2015, John McNamara, jmcnamara@cpan.org 6 | # 7 | 8 | import re 9 | 10 | from . import xmlwriter 11 | from .utility import xl_rowcol_to_cell 12 | 13 | 14 | class Comments(xmlwriter.XMLwriter): 15 | """ 16 | A class for writing the Excel XLSX Comments file. 17 | 18 | 19 | """ 20 | 21 | ########################################################################### 22 | # 23 | # Public API. 24 | # 25 | ########################################################################### 26 | 27 | def __init__(self): 28 | """ 29 | Constructor. 30 | 31 | """ 32 | 33 | super(Comments, self).__init__() 34 | self.author_ids = {} 35 | 36 | ########################################################################### 37 | # 38 | # Private API. 39 | # 40 | ########################################################################### 41 | 42 | def _assemble_xml_file(self, comments_data=[]): 43 | # Assemble and write the XML file. 44 | 45 | # Write the XML declaration. 46 | self._xml_declaration() 47 | 48 | # Write the comments element. 49 | self._write_comments() 50 | 51 | # Write the authors element. 52 | self._write_authors(comments_data) 53 | 54 | # Write the commentList element. 55 | self._write_comment_list(comments_data) 56 | 57 | self._xml_end_tag('comments') 58 | 59 | # Close the file. 60 | self._xml_close() 61 | 62 | ########################################################################### 63 | # 64 | # XML methods. 65 | # 66 | ########################################################################### 67 | 68 | def _write_comments(self): 69 | # Write the element. 70 | xmlns = 'http://schemas.openxmlformats.org/spreadsheetml/2006/main' 71 | 72 | attributes = [('xmlns', xmlns)] 73 | 74 | self._xml_start_tag('comments', attributes) 75 | 76 | def _write_authors(self, comment_data): 77 | # Write the element. 78 | author_count = 0 79 | 80 | self._xml_start_tag('authors') 81 | 82 | for comment in comment_data: 83 | author = comment[3] 84 | 85 | if author is not None and author not in self.author_ids: 86 | # Store the author id. 87 | self.author_ids[author] = author_count 88 | author_count += 1 89 | 90 | # Write the author element. 91 | self._write_author(author) 92 | 93 | self._xml_end_tag('authors') 94 | 95 | def _write_author(self, data): 96 | # Write the element. 97 | self._xml_data_element('author', data) 98 | 99 | def _write_comment_list(self, comment_data): 100 | # Write the element. 101 | self._xml_start_tag('commentList') 102 | 103 | for comment in comment_data: 104 | row = comment[0] 105 | col = comment[1] 106 | text = comment[2] 107 | author = comment[3] 108 | 109 | # Look up the author id. 110 | author_id = None 111 | if author is not None: 112 | author_id = self.author_ids[author] 113 | 114 | # Write the comment element. 115 | self._write_comment(row, col, text, author_id) 116 | 117 | self._xml_end_tag('commentList') 118 | 119 | def _write_comment(self, row, col, text, author_id): 120 | # Write the element. 121 | ref = xl_rowcol_to_cell(row, col) 122 | 123 | attributes = [('ref', ref)] 124 | 125 | if author_id is not None: 126 | attributes.append(('authorId', author_id)) 127 | 128 | self._xml_start_tag('comment', attributes) 129 | 130 | # Write the text element. 131 | self._write_text(text) 132 | 133 | self._xml_end_tag('comment') 134 | 135 | def _write_text(self, text): 136 | # Write the element. 137 | self._xml_start_tag('text') 138 | 139 | # Write the text r element. 140 | self._write_text_r(text) 141 | 142 | self._xml_end_tag('text') 143 | 144 | def _write_text_r(self, text): 145 | # Write the element. 146 | self._xml_start_tag('r') 147 | 148 | # Write the rPr element. 149 | self._write_r_pr() 150 | 151 | # Write the text r element. 152 | self._write_text_t(text) 153 | 154 | self._xml_end_tag('r') 155 | 156 | def _write_text_t(self, text): 157 | # Write the text element. 158 | attributes = [] 159 | 160 | if re.search('^\s', text) or re.search('\s$', text): 161 | attributes.append(('xml:space', 'preserve')) 162 | 163 | self._xml_data_element('t', text, attributes) 164 | 165 | def _write_r_pr(self): 166 | # Write the element. 167 | self._xml_start_tag('rPr') 168 | 169 | # Write the sz element. 170 | self._write_sz() 171 | 172 | # Write the color element. 173 | self._write_color() 174 | 175 | # Write the rFont element. 176 | self._write_r_font() 177 | 178 | # Write the family element. 179 | self._write_family() 180 | 181 | self._xml_end_tag('rPr') 182 | 183 | def _write_sz(self): 184 | # Write the element. 185 | attributes = [('val', 8)] 186 | 187 | self._xml_empty_tag('sz', attributes) 188 | 189 | def _write_color(self): 190 | # Write the element. 191 | attributes = [('indexed', 81)] 192 | 193 | self._xml_empty_tag('color', attributes) 194 | 195 | def _write_r_font(self): 196 | # Write the element. 197 | attributes = [('val', 'Tahoma')] 198 | 199 | self._xml_empty_tag('rFont', attributes) 200 | 201 | def _write_family(self): 202 | # Write the element. 203 | attributes = [('val', 2)] 204 | 205 | self._xml_empty_tag('family', attributes) 206 | -------------------------------------------------------------------------------- /app/utils/IpaParse.py: -------------------------------------------------------------------------------- 1 | #coding=utf-8 2 | ''' 3 | Created on 2015年5月18日 4 | 5 | @author: atool 6 | ''' 7 | import json 8 | import os 9 | import re 10 | import tempfile 11 | import zipfile 12 | 13 | from biplist import readPlist 14 | 15 | class IpaParse(object): 16 | ''' 17 | DEMO 18 | parse = IpaParse(filename) 19 | print parse.app_name() #app 名称 20 | print parse.bundle_identifier() #package 21 | print parse.version() 22 | print parse.target_os_version() #target version 23 | print parse.minimum_os_version() #min version 24 | print parse.icon_file_name() # icon name 25 | print parse.icon_file_path() #path 26 | 27 | print parse.mv_icon_to('test.png') #ico复制到指定位置,图片被加密暂时无法处理 28 | ''' 29 | ipa_file_path = None 30 | ipa_base_path = None 31 | 32 | plist_temp_file = None 33 | plist_info_list = None 34 | 35 | def __init__(self, ipa_file_path): 36 | ''' 37 | Constructor 38 | ''' 39 | self.ipa_file_path = ipa_file_path 40 | 41 | 42 | def _get_plist_temp_file(self): 43 | self.plist_temp_file = '' 44 | 45 | zfile = zipfile.ZipFile(self.ipa_file_path) 46 | zip_name_list = zfile.namelist() 47 | for name in zip_name_list: 48 | if ".app/Info.plist" in name: 49 | print name 50 | tup = tempfile.mkstemp(suffix = '.plist') 51 | fd = os.fdopen(tup[0], "w") 52 | fd.write(zfile.read(name)) 53 | fd.close() 54 | self.plist_temp_file = tup[1] 55 | zfile.close() 56 | 57 | return self.plist_temp_file 58 | 59 | def _parse_plist(self): 60 | try: 61 | if self.plist_temp_file == None: 62 | self._get_plist_temp_file() 63 | 64 | if self.plist_temp_file == '': 65 | self.plist_info_list = {} 66 | return False 67 | 68 | self.plist_info_list = readPlist(self.plist_temp_file) 69 | os.remove(self.plist_temp_file) 70 | return self.plist_info_list 71 | except Exception, e: 72 | print "Not a plist:", e 73 | self.plist_info_list = {} 74 | return False 75 | 76 | def _check(self): 77 | if self.plist_info_list == None: 78 | self._parse_plist() 79 | 80 | def all_info(self): 81 | self._check() 82 | return self.plist_info_list 83 | 84 | def app_name(self): 85 | self._check() 86 | if 'CFBundleDisplayName' in self.plist_info_list: 87 | return self.plist_info_list['CFBundleDisplayName'] 88 | elif 'CFBundleName' in self.plist_info_list: 89 | return self.plist_info_list['CFBundleName'] 90 | return None 91 | 92 | def bundle_identifier(self): 93 | self._check() 94 | if 'CFBundleIdentifier' in self.plist_info_list: 95 | return self.plist_info_list['CFBundleIdentifier'] 96 | return '' 97 | 98 | def target_os_version(self): 99 | self._check() 100 | if 'DTPlatformVersion' in self.plist_info_list: 101 | return re.findall('[\d\.]*', self.plist_info_list['DTPlatformVersion'])[0] 102 | return '' 103 | 104 | def minimum_os_version(self): 105 | self._check() 106 | if 'MinimumOSVersion' in self.plist_info_list: 107 | return re.findall('[\d\.]*', self.plist_info_list['MinimumOSVersion'])[0] 108 | return '' 109 | 110 | def version(self): 111 | self._check() 112 | if 'CFBundleVersion' in self.plist_info_list: 113 | return self.plist_info_list['CFBundleVersion'] 114 | return '' 115 | 116 | def icon_file_name(self): 117 | if 'CFBundleIcons' in self.plist_info_list and \ 118 | 'CFBundlePrimaryIcon' in self.plist_info_list["CFBundleIcons"] and \ 119 | 'CFBundleIconFiles' in self.plist_info_list["CFBundleIcons"]["CFBundlePrimaryIcon"]: 120 | icons = self.plist_info_list["CFBundleIcons"]["CFBundlePrimaryIcon"]['CFBundleIconFiles'] 121 | if icons != None and len(icons) > 0: 122 | return icons[len(icons) - 1] 123 | elif 'CFBundleIcons~ipad' in self.plist_info_list and \ 124 | 'CFBundlePrimaryIcon' in self.plist_info_list["CFBundleIcons~ipad"] and \ 125 | 'CFBundleIconFiles' in self.plist_info_list["CFBundleIcons~ipad"]["CFBundlePrimaryIcon"]: 126 | icons = self.plist_info_list["CFBundleIcons~ipad"]["CFBundlePrimaryIcon"]['CFBundleIconFiles'] 127 | if icons != None and len(icons) > 0: 128 | return icons[len(icons) - 1] 129 | else: 130 | return False 131 | 132 | def icon_file_path(self): 133 | icon_file_name = self.icon_file_name() 134 | if icon_file_name: 135 | zfile = zipfile.ZipFile(self.ipa_file_path) 136 | zip_name_list = zfile.namelist() 137 | for name in zip_name_list: 138 | tempkey = ".app/" + icon_file_name 139 | if tempkey in name: 140 | zfile.close() 141 | return name 142 | zfile.close() 143 | 144 | return False 145 | 146 | def mv_icon_to(self, file_name): 147 | icon_path = self.icon_file_path() 148 | if icon_path: 149 | zfile = zipfile.ZipFile(self.ipa_file_path) 150 | 151 | icon_file = open(file_name, "wb") 152 | icon_file.write(zfile.read(icon_path)) 153 | icon_file.close() 154 | zfile.close() 155 | return True 156 | 157 | return False 158 | 159 | if __name__ == '__main__': 160 | parse = IpaParse(u'C:\\Users\\atool\\Desktop\\H28_150514121833_resign.ipa') 161 | 162 | print json.dumps(parse.all_info(), default = lambda o: o.__dict__) 163 | print parse.app_name() 164 | print parse.bundle_identifier() 165 | print parse.target_os_version() 166 | print parse.minimum_os_version() 167 | print parse.icon_file_name() 168 | print parse.icon_file_path() 169 | print parse.mv_icon_to('test.png') 170 | -------------------------------------------------------------------------------- /utils/lib/xlsxwriter/app.py: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # 3 | # App - A class for writing the Excel XLSX App file. 4 | # 5 | # Copyright 2013-2015, John McNamara, jmcnamara@cpan.org 6 | # 7 | 8 | # Package imports. 9 | from . import xmlwriter 10 | 11 | 12 | class App(xmlwriter.XMLwriter): 13 | """ 14 | A class for writing the Excel XLSX App file. 15 | 16 | 17 | """ 18 | 19 | ########################################################################### 20 | # 21 | # Public API. 22 | # 23 | ########################################################################### 24 | 25 | def __init__(self): 26 | """ 27 | Constructor. 28 | 29 | """ 30 | 31 | super(App, self).__init__() 32 | 33 | self.part_names = [] 34 | self.heading_pairs = [] 35 | self.properties = {} 36 | 37 | def _add_part_name(self, part_name): 38 | # Add the name of a workbook Part such as 'Sheet1' or 'Print_Titles'. 39 | self.part_names.append(part_name) 40 | 41 | def _add_heading_pair(self, heading_pair): 42 | # Add the name of a workbook Heading Pair such as 'Worksheets', 43 | # 'Charts' or 'Named Ranges'. 44 | 45 | # Ignore empty pairs such as chartsheets. 46 | if not heading_pair[1]: 47 | return 48 | 49 | self.heading_pairs.append(('lpstr', heading_pair[0])) 50 | self.heading_pairs.append(('i4', heading_pair[1])) 51 | 52 | def _set_properties(self, properties): 53 | # Set the document properties. 54 | self.properties = properties 55 | 56 | ########################################################################### 57 | # 58 | # Private API. 59 | # 60 | ########################################################################### 61 | 62 | def _assemble_xml_file(self): 63 | # Assemble and write the XML file. 64 | 65 | # Write the XML declaration. 66 | self._xml_declaration() 67 | 68 | self._write_properties() 69 | self._write_application() 70 | self._write_doc_security() 71 | self._write_scale_crop() 72 | self._write_heading_pairs() 73 | self._write_titles_of_parts() 74 | self._write_manager() 75 | self._write_company() 76 | self._write_links_up_to_date() 77 | self._write_shared_doc() 78 | self._write_hyperlinks_changed() 79 | self._write_app_version() 80 | 81 | self._xml_end_tag('Properties') 82 | 83 | # Close the file. 84 | self._xml_close() 85 | 86 | ########################################################################### 87 | # 88 | # XML methods. 89 | # 90 | ########################################################################### 91 | 92 | def _write_properties(self): 93 | # Write the element. 94 | schema = 'http://schemas.openxmlformats.org/officeDocument/2006/' 95 | xmlns = schema + 'extended-properties' 96 | xmlns_vt = schema + 'docPropsVTypes' 97 | 98 | attributes = [ 99 | ('xmlns', xmlns), 100 | ('xmlns:vt', xmlns_vt), 101 | ] 102 | 103 | self._xml_start_tag('Properties', attributes) 104 | 105 | def _write_application(self): 106 | # Write the element. 107 | self._xml_data_element('Application', 'Microsoft Excel') 108 | 109 | def _write_doc_security(self): 110 | # Write the element. 111 | self._xml_data_element('DocSecurity', '0') 112 | 113 | def _write_scale_crop(self): 114 | # Write the element. 115 | self._xml_data_element('ScaleCrop', 'false') 116 | 117 | def _write_heading_pairs(self): 118 | # Write the element. 119 | self._xml_start_tag('HeadingPairs') 120 | self._write_vt_vector('variant', self.heading_pairs) 121 | self._xml_end_tag('HeadingPairs') 122 | 123 | def _write_titles_of_parts(self): 124 | # Write the element. 125 | parts_data = [] 126 | 127 | self._xml_start_tag('TitlesOfParts') 128 | 129 | for part_name in self.part_names: 130 | parts_data.append(('lpstr', part_name)) 131 | 132 | self._write_vt_vector('lpstr', parts_data) 133 | 134 | self._xml_end_tag('TitlesOfParts') 135 | 136 | def _write_vt_vector(self, base_type, vector_data): 137 | # Write the element. 138 | attributes = [ 139 | ('size', len(vector_data)), 140 | ('baseType', base_type), 141 | ] 142 | 143 | self._xml_start_tag('vt:vector', attributes) 144 | 145 | for vt_data in vector_data: 146 | if base_type == 'variant': 147 | self._xml_start_tag('vt:variant') 148 | 149 | self._write_vt_data(vt_data) 150 | 151 | if base_type == 'variant': 152 | self._xml_end_tag('vt:variant') 153 | 154 | self._xml_end_tag('vt:vector') 155 | 156 | def _write_vt_data(self, vt_data): 157 | # Write the elements such as and . 158 | self._xml_data_element("vt:%s" % vt_data[0], vt_data[1]) 159 | 160 | def _write_company(self): 161 | company = self.properties.get('company', '') 162 | 163 | self._xml_data_element('Company', company) 164 | 165 | def _write_manager(self): 166 | # Write the element. 167 | if 'manager' not in self.properties: 168 | return 169 | 170 | self._xml_data_element('Manager', self.properties['manager']) 171 | 172 | def _write_links_up_to_date(self): 173 | # Write the element. 174 | self._xml_data_element('LinksUpToDate', 'false') 175 | 176 | def _write_shared_doc(self): 177 | # Write the element. 178 | self._xml_data_element('SharedDoc', 'false') 179 | 180 | def _write_hyperlinks_changed(self): 181 | # Write the element. 182 | self._xml_data_element('HyperlinksChanged', 'false') 183 | 184 | def _write_app_version(self): 185 | # Write the element. 186 | self._xml_data_element('AppVersion', '12.0000') 187 | -------------------------------------------------------------------------------- /utils/lib/xlsxwriter/core.py: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # 3 | # Core - A class for writing the Excel XLSX Worksheet file. 4 | # 5 | # Copyright 2013-2015, John McNamara, jmcnamara@cpan.org 6 | # 7 | 8 | # Standard packages. 9 | from datetime import datetime 10 | 11 | # Package imports. 12 | from . import xmlwriter 13 | 14 | 15 | class Core(xmlwriter.XMLwriter): 16 | """ 17 | A class for writing the Excel XLSX Core file. 18 | 19 | 20 | """ 21 | 22 | ########################################################################### 23 | # 24 | # Public API. 25 | # 26 | ########################################################################### 27 | 28 | def __init__(self): 29 | """ 30 | Constructor. 31 | 32 | """ 33 | 34 | super(Core, self).__init__() 35 | 36 | self.properties = {} 37 | 38 | ########################################################################### 39 | # 40 | # Private API. 41 | # 42 | ########################################################################### 43 | 44 | def _assemble_xml_file(self): 45 | # Assemble and write the XML file. 46 | 47 | # Write the XML declaration. 48 | self._xml_declaration() 49 | 50 | self._write_cp_core_properties() 51 | self._write_dc_title() 52 | self._write_dc_subject() 53 | self._write_dc_creator() 54 | self._write_cp_keywords() 55 | self._write_dc_description() 56 | self._write_cp_last_modified_by() 57 | self._write_dcterms_created() 58 | self._write_dcterms_modified() 59 | self._write_cp_category() 60 | self._write_cp_content_status() 61 | 62 | self._xml_end_tag('cp:coreProperties') 63 | 64 | # Close the file. 65 | self._xml_close() 66 | 67 | def _set_properties(self, properties): 68 | # Set the document properties. 69 | self.properties = properties 70 | 71 | def _localtime_to_iso8601_date(self, date): 72 | # Convert to a ISO 8601 style "2010-01-01T00:00:00Z" date. 73 | if not date: 74 | date = datetime.now() 75 | 76 | return date.strftime("%Y-%m-%dT%H:%M:%SZ") 77 | 78 | ########################################################################### 79 | # 80 | # XML methods. 81 | # 82 | ########################################################################### 83 | 84 | def _write_cp_core_properties(self): 85 | # Write the element. 86 | 87 | xmlns_cp = ('http://schemas.openxmlformats.org/package/2006/' + 88 | 'metadata/core-properties') 89 | xmlns_dc = 'http://purl.org/dc/elements/1.1/' 90 | xmlns_dcterms = 'http://purl.org/dc/terms/' 91 | xmlns_dcmitype = 'http://purl.org/dc/dcmitype/' 92 | xmlns_xsi = 'http://www.w3.org/2001/XMLSchema-instance' 93 | 94 | attributes = [ 95 | ('xmlns:cp', xmlns_cp), 96 | ('xmlns:dc', xmlns_dc), 97 | ('xmlns:dcterms', xmlns_dcterms), 98 | ('xmlns:dcmitype', xmlns_dcmitype), 99 | ('xmlns:xsi', xmlns_xsi), 100 | ] 101 | 102 | self._xml_start_tag('cp:coreProperties', attributes) 103 | 104 | def _write_dc_creator(self): 105 | # Write the element. 106 | data = self.properties.get('author', '') 107 | 108 | self._xml_data_element('dc:creator', data) 109 | 110 | def _write_cp_last_modified_by(self): 111 | # Write the element. 112 | data = self.properties.get('author', '') 113 | 114 | self._xml_data_element('cp:lastModifiedBy', data) 115 | 116 | def _write_dcterms_created(self): 117 | # Write the element. 118 | date = self.properties.get('created', datetime.now()) 119 | 120 | xsi_type = 'dcterms:W3CDTF' 121 | 122 | date = self._localtime_to_iso8601_date(date) 123 | 124 | attributes = [('xsi:type', xsi_type,)] 125 | 126 | self._xml_data_element('dcterms:created', date, attributes) 127 | 128 | def _write_dcterms_modified(self): 129 | # Write the element. 130 | date = self.properties.get('created', datetime.now()) 131 | 132 | xsi_type = 'dcterms:W3CDTF' 133 | 134 | date = self._localtime_to_iso8601_date(date) 135 | 136 | attributes = [('xsi:type', xsi_type,)] 137 | 138 | self._xml_data_element('dcterms:modified', date, attributes) 139 | 140 | def _write_dc_title(self): 141 | # Write the element. 142 | if 'title' in self.properties: 143 | data = self.properties['title'] 144 | else: 145 | return 146 | 147 | self._xml_data_element('dc:title', data) 148 | 149 | def _write_dc_subject(self): 150 | # Write the element. 151 | if 'subject' in self.properties: 152 | data = self.properties['subject'] 153 | else: 154 | return 155 | 156 | self._xml_data_element('dc:subject', data) 157 | 158 | def _write_cp_keywords(self): 159 | # Write the element. 160 | if 'keywords' in self.properties: 161 | data = self.properties['keywords'] 162 | else: 163 | return 164 | 165 | self._xml_data_element('cp:keywords', data) 166 | 167 | def _write_dc_description(self): 168 | # Write the element. 169 | if 'comments' in self.properties: 170 | data = self.properties['comments'] 171 | else: 172 | return 173 | 174 | self._xml_data_element('dc:description', data) 175 | 176 | def _write_cp_category(self): 177 | # Write the element. 178 | if 'category' in self.properties: 179 | data = self.properties['category'] 180 | else: 181 | return 182 | 183 | self._xml_data_element('cp:category', data) 184 | 185 | def _write_cp_content_status(self): 186 | # Write the element. 187 | if 'status' in self.properties: 188 | data = self.properties['status'] 189 | else: 190 | return 191 | 192 | self._xml_data_element('cp:contentStatus', data) 193 | -------------------------------------------------------------------------------- /app/dbs/inc/Mysql.py: -------------------------------------------------------------------------------- 1 | #coding=utf-8 2 | ''' 3 | Created on 2015年8月21日 4 | 5 | @author: atool 6 | ''' 7 | 8 | import MySQLdb 9 | from app import db_config 10 | import time 11 | 12 | import sys 13 | from app.wraps.mysql_escape_warp import mysql_escape 14 | 15 | reload(sys) 16 | sys.setdefaultencoding('utf8') 17 | 18 | @mysql_escape 19 | def dict_2_sql_conditions(dict_param): 20 | conditions = [] 21 | p_keys = dict_param.keys() 22 | for k in p_keys: 23 | conditions.append(k + " = '" + str(dict_param[k]) + "'") 24 | 25 | return " and ".join(conditions) 26 | 27 | @mysql_escape 28 | def dict_2_insert_sql(table_name, dict_param): 29 | key = [] 30 | val = [] 31 | p_keys = dict_param.keys() 32 | for k in p_keys: 33 | key.append(k) 34 | val.append("'" + str(dict_param[k]) + "'") 35 | 36 | key = ",".join(key) 37 | val = ",".join(val) 38 | sql = "insert into " + table_name + "("+ key+") values (" + val + ");" 39 | return sql 40 | 41 | class Mysql(): 42 | #对象属性 43 | #连接 44 | conn = None 45 | #数据游标 46 | cursor = None 47 | 48 | #构造函数 49 | def __init__(self, host = db_config['DB_HOST'], 50 | port = db_config['DB_PORT'], 51 | user = db_config['DB_USER'], 52 | passwd = db_config['DB_PSW'], 53 | db = db_config['DB_NAME'], 54 | charset = db_config['DB_CHARSET']): 55 | self.host = host 56 | self.port = port 57 | self.user = user 58 | self.passwd = passwd 59 | self.db = db 60 | self.charset = charset 61 | 62 | self.__connect() 63 | 64 | 65 | def __connect(self): 66 | try: 67 | self.conn = MySQLdb.connect(host = self.host, port = self.port, user = self.user, passwd = self.passwd, db = self.db, charset = self.charset) 68 | #字典形式 69 | self.cursor = self.conn.cursor(cursorclass = MySQLdb.cursors.DictCursor) 70 | # print("Mysql Connect to %s: %s" % (self.host, str(self.port))) 71 | except MySQLdb.Error as e: 72 | print("Mysql Error %s: %s" % (self.host, e.args[1])) 73 | 74 | def exec_select(self, sql, params): 75 | ''' 76 | ps:执行查询类型的sql语句 77 | ''' 78 | try: 79 | self.cursor.execute(sql, params) 80 | result_set = self.cursor.fetchall() 81 | return result_set 82 | except MySQLdb.Error as e: 83 | print("Mysql Error:%s\nSQL:%s" %(e, sql)) 84 | return False 85 | 86 | def exec_select_one(self, sql, params): 87 | ''' 88 | ps:执行查询类型的sql语句 89 | ''' 90 | try: 91 | self.cursor.execute(sql, params) 92 | result_set = self.cursor.fetchone() 93 | return result_set 94 | except MySQLdb.Error as e: 95 | print("Mysql Error:%s\nSQL:%s" %(e, sql)) 96 | return False 97 | 98 | def exec_insert(self, sql, params): 99 | ''' 100 | ps:执行插入类sql语句 101 | ''' 102 | try: 103 | # 执行sql语句 104 | self.cursor.execute(sql, params) 105 | # 提交到数据库执行 106 | insert_id = self.conn.insert_id() 107 | self.conn.commit() 108 | return insert_id 109 | except MySQLdb.Error as e: 110 | print("Mysql Error:%s\nSQL:%s" %(e, sql)) 111 | self.conn.rollback() 112 | return False 113 | 114 | def exec_insert_dict(self, table_name, dict_param): 115 | ''' 116 | ps:执行插入数据,数据由一个dict给定,key为column名称 117 | ''' 118 | try: 119 | sql = dict_2_insert_sql(table_name, dict_param) 120 | return self.exec_insert(sql, ()) 121 | except MySQLdb.Error as e: 122 | print("Mysql Error:%s\nSQL:%s" %(e, sql)) 123 | self.conn.rollback() 124 | return False 125 | 126 | def exec_update(self, sql, params): 127 | ''' 128 | ps:执行更新类sql语句 129 | ''' 130 | try: 131 | # 执行sql语句 132 | self.cursor.execute(sql, params) 133 | row_count = self.cursor.rowcount 134 | # 提交到数据库执行 135 | self.conn.commit() 136 | if row_count == False: 137 | row_count = True 138 | return row_count 139 | except MySQLdb.Error as e: 140 | print("Mysql Error:%s\nSQL:%s" %(e, sql)) 141 | self.conn.rollback() 142 | return False 143 | ################### 144 | ################### 145 | def exec_sql(self, sql, params): 146 | try: 147 | n = self.cursor.execute(sql, params) 148 | return n 149 | except MySQLdb.Error as e: 150 | print("Mysql Error:%s\nSQL:%s" %(e, sql)) 151 | 152 | def get_last_insert_id(self): 153 | ''' 154 | ps:最后插入行id 155 | ''' 156 | return self.conn.insert_id() 157 | 158 | def get_influence_row_count(self): 159 | ''' 160 | ps:影响函数 161 | ''' 162 | return self.cursor.rowcount 163 | #for transation 164 | 165 | def commit(self): 166 | ''' 167 | PS:事物完成之后,commit 168 | ''' 169 | self.conn.commit() 170 | 171 | # @check_connect 172 | def rollback(self): 173 | ''' 174 | PS:事物失败之后,回退 175 | ''' 176 | self.conn.rollback() 177 | #end for transation 178 | ################### 179 | 180 | def close(self): 181 | if self.cursor: 182 | self.cursor.close() 183 | self.cursor = None 184 | if self.conn: 185 | self.conn.close() 186 | self.conn = None 187 | 188 | #test 189 | if __name__ == '__main__': 190 | start = time.time() 191 | for i in xrange(100): 192 | sql = "insert into abtest_users(user_type) values(%s)" 193 | params = (str(i), ) 194 | Mysql().exec_insert(sql, params) 195 | 196 | end = time.time() 197 | print '多连接:', 100 / (end - start) 198 | 199 | start = time.time() 200 | my = Mysql() 201 | for i in xrange(100): 202 | sql = "insert into abtest_users(user_type) values(%s)" 203 | params = (str(i), ) 204 | my.exec_insert(sql, params) 205 | 206 | end = time.time() 207 | print '单连接:', 100 / (end - start) 208 | -------------------------------------------------------------------------------- /utils/lib/xlsxwriter/chart_pie.py: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # 3 | # ChartPie - A class for writing the Excel XLSX Pie charts. 4 | # 5 | # Copyright 2013-2015, John McNamara, jmcnamara@cpan.org 6 | # 7 | 8 | from warnings import warn 9 | from . import chart 10 | 11 | 12 | class ChartPie(chart.Chart): 13 | """ 14 | A class for writing the Excel XLSX Pie charts. 15 | 16 | 17 | """ 18 | 19 | ########################################################################### 20 | # 21 | # Public API. 22 | # 23 | ########################################################################### 24 | 25 | def __init__(self, options=None): 26 | """ 27 | Constructor. 28 | 29 | """ 30 | super(ChartPie, self).__init__() 31 | 32 | if options is None: 33 | options = {} 34 | 35 | self.vary_data_color = 1 36 | self.rotation = 0 37 | 38 | # Set the available data label positions for this chart type. 39 | self.label_position_default = 'best_fit' 40 | self.label_positions = { 41 | 'center': 'ctr', 42 | 'inside_end': 'inEnd', 43 | 'outside_end': 'outEnd', 44 | 'best_fit': 'bestFit'} 45 | 46 | def set_rotation(self, rotation): 47 | """ 48 | Set the Pie/Doughnut chart rotation: the angle of the first slice. 49 | 50 | Args: 51 | rotation: First segment angle: 0 <= rotation <= 360. 52 | 53 | Returns: 54 | Nothing. 55 | 56 | """ 57 | if rotation is None: 58 | return 59 | 60 | # Ensure the rotation is in Excel's range. 61 | if rotation < 0 or rotation > 360: 62 | warn("Chart rotation %d outside Excel range: 0 <= rotation <= 360" 63 | % rotation) 64 | return 65 | 66 | self.rotation = int(rotation) 67 | 68 | ########################################################################### 69 | # 70 | # Private API. 71 | # 72 | ########################################################################### 73 | 74 | def _write_chart_type(self, args): 75 | # Override the virtual superclass method with a chart specific method. 76 | # Write the c:pieChart element. 77 | self._write_pie_chart(args) 78 | 79 | ########################################################################### 80 | # 81 | # XML methods. 82 | # 83 | ########################################################################### 84 | 85 | def _write_pie_chart(self, args): 86 | # Write the element. Over-ridden method to remove 87 | # axis_id code since Pie charts don't require val and cat axes. 88 | self._xml_start_tag('c:pieChart') 89 | 90 | # Write the c:varyColors element. 91 | self._write_vary_colors() 92 | 93 | # Write the series elements. 94 | for data in self.series: 95 | self._write_ser(data) 96 | 97 | # Write the c:firstSliceAng element. 98 | self._write_first_slice_ang() 99 | 100 | self._xml_end_tag('c:pieChart') 101 | 102 | def _write_plot_area(self): 103 | # Over-ridden method to remove the cat_axis() and val_axis() code 104 | # since Pie charts don't require those axes. 105 | # 106 | # Write the element. 107 | 108 | self._xml_start_tag('c:plotArea') 109 | 110 | # Write the c:layout element. 111 | self._write_layout(self.plotarea.get('layout'), 'plot') 112 | 113 | # Write the subclass chart type element. 114 | self._write_chart_type(None) 115 | 116 | self._xml_end_tag('c:plotArea') 117 | 118 | def _write_legend(self): 119 | # Over-ridden method to add to legend. 120 | # Write the element. 121 | 122 | position = self.legend_position 123 | font = self.legend_font 124 | delete_series = [] 125 | overlay = 0 126 | 127 | if (self.legend_delete_series is not None 128 | and type(self.legend_delete_series) is list): 129 | delete_series = self.legend_delete_series 130 | 131 | if position.startswith('overlay_'): 132 | position = position.replace('overlay_', '') 133 | overlay = 1 134 | 135 | allowed = { 136 | 'right': 'r', 137 | 'left': 'l', 138 | 'top': 't', 139 | 'bottom': 'b', 140 | } 141 | 142 | if position == 'none': 143 | return 144 | 145 | if position not in allowed: 146 | return 147 | 148 | position = allowed[position] 149 | 150 | self._xml_start_tag('c:legend') 151 | 152 | # Write the c:legendPos element. 153 | self._write_legend_pos(position) 154 | 155 | # Remove series labels from the legend. 156 | for index in delete_series: 157 | # Write the c:legendEntry element. 158 | self._write_legend_entry(index) 159 | 160 | # Write the c:layout element. 161 | self._write_layout(self.legend_layout, 'legend') 162 | 163 | # Write the c:overlay element. 164 | if overlay: 165 | self._write_overlay() 166 | 167 | # Write the c:txPr element. Over-ridden. 168 | self._write_tx_pr_legend(None, font) 169 | 170 | self._xml_end_tag('c:legend') 171 | 172 | def _write_tx_pr_legend(self, horiz, font): 173 | # Write the element for legends. 174 | 175 | if font and font.get('rotation'): 176 | rotation = font['rotation'] 177 | else: 178 | rotation = None 179 | 180 | self._xml_start_tag('c:txPr') 181 | 182 | # Write the a:bodyPr element. 183 | self._write_a_body_pr(rotation, horiz) 184 | 185 | # Write the a:lstStyle element. 186 | self._write_a_lst_style() 187 | 188 | # Write the a:p element. 189 | self._write_a_p_legend(font) 190 | 191 | self._xml_end_tag('c:txPr') 192 | 193 | def _write_a_p_legend(self, font): 194 | # Write the element for legends. 195 | 196 | self._xml_start_tag('a:p') 197 | 198 | # Write the a:pPr element. 199 | self._write_a_p_pr_legend(font) 200 | 201 | # Write the a:endParaRPr element. 202 | self._write_a_end_para_rpr() 203 | 204 | self._xml_end_tag('a:p') 205 | 206 | def _write_a_p_pr_legend(self, font): 207 | # Write the element for legends. 208 | attributes = [('rtl', 0)] 209 | 210 | self._xml_start_tag('a:pPr', attributes) 211 | 212 | # Write the a:defRPr element. 213 | self._write_a_def_rpr(font) 214 | 215 | self._xml_end_tag('a:pPr') 216 | 217 | def _write_vary_colors(self): 218 | # Write the element. 219 | attributes = [('val', 1)] 220 | 221 | self._xml_empty_tag('c:varyColors', attributes) 222 | 223 | def _write_first_slice_ang(self): 224 | # Write the element. 225 | attributes = [('val', self.rotation)] 226 | 227 | self._xml_empty_tag('c:firstSliceAng', attributes) 228 | -------------------------------------------------------------------------------- /app/templates/main/index_page.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | iOS私有api检查 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 18 | 19 | 20 | 21 | 31 | 32 | 33 |
34 |
35 |
36 | Home 37 | FAQ 38 |
39 |
40 |
41 |
42 |
43 |
44 |
Drop files here or click to upload.
45 | (文件拖放到这里或者点击选择ipa文件 检查iOS私有api使用情况) 46 |
47 | 48 |
49 |
50 | 62 |
63 |
64 |
65 |
66 |

Private API in APP

67 | 68 |
69 |
70 |
71 |
72 |
73 |
74 |

Private Framework in APP

75 | 76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |

FAQ

84 | 私有API检查的原因在于:苹果在app提审的时候,会检查app使用私有api的情况,对于使用了私有api的app,不予通过,这个工具的目地就是在提审之前检查一下,提高通过率。 85 | 92 | 93 |
一、功能
94 | 目前功能主要有三: 95 |
    96 |
  • 从ipa中提取一些基本信息,例如app名字,sdk版本,包名等,可以辅助QA日常工作。
  • 97 |
  • ipa架构检查,可以看出是否支持64位架构,可以辅助AppStore提审。
  • 98 |
  • ipa使用私有api情况,可以辅助AppStore提审。
  • 99 |
100 | 101 | 102 |
二、如何使用
103 | 运行方式有二,建议第二种web方式: 104 |
    105 |
  • 修改iOS_private.py main方法中的ipa路径,运行即可。
  • 106 |
  • 使用Web上传运行的方式,运行python run_web.py(请先配置flask运行环境),然后浏览器输入127.0.0.1:9527 将ipa拖入上传框等待即可看到检查结果。
  • 107 |
108 | 109 |
三、检查原理
110 |
    111 |
  • 通过mac上xcode的开发环境,找出不同sdk版本的public framework和private framework;通过class-dump反编译出public framework中的api,分别设置为集合PU和PR。
  • 112 |
  • 通过xcode代码提示的sqlite数据库查询出所有的document api,设置为集合DA。
  • 113 |
  • 那么PU - DA为公有framework中的私有api,设置为A
  • 114 |
  • PR为私有framework中的api都不能使用,则私有api集合PRAPI = A + PR
  • 115 |
  • 使用class-dump反编译ipa中的app文件,然后和PRAPI集合取交集即可获得。
  • 116 |
  • 其中有一些细节的集合操作,来准确定位api,例如使用方法名和类名来唯一确定API方法。
  • 117 |
118 | 119 |
四、Note
120 |

1. 私有的api = (class-dump Framework下的库生成的头文件中的api - (Framework下的头文件里的api = 有文档的api + 没有文档的api)) + PrivateFramework下的api。

121 |

2. 私有api在公开的Framework及私有的PrivateFramework都有。

122 |

3. 请暂时暂mac上运行,linux上暂时没有找到合适的、代替otool的工具,求推荐^^!

123 |
124 |
125 |
126 |
127 | 132 |
133 |
134 |
135 | 136 | 137 | 138 | 139 | 140 | 141 | -------------------------------------------------------------------------------- /utils/lib/xlsxwriter/contenttypes.py: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # 3 | # ContentTypes - A class for writing the Excel XLSX ContentTypes file. 4 | # 5 | # Copyright 2013-2015, John McNamara, jmcnamara@cpan.org 6 | # 7 | 8 | import copy 9 | from . import xmlwriter 10 | 11 | # Long namespace strings used in the class. 12 | app_package = 'application/vnd.openxmlformats-package.' 13 | app_document = 'application/vnd.openxmlformats-officedocument.' 14 | 15 | defaults = [ 16 | ['rels', app_package + 'relationships+xml'], 17 | ['xml', 'application/xml'], 18 | ] 19 | 20 | overrides = [ 21 | ['/docProps/app.xml', app_document + 'extended-properties+xml'], 22 | ['/docProps/core.xml', app_package + 'core-properties+xml'], 23 | ['/xl/styles.xml', app_document + 'spreadsheetml.styles+xml'], 24 | ['/xl/theme/theme1.xml', app_document + 'theme+xml'], 25 | ['/xl/workbook.xml', app_document + 'spreadsheetml.sheet.main+xml'], 26 | ] 27 | 28 | 29 | class ContentTypes(xmlwriter.XMLwriter): 30 | """ 31 | A class for writing the Excel XLSX ContentTypes file. 32 | 33 | 34 | """ 35 | 36 | ########################################################################### 37 | # 38 | # Public API. 39 | # 40 | ########################################################################### 41 | 42 | def __init__(self): 43 | """ 44 | Constructor. 45 | 46 | """ 47 | 48 | super(ContentTypes, self).__init__() 49 | 50 | # Copy the defaults in case we need to change them. 51 | self.defaults = copy.deepcopy(defaults) 52 | self.overrides = copy.deepcopy(overrides) 53 | 54 | ########################################################################### 55 | # 56 | # Private API. 57 | # 58 | ########################################################################### 59 | 60 | def _assemble_xml_file(self): 61 | # Assemble and write the XML file. 62 | 63 | # Write the XML declaration. 64 | self._xml_declaration() 65 | 66 | self._write_types() 67 | self._write_defaults() 68 | self._write_overrides() 69 | 70 | self._xml_end_tag('Types') 71 | 72 | # Close the file. 73 | self._xml_close() 74 | 75 | def _add_default(self, default): 76 | # Add elements to the ContentTypes defaults. 77 | self.defaults.append(default) 78 | 79 | def _add_override(self, override): 80 | # Add elements to the ContentTypes overrides. 81 | self.overrides.append(override) 82 | 83 | def _add_worksheet_name(self, worksheet_name): 84 | # Add the name of a worksheet to the ContentTypes overrides. 85 | worksheet_name = "/xl/worksheets/" + worksheet_name + ".xml" 86 | 87 | self._add_override((worksheet_name, 88 | app_document + 'spreadsheetml.worksheet+xml')) 89 | 90 | def _add_chartsheet_name(self, chartsheet_name): 91 | # Add the name of a chartsheet to the ContentTypes overrides. 92 | chartsheet_name = "/xl/chartsheets/" + chartsheet_name + ".xml" 93 | 94 | self._add_override((chartsheet_name, 95 | app_document + 'spreadsheetml.chartsheet+xml')) 96 | 97 | def _add_chart_name(self, chart_name): 98 | # Add the name of a chart to the ContentTypes overrides. 99 | chart_name = "/xl/charts/" + chart_name + ".xml" 100 | 101 | self._add_override((chart_name, app_document + 'drawingml.chart+xml')) 102 | 103 | def _add_drawing_name(self, drawing_name): 104 | # Add the name of a drawing to the ContentTypes overrides. 105 | drawing_name = "/xl/drawings/" + drawing_name + ".xml" 106 | 107 | self._add_override((drawing_name, app_document + 'drawing+xml')) 108 | 109 | def _add_vml_name(self): 110 | # Add the name of a VML drawing to the ContentTypes defaults. 111 | self._add_default(('vml', app_document + 'vmlDrawing')) 112 | 113 | def _add_comment_name(self, comment_name): 114 | # Add the name of a comment to the ContentTypes overrides. 115 | comment_name = "/xl/" + comment_name + ".xml" 116 | 117 | self._add_override((comment_name, 118 | app_document + 'spreadsheetml.comments+xml')) 119 | 120 | def _add_shared_strings(self): 121 | # Add the sharedStrings link to the ContentTypes overrides. 122 | self._add_override(('/xl/sharedStrings.xml', 123 | app_document + 'spreadsheetml.sharedStrings+xml')) 124 | 125 | def _add_calc_chain(self): 126 | # Add the calcChain link to the ContentTypes overrides. 127 | self._add_override(('/xl/calcChain.xml', 128 | app_document + 'spreadsheetml.calcChain+xml')) 129 | 130 | def _add_image_types(self, image_types): 131 | # Add the image default types. 132 | for image_type in image_types: 133 | self._add_default((image_type, 'image/' + image_type)) 134 | 135 | def _add_table_name(self, table_name): 136 | # Add the name of a table to the ContentTypes overrides. 137 | table_name = "/xl/tables/" + table_name + ".xml" 138 | 139 | self._add_override((table_name, 140 | app_document + 'spreadsheetml.table+xml')) 141 | 142 | def _add_vba_project(self): 143 | # Add a vbaProject to the ContentTypes defaults. 144 | 145 | # Change the workbook.xml content-type from xlsx to xlsm. 146 | for i, override in enumerate(self.overrides): 147 | if override[0] == '/xl/workbook.xml': 148 | self.overrides[i][1] = 'application/vnd.ms-excel.' \ 149 | 'sheet.macroEnabled.main+xml' 150 | 151 | self._add_default(('bin', 'application/vnd.ms-office.vbaProject')) 152 | 153 | ########################################################################### 154 | # 155 | # XML methods. 156 | # 157 | ########################################################################### 158 | 159 | def _write_defaults(self): 160 | # Write out all of the types. 161 | 162 | for extension, content_type in self.defaults: 163 | self._xml_empty_tag('Default', 164 | [('Extension', extension), 165 | ('ContentType', content_type)]) 166 | 167 | def _write_overrides(self): 168 | # Write out all of the types. 169 | for part_name, content_type in self.overrides: 170 | self._xml_empty_tag('Override', 171 | [('PartName', part_name), 172 | ('ContentType', content_type)]) 173 | 174 | def _write_types(self): 175 | # Write the element. 176 | xmlns = 'http://schemas.openxmlformats.org/package/2006/content-types' 177 | 178 | attributes = [('xmlns', xmlns,)] 179 | self._xml_start_tag('Types', attributes) 180 | 181 | def _write_default(self, extension, content_type): 182 | # Write the element. 183 | attributes = [ 184 | ('Extension', extension), 185 | ('ContentType', content_type), 186 | ] 187 | 188 | self._xml_empty_tag('Default', attributes) 189 | 190 | def _write_override(self, part_name, content_type): 191 | # Write the element. 192 | attributes = [ 193 | ('PartName', part_name), 194 | ('ContentType', content_type), 195 | ] 196 | 197 | self._xml_empty_tag('Override', attributes) 198 | -------------------------------------------------------------------------------- /utils/lib/xlsxwriter/xmlwriter.py: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # 3 | # XMLwriter - A base class for XlsxWriter classes. 4 | # 5 | # Used in conjunction with XlsxWriter. 6 | # 7 | # Copyright 2013-2015, John McNamara, jmcnamara@cpan.org 8 | # 9 | 10 | # Standard packages. 11 | import re 12 | import codecs 13 | 14 | # Standard packages in Python 2/3 compatibility mode. 15 | from .compatibility import StringIO 16 | 17 | 18 | class XMLwriter(object): 19 | """ 20 | Simple XML writer class. 21 | 22 | """ 23 | 24 | def __init__(self): 25 | self.fh = None 26 | self.escapes = re.compile('["&<>]') 27 | self.internal_fh = False 28 | 29 | def _set_filehandle(self, filehandle): 30 | # Set the writer filehandle directly. Mainly for testing. 31 | self.fh = filehandle 32 | self.internal_fh = False 33 | 34 | def _set_xml_writer(self, filename): 35 | # Set the XML writer filehandle for the object. 36 | if isinstance(filename, StringIO): 37 | self.internal_fh = False 38 | self.fh = filename 39 | else: 40 | self.internal_fh = True 41 | self.fh = codecs.open(filename, 'w', 'utf-8') 42 | 43 | def _xml_close(self): 44 | # Close the XML filehandle if we created it. 45 | if self.internal_fh: 46 | self.fh.close() 47 | 48 | def _xml_declaration(self): 49 | # Write the XML declaration. 50 | self.fh.write( 51 | """\n""") 52 | 53 | def _xml_start_tag(self, tag, attributes=[]): 54 | # Write an XML start tag with optional attributes. 55 | for key, value in attributes: 56 | value = self._escape_attributes(value) 57 | tag += ' %s="%s"' % (key, value) 58 | 59 | self.fh.write("<%s>" % tag) 60 | 61 | def _xml_start_tag_unencoded(self, tag, attributes=[]): 62 | # Write an XML start tag with optional, unencoded, attributes. 63 | # This is a minor speed optimisation for elements that don't 64 | # need encoding. 65 | for key, value in attributes: 66 | tag += ' %s="%s"' % (key, value) 67 | 68 | self.fh.write("<%s>" % tag) 69 | 70 | def _xml_end_tag(self, tag): 71 | # Write an XML end tag. 72 | self.fh.write("" % tag) 73 | 74 | def _xml_empty_tag(self, tag, attributes=[]): 75 | # Write an empty XML tag with optional attributes. 76 | for key, value in attributes: 77 | value = self._escape_attributes(value) 78 | tag += ' %s="%s"' % (key, value) 79 | 80 | self.fh.write("<%s/>" % tag) 81 | 82 | def _xml_empty_tag_unencoded(self, tag, attributes=[]): 83 | # Write an empty XML tag with optional, unencoded, attributes. 84 | # This is a minor speed optimisation for elements that don't 85 | # need encoding. 86 | for key, value in attributes: 87 | tag += ' %s="%s"' % (key, value) 88 | 89 | self.fh.write("<%s/>" % tag) 90 | 91 | def _xml_data_element(self, tag, data, attributes=[]): 92 | # Write an XML element containing data with optional attributes. 93 | end_tag = tag 94 | 95 | for key, value in attributes: 96 | value = self._escape_attributes(value) 97 | tag += ' %s="%s"' % (key, value) 98 | 99 | data = self._escape_data(data) 100 | self.fh.write("<%s>%s" % (tag, data, end_tag)) 101 | 102 | def _xml_string_element(self, index, attributes=[]): 103 | # Optimised tag writer for cell string elements in the inner loop. 104 | attr = '' 105 | 106 | for key, value in attributes: 107 | value = self._escape_attributes(value) 108 | attr += ' %s="%s"' % (key, value) 109 | 110 | self.fh.write("""%d""" % (attr, index)) 111 | 112 | def _xml_si_element(self, string, attributes=[]): 113 | # Optimised tag writer for shared strings elements. 114 | attr = '' 115 | 116 | for key, value in attributes: 117 | value = self._escape_attributes(value) 118 | attr += ' %s="%s"' % (key, value) 119 | 120 | string = self._escape_data(string) 121 | 122 | self.fh.write("""%s
""" % (attr, string)) 123 | 124 | def _xml_rich_si_element(self, string): 125 | # Optimised tag writer for shared strings rich string elements. 126 | 127 | self.fh.write("""%s""" % string) 128 | 129 | def _xml_number_element(self, number, attributes=[]): 130 | # Optimised tag writer for cell number elements in the inner loop. 131 | attr = '' 132 | 133 | for key, value in attributes: 134 | value = self._escape_attributes(value) 135 | attr += ' %s="%s"' % (key, value) 136 | 137 | self.fh.write("""%.15g""" % (attr, number)) 138 | 139 | def _xml_formula_element(self, formula, result, attributes=[]): 140 | # Optimised tag writer for cell formula elements in the inner loop. 141 | attr = '' 142 | 143 | for key, value in attributes: 144 | value = self._escape_attributes(value) 145 | attr += ' %s="%s"' % (key, value) 146 | 147 | self.fh.write("""%s%s""" 148 | % (attr, self._escape_data(formula), 149 | self._escape_data(result))) 150 | 151 | def _xml_inline_string(self, string, preserve, attributes=[]): 152 | # Optimised tag writer for inlineStr cell elements in the inner loop. 153 | attr = '' 154 | t_attr = '' 155 | 156 | # Set the attribute to preserve whitespace. 157 | if preserve: 158 | t_attr = ' xml:space="preserve"' 159 | 160 | for key, value in attributes: 161 | value = self._escape_attributes(value) 162 | attr += ' %s="%s"' % (key, value) 163 | 164 | string = self._escape_data(string) 165 | 166 | self.fh.write("""%s""" % 167 | (attr, t_attr, string)) 168 | 169 | def _xml_rich_inline_string(self, string, attributes=[]): 170 | # Optimised tag writer for rich inlineStr in the inner loop. 171 | attr = '' 172 | 173 | for key, value in attributes: 174 | value = self._escape_attributes(value) 175 | attr += ' %s="%s"' % (key, value) 176 | 177 | self.fh.write("""%s""" % 178 | (attr, string)) 179 | 180 | def _escape_attributes(self, attribute): 181 | # Escape XML characters in attributes. 182 | try: 183 | if not self.escapes.search(attribute): 184 | return attribute 185 | except TypeError: 186 | return attribute 187 | 188 | attribute = attribute.replace('&', '&') 189 | attribute = attribute.replace('"', '"') 190 | attribute = attribute.replace('<', '<') 191 | attribute = attribute.replace('>', '>') 192 | 193 | return attribute 194 | 195 | def _escape_data(self, data): 196 | # Escape XML characters in data sections of tags. Note, this 197 | # is different from _escape_attributes() in that double quotes 198 | # are not escaped by Excel. 199 | try: 200 | if not self.escapes.search(data): 201 | return data 202 | except TypeError: 203 | return data 204 | 205 | data = data.replace('&', '&') 206 | data = data.replace('<', '<') 207 | data = data.replace('>', '>') 208 | 209 | return data 210 | -------------------------------------------------------------------------------- /iOS_private.py: -------------------------------------------------------------------------------- 1 | #coding=utf-8 2 | ''' 3 | Created on 2015年10月27日 4 | iOS private api检查入口 5 | @author: atool 6 | ''' 7 | import os, shutil 8 | from utils import report_utils, utils 9 | from dump import otool_utils, codesign_utils 10 | from api import app_utils, api_utils 11 | from db import api_dbs 12 | # from app.utils import IpaParse 13 | from app.utils import checkipa 14 | 15 | def get_executable_path(ipa_path, pid): 16 | ''' 17 | info: unzip ipa, get execute app path 18 | ''' 19 | if not os.path.exists(ipa_path): 20 | #不存在,返回检查结果为空值 21 | return False 22 | cur_dir = os.getcwd() 23 | dest = os.path.join(cur_dir, 'tmp/' + pid) 24 | if not os.path.exists(dest): 25 | os.mkdir(dest) 26 | app_path = app_utils.unzip_ipa(ipa_path, dest) #解压ipa,获得xxx.app目录路径 27 | app = app_utils.get_executable_file(app_path) 28 | 29 | return app 30 | 31 | #检查私有api,返回三个参数 32 | def check_private_api(app, pid): 33 | strings = app_utils.get_app_strings(app, pid) #一般是app中的一些可打印文本 34 | #app中的私有库和公有库 .framework 35 | private, public = otool_utils.otool_app(app) 36 | print '=' * 15 37 | print 'private:', len(private) 38 | print 'public', len(public) 39 | print '=' * 15 40 | 41 | dump_result = app_utils.get_dump_result(app) 42 | app_varibles = app_utils.get_app_variables(dump_result, pid) #app自定义的一些方法,不需要检查 43 | left = strings - app_varibles #去除一些app中开发人员自定义的方法,剩余app中的一些字符串 44 | 45 | app_methods = app_utils.get_app_methods(dump_result, pid) #dump-class分析出app中的类和方法名 46 | 47 | app_apis = [] 48 | for m in app_methods: 49 | class_name = m["class"] if m["class"] != "ctype" else 'cur_app' 50 | #if m["class"] != "ctype" else 'cur_app' 51 | method_list = m["methods"] 52 | m_type = m["type"] 53 | for m in method_list: 54 | tmp_api = {} 55 | tmp_api['api_name'] = m 56 | tmp_api['class_name'] = class_name 57 | tmp_api['type'] = m_type 58 | #tmp_api['header_file'] = '' 59 | #tmp_api['sdk'] = '' 60 | #tmp_api['framework'] = '' 61 | app_apis.append(tmp_api) 62 | 63 | api_set = api_dbs.get_private_api_list(public) #数据库中的私有api,去除了whitelist白名单 64 | print '=' * 15 65 | print 'left app_varibles:', len(left) 66 | print 'app_methods:', len(app_apis) 67 | print 'private length:', len(api_set) 68 | print '=' * 15 69 | inter_api = api_utils.intersection_list_and_api(left, api_set) # app中的api和数据库中的私有api取交集,获得app中的私有api关键字数据 70 | 71 | methods_in_app, method_not_in = api_utils.intersection_api(app_apis, inter_api) #app中的私有方法 72 | 73 | print '=' * 15 74 | print 'methods_in_app', len(methods_in_app) 75 | print 'methods_not_in_app', len(method_not_in) 76 | # for i in xrange(20): 77 | # print methods_not_in_app[i] 78 | print '=' * 15 79 | 80 | 81 | return methods_in_app, method_not_in, private 82 | 83 | #检查架构,返回架构数组 84 | def check_architectures(app): 85 | arcs = app_utils.check_architectures(app) 86 | return arcs 87 | 88 | #检查xcode ghost,返回bool 89 | def check_xcode_ghost(app): 90 | return app_utils.check_xcode_ghost(app) 91 | 92 | #检查info和provision文件,并获取建议和错误配置 93 | def check_app_info_and_provision(ipa): 94 | return checkipa.process_ipa(ipa) 95 | 96 | #检查codesign信息 97 | def check_codesign(app): 98 | return codesign_utils.codesignapp(app) 99 | 100 | #ipa的md5 101 | def get_file_md5(ipa): 102 | return app_utils.file_md5(ipa) 103 | 104 | #检查单个的app,获得结果字典 105 | def check_ipa(ipa_path): 106 | result = {} #每个app的检查结果 107 | pid = utils.get_unique_str() 108 | print '1.', '+' * 10, 'get_file_md5' 109 | result['md5'] = get_file_md5(ipa_path) 110 | 111 | print '2.', '+' * 10, 'check_app_info_and_provision' 112 | rsts = check_app_info_and_provision(ipa_path) 113 | for key in rsts.keys(): 114 | result[key] = rsts[key] 115 | #检查ios私有api 116 | print '3.', '+' * 10, 'check_private_api' 117 | app = get_executable_path(ipa_path, pid) 118 | if not app: 119 | #找不到math-o文件,说明不是正常的ipa,忽略 120 | return False 121 | 122 | methods_in_app, _, private = check_private_api(app, pid) 123 | result['private_apis'] = methods_in_app 124 | # result['private_apis_not'] = _ 125 | result['private_frameworks'] = list(private) 126 | #检查ipa 64支持情况 127 | print '4.', '+' * 10, 'check_architectures' 128 | arcs = check_architectures(app) 129 | result['arcs'] = arcs 130 | if len(arcs) < 2: 131 | result['error'].append({'label': 'Architecture:', 132 | 'description': 'app may be not support 64-bit'}) 133 | #检查ghost情况 134 | print '5.', '+' * 10, 'check_xcode_ghost' 135 | ghost = check_xcode_ghost(app) 136 | result['ghost'] = ghost 137 | #检查codesign 138 | print '6.', '+' * 10, 'check_private_api' 139 | codesign = check_codesign(app) 140 | result['codesign'] = codesign 141 | 142 | print '7.', '+' * 10, 'remove tmp files' 143 | cur_dir = os.getcwd() #删除检查临时目录 144 | dest_tmp = os.path.join(cur_dir, 'tmp/' + pid) 145 | # print 'tmp:', dest_tmp 146 | if os.path.exists(dest_tmp): 147 | shutil.rmtree(dest_tmp) 148 | 149 | return result 150 | 151 | def batch_check(app_folder, excel_path): 152 | ''' 153 | 批量检测多个ipa,并产生excel报告 154 | ''' 155 | #遍历folder,找出.ipa文件 156 | if not app_folder or not excel_path: 157 | return False 158 | 159 | check_results = [] 160 | ipa_list = os.listdir(app_folder) 161 | for ipa in ipa_list: 162 | print 'start check :', ipa 163 | if ipa.endswith('.ipa'): 164 | ipa_path = os.path.join(app_folder, ipa) 165 | #单个app的检查结果 166 | try: 167 | r = check_ipa(ipa_path) 168 | if r: 169 | check_results.append(r) 170 | except Exception, e: 171 | print e 172 | continue 173 | 174 | #将结果转化成excel报告 175 | report_utils.excel_report(check_results, excel_path) 176 | return excel_path 177 | 178 | if __name__ == '__main__': 179 | ####### 180 | #check one app 181 | # ipa_path = "/Users/summer-wj/code/svn/ljsg_for_netease_20150928_resign.ipa" 182 | 183 | # private_1 = open("tmp/private_1.txt", "w") 184 | # private_2 = open("tmp/private_2.txt", "w") 185 | # #将strings内容输出到文件中 186 | # pid = app_utils.get_unique_str() 187 | # app = get_executable_path(ipa_path, pid) 188 | # print app 189 | # arcs = check_architectures(app) 190 | # print arcs 191 | # a, b, c = check_private_api(app, pid) 192 | # print "=" * 50 193 | # print len(a), "Private Methods in App:" 194 | # print "*" * 50 195 | # for aa in a: 196 | # print aa 197 | # print >>private_1, aa 198 | 199 | # print "=" * 50 200 | # print len(b), "Private Methods not in App, May in Framework Used:" 201 | # print "*" * 50 202 | # for bb in b: 203 | # print >>private_2, bb 204 | 205 | # print "=" * 50 206 | # print len(c), "Private Framework in App:" 207 | # print "*" * 50 208 | 209 | ########## 210 | #test batch check ipa 211 | cwd = os.getcwd() 212 | excel_path = os.path.join(cwd, 'tmp/' + utils.get_unique_str() + '.xlsx') 213 | # excel_path = os.path.join(cwd, 'tmp/test.xlsx') # for test 214 | print excel_path 215 | ipa_folder = '/Users/netease/Downloads/ipas/mg/' 216 | # ipa_folder = '/Users/netease/Music/iTunes/iTunes Media/Mobile Applications/' 217 | # ipa_folder = '/Users/netease/Music/iTunes/iTunes Media/' 218 | print batch_check(ipa_folder, excel_path) 219 | 220 | ######### 221 | #test check arcs 222 | # app_path = '/Users/netease/Downloads/ipas/mg/Payload' 223 | # app = app_utils.get_executable_file(app_path) 224 | # print check_architectures(app) 225 | 226 | -------------------------------------------------------------------------------- /api/api_utils.py: -------------------------------------------------------------------------------- 1 | #coding=utf-8 2 | ''' 3 | Created on 2015年10月27日 4 | 各种api的获取方式,最后通过这些api计算出私有api的集合,用来后续计算app中是否使用私有api 5 | @author: atool 6 | ''' 7 | from db import dsidx_dbs 8 | import os 9 | import api 10 | from api import api_helpers 11 | from dump import class_dump_utils 12 | from itertools import groupby 13 | 14 | def framework_dump_apis(sdk, framework_folder): 15 | ''' 16 | class-dump Framework下的库生成的头文件中的api 17 | sdk: sdk version 18 | info: 用class-dump对所有的公开库(/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator7.0.sdk/System/Library/Frameworks)进行逆向工程得到所有的头文件内容。提取每个.h文件中的api得到api集合set_A。 19 | ''' 20 | 21 | #分析frame,将头文件输出到tmp/pub-headers目录 22 | framework_header_folder = _dump_frameworks(framework_folder, 'pub-headers') 23 | 24 | #得到.h文件 25 | all_headers = _get_headers_from_path(framework_header_folder) 26 | #解析文件内容,获得api 27 | framework_apis = _get_apis_from_headers(sdk, all_headers) 28 | 29 | return framework_apis 30 | 31 | 32 | def framework_header_apis(sdk, framework_folder): 33 | ''' 34 | get all public frameworks' header files(documented) 35 | ''' 36 | all_headers = _get_headers_from_path(framework_folder) 37 | 38 | framework_apis = _get_apis_from_headers(sdk, all_headers) 39 | 40 | return framework_apis 41 | 42 | #没有文档的api 43 | def undocument_apis(sdk): 44 | ''' 45 | set_C = set_header - set_B 46 | info:不在文档中的api方法 47 | ''' 48 | framework_header_apis(sdk) - document_apis(sdk) 49 | 50 | #有文档的api 51 | def document_apis(sdk, db_path): 52 | ''' 53 | has document apis 54 | info:获得带文档的api。(/Users/Test/Library/Developer/Shared/Documentation/DocSets/com.apple.adc.documentation.AppleiOS7.0.iOSLibrary.docset/Contents/Resources),在里面有个docSet.dsidx的文件,这就是Xcode针对api做的数据库,从这里可以获得带文档的api的各种信息了,从而有了带文档的api集合set_B。 55 | 56 | ''' 57 | doc_apis = [] 58 | #从dsidx 数据库中获得初始数据 59 | apiset = dsidx_dbs.get_dsidx_apis(db_path) 60 | #过滤初始数据获得有文档的api集合 61 | for api in apiset: 62 | Z_PK = api['Z_PK'] 63 | ZDECLAREDIN = api['ZDECLAREDIN'] 64 | ZCONTAINER = api['ZCONTAINER'] 65 | # get containername from ZCONTAINER table 66 | container_name = '' 67 | if Z_PK: 68 | container_name = dsidx_dbs.get_container_name(ZCONTAINER, db_path) or '' 69 | # get frameworkname and headerpath from ZHEADER table 70 | 71 | framework_name = '' 72 | header_path = '' 73 | if ZDECLAREDIN: 74 | frame_header = dsidx_dbs.get_framework_and_header(ZDECLAREDIN, db_path) 75 | if frame_header: 76 | framework_name = frame_header.get('ZFRAMEWORKNAME', '') 77 | header_path = frame_header.get('ZHEADERPATH', '') 78 | 79 | doc_apis.append({'api_name': api['ZTOKENNAME'], 'class_name': container_name, 'type': api['ZTOKENTYPE'], 'header_file': header_path, 'framework': framework_name, 'sdk': sdk}) 80 | return doc_apis 81 | 82 | def private_framework_dump_apis(sdk, framework_folder): 83 | ''' 84 | PrivateFramework下的api 85 | ''' 86 | framework_header_folder = _dump_frameworks(framework_folder, 'pri-headers') 87 | #得到.h文件 88 | all_headers = _get_headers_from_path(framework_header_folder) 89 | #解析文件内容,获得api 90 | framework_apis = _get_apis_from_headers(sdk, all_headers) 91 | 92 | return framework_apis 93 | 94 | def all_private_apis(sdk, include_private_framework = False): 95 | ''' 96 | info: 私有的api = ( 97 | class-dump Framework下的库生成的头文件中的api 98 | - 99 | (Framework下的头文件里的api = 有文档的api + 没有文档的api) 100 | ) 101 | + 102 | PrivateFramework下的api。 103 | ''' 104 | pub_private_apis = framework_dump_apis(sdk) - framework_header_apis(sdk) 105 | 106 | if include_private_framework: 107 | pri_private_apis = private_framework_apis(sdk) 108 | 109 | return pub_private_apis + pri_private_apis 110 | return pub_private_apis 111 | 112 | 113 | #目录迭代器 114 | def iterate_dir(framework, prefix, path): 115 | files = [] 116 | for f in os.listdir(path): 117 | if os.path.isfile(os.path.join(path, f)): 118 | files.append((framework, prefix + f, os.path.join(path, f))) 119 | elif os.path.isdir(os.path.join(path, f)): 120 | files += iterate_dir(framework, prefix + f + "/", os.path.join(path, f)) 121 | return files 122 | 123 | #从framework目录,获得所有的.h文件 124 | def _get_headers_from_path(framework_folder): 125 | all_headers_path = [] 126 | 127 | frameworks = os.listdir(framework_folder) 128 | #print frameworks 129 | for framework in frameworks: 130 | if framework.endswith(".framework"): 131 | header_path = os.path.join(os.path.join(framework_folder, framework), 'Headers') 132 | if os.path.exists(header_path): 133 | all_headers_path += iterate_dir(framework, "", os.path.join(framework_folder, header_path)) 134 | 135 | return all_headers_path 136 | 137 | 138 | def _get_apis_from_headers(sdk, all_headers): 139 | framework_apis = [] 140 | for header in all_headers: 141 | #get apis from .h file 142 | apis = api_helpers.get_apis_of_file(header[2]) 143 | 144 | for api in apis: 145 | class_name = api["class"] if api["class"] != "ctype" else header[1] 146 | method_list = api["methods"] 147 | m_type = api["type"] 148 | for m in method_list: 149 | tmp_api = {} 150 | tmp_api['api_name'] = m 151 | tmp_api['class_name'] = class_name 152 | tmp_api['type'] = m_type 153 | tmp_api['header_file'] = header[1] 154 | tmp_api['sdk'] = sdk 155 | tmp_api['framework'] = header[0] 156 | framework_apis.append(tmp_api) 157 | 158 | return framework_apis 159 | 160 | #使用calss-dump分析framework目录,将.h文件输出到对应的目录 161 | def _dump_frameworks(framework_folder, prefix): 162 | cur_dir = os.getcwd() 163 | headers_path = os.path.join(cur_dir, "tmp/" + prefix) 164 | 165 | #讲frame dump到./tmp/pub_headers目录中 166 | for framework in os.listdir(framework_folder): 167 | if framework.endswith(".framework"): 168 | frame_path = os.path.join(framework_folder, framework) 169 | out_path = os.path.join(headers_path, framework) 170 | out_path = os.path.join(out_path, 'Headers') #构造目录结果: /tmp/xxx.framework/Headers/xx.h 171 | class_dump_utils.dump_framework(frame_path, out_path) 172 | return headers_path 173 | 174 | 175 | #api去重 176 | def deduplication_api_list(apis): 177 | 178 | def api_gourpby(api): 179 | return api['api_name'] + '/' + api['class_name'] 180 | 181 | new_apis = [] 182 | 183 | apis = sorted(apis, key = api_gourpby) 184 | 185 | for g, l in groupby(apis, key = api_gourpby): 186 | l = list(l) 187 | if l and len(l) > 0: 188 | new_apis.append(l[0]) 189 | 190 | return new_apis 191 | 192 | 193 | def _apis_2_dict(apis): 194 | apis_dict = {} 195 | if apis: 196 | for api in apis: 197 | api_hash = api['api_name'] + '/' + api['class_name'] 198 | apis_dict[api_hash] = api 199 | 200 | return apis_dict 201 | 202 | def intersection_api(apis_1, apis_2): 203 | ''' 204 | return intersection of apis_1 and apis_2 205 | in apis_1, also in apis_2 206 | ''' 207 | not_in_apis = [] 208 | apis = [] 209 | apis_1_dict = _apis_2_dict(apis_1) 210 | 211 | for api in apis_2: 212 | api_hash = api['api_name'] + '/' + api['class_name'] 213 | if apis_1_dict.get(api_hash, None): 214 | apis.append(api) 215 | else: 216 | not_in_apis.append(api) 217 | 218 | return apis, not_in_apis 219 | 220 | def intersection_list_and_api(l, apis): 221 | ''' 222 | return intersection of api_name list and api dict list 223 | ''' 224 | #def _apis_2_api_dict(apis): 225 | new_apis = [] 226 | #new_methods = set() 227 | #apis_dict = _apis_2_dict(apis) 228 | 229 | for api_tmp in apis: 230 | api_hash = api_tmp['api_name'] 231 | if api_hash in l: 232 | new_apis.append(api_tmp) 233 | 234 | return new_apis 235 | -------------------------------------------------------------------------------- /utils/lib/xlsxwriter/compat_collections.py: -------------------------------------------------------------------------------- 1 | """ 2 | From the GC3Pie project: https://code.google.com/p/gc3pie/ 3 | 4 | A backport of the Python standard `collections` package, providing 5 | `namedtuple` and `defaultdict` also on Python 2.4 and 2.5. 6 | 7 | This package actually imports your Python `collections`, and adds 8 | its own versions of `namedtuple` and `defaultdict` only if they are 9 | missing. 10 | """ 11 | 12 | from collections import * 13 | import sys 14 | 15 | try: 16 | defaultdict 17 | except NameError: 18 | class defaultdict(dict): 19 | """ 20 | A backport of `defaultdict` to Python 2.4 21 | See http://docs.python.org/library/collections.html 22 | """ 23 | def __new__(cls, default_factory=None): 24 | return dict.__new__(cls) 25 | 26 | def __init__(self, default_factory): 27 | self.default_factory = default_factory 28 | 29 | def __missing__(self, key): 30 | try: 31 | return self.default_factory() 32 | except: 33 | raise KeyError("Key '%s' not in dictionary" % key) 34 | 35 | def __getitem__(self, key): 36 | if not dict.__contains__(self, key): 37 | dict.__setitem__(self, key, self.__missing__(key)) 38 | return dict.__getitem__(self, key) 39 | 40 | 41 | try: 42 | namedtuple 43 | except NameError: 44 | # Use Raymond Hettinger's original `namedtuple` package. 45 | # 46 | # Source originally taken from: 47 | # http://code.activestate.com/recipes/500261-named-tuples/ 48 | from operator import itemgetter as _itemgetter 49 | from keyword import iskeyword as _iskeyword 50 | import sys as _sys 51 | 52 | def namedtuple(typename, field_names, verbose=False, rename=False): 53 | """Returns a new subclass of tuple with named fields. 54 | 55 | >>> Point = namedtuple('Point', 'x y') 56 | >>> Point.__doc__ # docstring for the new class 57 | 'Point(x, y)' 58 | >>> p = Point(11, y=22) # instantiate with positional args or keywords 59 | >>> p[0] + p[1] # indexable like a plain tuple 60 | 33 61 | >>> x, y = p # unpack like a regular tuple 62 | >>> x, y 63 | (11, 22) 64 | >>> p.x + p.y # fields also accessable by name 65 | 33 66 | >>> d = p._asdict() # convert to a dictionary 67 | >>> d['x'] 68 | 11 69 | >>> Point(**d) # convert from a dictionary 70 | Point(x=11, y=22) 71 | >>> p._replace(x=100) # _replace() is like str.replace() but targets named fields 72 | Point(x=100, y=22) 73 | 74 | """ 75 | 76 | # Parse and validate the field names. Validation serves two purposes, 77 | # generating informative error messages and preventing template injection attacks. 78 | if isinstance(field_names, basestring): 79 | field_names = field_names.replace(',', ' ').split() # names separated by whitespace and/or commas 80 | field_names = tuple(map(str, field_names)) 81 | if rename: 82 | names = list(field_names) 83 | seen = set() 84 | for i, name in enumerate(names): 85 | if (not min(c.isalnum() or c == '_' for c in name) 86 | or _iskeyword(name) 87 | or not name or name[0].isdigit() 88 | or name.startswith('_') 89 | or name in seen): 90 | names[i] = '_%d' % i 91 | 92 | seen.add(name) 93 | field_names = tuple(names) 94 | for name in (typename,) + field_names: 95 | if not min(c.isalnum() or c == '_' for c in name): 96 | raise ValueError('Type names and field names can only contain alphanumeric characters and underscores: %r' % name) 97 | if _iskeyword(name): 98 | raise ValueError('Type names and field names cannot be a keyword: %r' % name) 99 | if name[0].isdigit(): 100 | raise ValueError('Type names and field names cannot start with a number: %r' % name) 101 | seen_names = set() 102 | for name in field_names: 103 | if name.startswith('_') and not rename: 104 | raise ValueError('Field names cannot start with an underscore: %r' % name) 105 | if name in seen_names: 106 | raise ValueError('Encountered duplicate field name: %r' % name) 107 | seen_names.add(name) 108 | 109 | # Create and fill-in the class template 110 | numfields = len(field_names) 111 | argtxt = repr(field_names).replace("'", "")[1:-1] # tuple repr without parens or quotes 112 | reprtxt = ', '.join('%s=%%r' % name for name in field_names) 113 | template = '''class %(typename)s(tuple): 114 | '%(typename)s(%(argtxt)s)' \n 115 | __slots__ = () \n 116 | _fields = %(field_names)r \n 117 | def __new__(_cls, %(argtxt)s): 118 | return _tuple.__new__(_cls, (%(argtxt)s)) \n 119 | @classmethod 120 | def _make(cls, iterable, new=tuple.__new__, len=len): 121 | 'Make a new %(typename)s object from a sequence or iterable' 122 | result = new(cls, iterable) 123 | if len(result) != %(numfields)d: 124 | raise TypeError('Expected %(numfields)d arguments, got %%d' %% len(result)) 125 | return result \n 126 | def __repr__(self): 127 | return '%(typename)s(%(reprtxt)s)' %% self \n 128 | def _asdict(self): 129 | 'Return a new dict which maps field names to their values' 130 | return dict(zip(self._fields, self)) \n 131 | def _replace(_self, **kwds): 132 | 'Return a new %(typename)s object replacing specified fields with new values' 133 | result = _self._make(map(kwds.pop, %(field_names)r, _self)) 134 | if kwds: 135 | raise ValueError('Got unexpected field names: %%r' %% kwds.keys()) 136 | return result \n 137 | def __getnewargs__(self): 138 | return tuple(self) \n\n''' % locals() 139 | for i, name in enumerate(field_names): 140 | template += ' %s = _property(_itemgetter(%d))\n' % (name, i) 141 | if verbose: 142 | print(template) 143 | 144 | # Execute the template string in a temporary namespace 145 | namespace = dict(_itemgetter=_itemgetter, __name__='namedtuple_%s' % typename, 146 | _property=property, _tuple=tuple) 147 | try: 148 | exec(template) in namespace 149 | except SyntaxError: 150 | e = sys.exc_info()[1] 151 | raise SyntaxError(str(e) + ':\n' + template) 152 | result = namespace[typename] 153 | 154 | # For pickling to work, the __module__ variable needs to be set to the frame 155 | # where the named tuple is created. Bypass this step in enviroments where 156 | # sys._getframe is not defined (Jython for example) or sys._getframe is not 157 | # defined for arguments greater than 0 (IronPython). 158 | try: 159 | result.__module__ = _sys._getframe(1).f_globals.get('__name__', '__main__') 160 | except (AttributeError, ValueError): 161 | pass 162 | 163 | return result 164 | 165 | 166 | if __name__ == '__main__': 167 | # verify that instances can be pickled 168 | from cPickle import loads, dumps 169 | Point = namedtuple('Point', 'x, y', True) 170 | p = Point(x=10, y=20) 171 | assert p == loads(dumps(p, -1)) 172 | 173 | # test and demonstrate ability to override methods 174 | class Point(namedtuple('Point', 'x y')): 175 | @property 176 | def hypot(self): 177 | return (self.x ** 2 + self.y ** 2) ** 0.5 178 | 179 | def __str__(self): 180 | return 'Point: x=%6.3f y=%6.3f hypot=%6.3f' % (self.x, self.y, self.hypot) 181 | 182 | for p in Point(3, 4), Point(14, 5), Point(9. / 7, 6): 183 | print(p) 184 | 185 | class Point(namedtuple('Point', 'x y')): 186 | 'Point class with optimized _make() and _replace() without error-checking' 187 | _make = classmethod(tuple.__new__) 188 | 189 | def _replace(self, _map=map, **kwds): 190 | return self._make(_map(kwds.get, ('x', 'y'), self)) 191 | 192 | print(Point(11, 22)._replace(x=100)) 193 | 194 | import doctest 195 | TestResults = namedtuple('TestResults', 'failed attempted') 196 | print(TestResults(*doctest.testmod())) 197 | -------------------------------------------------------------------------------- /utils/lib/xlsxwriter/theme.py: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # 3 | # Theme - A class for writing the Excel XLSX Worksheet file. 4 | # 5 | # Copyright 2013-2015, John McNamara, jmcnamara@cpan.org 6 | # 7 | 8 | # Standard packages. 9 | import codecs 10 | import sys 11 | 12 | # Standard packages in Python 2/3 compatibility mode. 13 | from .compatibility import StringIO 14 | 15 | 16 | class Theme(object): 17 | """ 18 | A class for writing the Excel XLSX Theme file. 19 | 20 | 21 | """ 22 | 23 | ########################################################################### 24 | # 25 | # Public API. 26 | # 27 | ########################################################################### 28 | 29 | def __init__(self): 30 | """ 31 | Constructor. 32 | 33 | """ 34 | super(Theme, self).__init__() 35 | self.fh = None 36 | self.internal_fh = False 37 | 38 | ########################################################################### 39 | # 40 | # Private API. 41 | # 42 | ########################################################################### 43 | 44 | def _assemble_xml_file(self): 45 | # Assemble and write the XML file. 46 | self._write_theme_file() 47 | if self.internal_fh: 48 | self.fh.close() 49 | 50 | def _set_xml_writer(self, filename): 51 | # Set the XML writer filehandle for the object. 52 | if isinstance(filename, StringIO): 53 | self.internal_fh = False 54 | self.fh = filename 55 | else: 56 | self.internal_fh = True 57 | self.fh = codecs.open(filename, 'w', 'utf-8') 58 | 59 | ########################################################################### 60 | # 61 | # XML methods. 62 | # 63 | ########################################################################### 64 | 65 | def _write_theme_file(self): 66 | # Write a default theme.xml file. 67 | 68 | # The theme is encoded to allow Python 2.5/Jython support. 69 | default_theme = """\n""" 70 | 71 | if sys.version_info < (3, 0, 0): 72 | default_theme = default_theme.decode('unicode-escape') 73 | 74 | self.fh.write(default_theme) 75 | -------------------------------------------------------------------------------- /app/static/res/css/dropzone.min.css: -------------------------------------------------------------------------------- 1 | @-webkit-keyframes passing-through{0%{opacity:0;-webkit-transform:translateY(40px);-moz-transform:translateY(40px);-ms-transform:translateY(40px);-o-transform:translateY(40px);transform:translateY(40px)}30%, 70%{opacity:1;-webkit-transform:translateY(0px);-moz-transform:translateY(0px);-ms-transform:translateY(0px);-o-transform:translateY(0px);transform:translateY(0px)}100%{opacity:0;-webkit-transform:translateY(-40px);-moz-transform:translateY(-40px);-ms-transform:translateY(-40px);-o-transform:translateY(-40px);transform:translateY(-40px)}}@-moz-keyframes passing-through{0%{opacity:0;-webkit-transform:translateY(40px);-moz-transform:translateY(40px);-ms-transform:translateY(40px);-o-transform:translateY(40px);transform:translateY(40px)}30%, 70%{opacity:1;-webkit-transform:translateY(0px);-moz-transform:translateY(0px);-ms-transform:translateY(0px);-o-transform:translateY(0px);transform:translateY(0px)}100%{opacity:0;-webkit-transform:translateY(-40px);-moz-transform:translateY(-40px);-ms-transform:translateY(-40px);-o-transform:translateY(-40px);transform:translateY(-40px)}}@keyframes passing-through{0%{opacity:0;-webkit-transform:translateY(40px);-moz-transform:translateY(40px);-ms-transform:translateY(40px);-o-transform:translateY(40px);transform:translateY(40px)}30%, 70%{opacity:1;-webkit-transform:translateY(0px);-moz-transform:translateY(0px);-ms-transform:translateY(0px);-o-transform:translateY(0px);transform:translateY(0px)}100%{opacity:0;-webkit-transform:translateY(-40px);-moz-transform:translateY(-40px);-ms-transform:translateY(-40px);-o-transform:translateY(-40px);transform:translateY(-40px)}}@-webkit-keyframes slide-in{0%{opacity:0;-webkit-transform:translateY(40px);-moz-transform:translateY(40px);-ms-transform:translateY(40px);-o-transform:translateY(40px);transform:translateY(40px)}30%{opacity:1;-webkit-transform:translateY(0px);-moz-transform:translateY(0px);-ms-transform:translateY(0px);-o-transform:translateY(0px);transform:translateY(0px)}}@-moz-keyframes slide-in{0%{opacity:0;-webkit-transform:translateY(40px);-moz-transform:translateY(40px);-ms-transform:translateY(40px);-o-transform:translateY(40px);transform:translateY(40px)}30%{opacity:1;-webkit-transform:translateY(0px);-moz-transform:translateY(0px);-ms-transform:translateY(0px);-o-transform:translateY(0px);transform:translateY(0px)}}@keyframes slide-in{0%{opacity:0;-webkit-transform:translateY(40px);-moz-transform:translateY(40px);-ms-transform:translateY(40px);-o-transform:translateY(40px);transform:translateY(40px)}30%{opacity:1;-webkit-transform:translateY(0px);-moz-transform:translateY(0px);-ms-transform:translateY(0px);-o-transform:translateY(0px);transform:translateY(0px)}}@-webkit-keyframes pulse{0%{-webkit-transform:scale(1);-moz-transform:scale(1);-ms-transform:scale(1);-o-transform:scale(1);transform:scale(1)}10%{-webkit-transform:scale(1.1);-moz-transform:scale(1.1);-ms-transform:scale(1.1);-o-transform:scale(1.1);transform:scale(1.1)}20%{-webkit-transform:scale(1);-moz-transform:scale(1);-ms-transform:scale(1);-o-transform:scale(1);transform:scale(1)}}@-moz-keyframes pulse{0%{-webkit-transform:scale(1);-moz-transform:scale(1);-ms-transform:scale(1);-o-transform:scale(1);transform:scale(1)}10%{-webkit-transform:scale(1.1);-moz-transform:scale(1.1);-ms-transform:scale(1.1);-o-transform:scale(1.1);transform:scale(1.1)}20%{-webkit-transform:scale(1);-moz-transform:scale(1);-ms-transform:scale(1);-o-transform:scale(1);transform:scale(1)}}@keyframes pulse{0%{-webkit-transform:scale(1);-moz-transform:scale(1);-ms-transform:scale(1);-o-transform:scale(1);transform:scale(1)}10%{-webkit-transform:scale(1.1);-moz-transform:scale(1.1);-ms-transform:scale(1.1);-o-transform:scale(1.1);transform:scale(1.1)}20%{-webkit-transform:scale(1);-moz-transform:scale(1);-ms-transform:scale(1);-o-transform:scale(1);transform:scale(1)}}.dropzone,.dropzone *{box-sizing:border-box}.dropzone{min-height:150px;border:2px solid rgba(0,0,0,0.3);background:white;padding:20px 20px}.dropzone.dz-clickable{cursor:pointer}.dropzone.dz-clickable *{cursor:default}.dropzone.dz-clickable .dz-message,.dropzone.dz-clickable .dz-message *{cursor:pointer}.dropzone.dz-started .dz-message{display:none}.dropzone.dz-drag-hover{border-style:solid}.dropzone.dz-drag-hover .dz-message{opacity:0.5}.dropzone .dz-message{text-align:center;margin:2em 0}.dropzone .dz-preview{position:relative;display:inline-block;vertical-align:top;margin:16px;min-height:100px}.dropzone .dz-preview:hover{z-index:1000}.dropzone .dz-preview:hover .dz-details{opacity:1}.dropzone .dz-preview.dz-file-preview .dz-image{border-radius:20px;background:#999;background:linear-gradient(to bottom, #eee, #ddd)}.dropzone .dz-preview.dz-file-preview .dz-details{opacity:1}.dropzone .dz-preview.dz-image-preview{background:white}.dropzone .dz-preview.dz-image-preview .dz-details{-webkit-transition:opacity 0.2s linear;-moz-transition:opacity 0.2s linear;-ms-transition:opacity 0.2s linear;-o-transition:opacity 0.2s linear;transition:opacity 0.2s linear}.dropzone .dz-preview .dz-remove{font-size:14px;text-align:center;display:block;cursor:pointer;border:none}.dropzone .dz-preview .dz-remove:hover{text-decoration:underline}.dropzone .dz-preview:hover .dz-details{opacity:1}.dropzone .dz-preview .dz-details{z-index:20;position:absolute;top:0;left:0;opacity:0;font-size:13px;min-width:100%;max-width:100%;padding:2em 1em;text-align:center;color:rgba(0,0,0,0.9);line-height:150%}.dropzone .dz-preview .dz-details .dz-size{margin-bottom:1em;font-size:16px}.dropzone .dz-preview .dz-details .dz-filename{white-space:nowrap}.dropzone .dz-preview .dz-details .dz-filename:hover span{border:1px solid rgba(200,200,200,0.8);background-color:rgba(255,255,255,0.8)}.dropzone .dz-preview .dz-details .dz-filename:not(:hover){overflow:hidden;text-overflow:ellipsis}.dropzone .dz-preview .dz-details .dz-filename:not(:hover) span{border:1px solid transparent}.dropzone .dz-preview .dz-details .dz-filename span,.dropzone .dz-preview .dz-details .dz-size span{background-color:rgba(255,255,255,0.4);padding:0 0.4em;border-radius:3px}.dropzone .dz-preview:hover .dz-image img{-webkit-transform:scale(1.05, 1.05);-moz-transform:scale(1.05, 1.05);-ms-transform:scale(1.05, 1.05);-o-transform:scale(1.05, 1.05);transform:scale(1.05, 1.05);-webkit-filter:blur(8px);filter:blur(8px)}.dropzone .dz-preview .dz-image{border-radius:20px;overflow:hidden;width:120px;height:120px;position:relative;display:block;z-index:10}.dropzone .dz-preview .dz-image img{display:block}.dropzone .dz-preview.dz-success .dz-success-mark{-webkit-animation:passing-through 3s cubic-bezier(0.77, 0, 0.175, 1);-moz-animation:passing-through 3s cubic-bezier(0.77, 0, 0.175, 1);-ms-animation:passing-through 3s cubic-bezier(0.77, 0, 0.175, 1);-o-animation:passing-through 3s cubic-bezier(0.77, 0, 0.175, 1);animation:passing-through 3s cubic-bezier(0.77, 0, 0.175, 1)}.dropzone .dz-preview.dz-error .dz-error-mark{opacity:1;-webkit-animation:slide-in 3s cubic-bezier(0.77, 0, 0.175, 1);-moz-animation:slide-in 3s cubic-bezier(0.77, 0, 0.175, 1);-ms-animation:slide-in 3s cubic-bezier(0.77, 0, 0.175, 1);-o-animation:slide-in 3s cubic-bezier(0.77, 0, 0.175, 1);animation:slide-in 3s cubic-bezier(0.77, 0, 0.175, 1)}.dropzone .dz-preview .dz-success-mark,.dropzone .dz-preview .dz-error-mark{pointer-events:none;opacity:0;z-index:500;position:absolute;display:block;top:50%;left:50%;margin-left:-27px;margin-top:-27px}.dropzone .dz-preview .dz-success-mark svg,.dropzone .dz-preview .dz-error-mark svg{display:block;width:54px;height:54px}.dropzone .dz-preview.dz-processing .dz-progress{opacity:1;-webkit-transition:all 0.2s linear;-moz-transition:all 0.2s linear;-ms-transition:all 0.2s linear;-o-transition:all 0.2s linear;transition:all 0.2s linear}.dropzone .dz-preview.dz-complete .dz-progress{opacity:0;-webkit-transition:opacity 0.4s ease-in;-moz-transition:opacity 0.4s ease-in;-ms-transition:opacity 0.4s ease-in;-o-transition:opacity 0.4s ease-in;transition:opacity 0.4s ease-in}.dropzone .dz-preview:not(.dz-processing) .dz-progress{-webkit-animation:pulse 6s ease infinite;-moz-animation:pulse 6s ease infinite;-ms-animation:pulse 6s ease infinite;-o-animation:pulse 6s ease infinite;animation:pulse 6s ease infinite}.dropzone .dz-preview .dz-progress{opacity:1;z-index:1000;pointer-events:none;position:absolute;height:16px;left:50%;top:50%;margin-top:-8px;width:80px;margin-left:-40px;background:rgba(255,255,255,0.9);-webkit-transform:scale(1);border-radius:8px;overflow:hidden}.dropzone .dz-preview .dz-progress .dz-upload{background:#333;background:linear-gradient(to bottom, #666, #444);position:absolute;top:0;left:0;bottom:0;width:0;-webkit-transition:width 300ms ease-in-out;-moz-transition:width 300ms ease-in-out;-ms-transition:width 300ms ease-in-out;-o-transition:width 300ms ease-in-out;transition:width 300ms ease-in-out}.dropzone .dz-preview.dz-error .dz-error-message{display:block}.dropzone .dz-preview.dz-error:hover .dz-error-message{opacity:1;pointer-events:auto}.dropzone .dz-preview .dz-error-message{pointer-events:none;z-index:1000;position:absolute;display:block;display:none;opacity:0;-webkit-transition:opacity 0.3s ease;-moz-transition:opacity 0.3s ease;-ms-transition:opacity 0.3s ease;-o-transition:opacity 0.3s ease;transition:opacity 0.3s ease;border-radius:8px;font-size:13px;top:130px;left:-10px;width:140px;background:#be2626;background:linear-gradient(to bottom, #be2626, #a92222);padding:0.5em 1.2em;color:white}.dropzone .dz-preview .dz-error-message:after{content:'';position:absolute;top:-6px;left:64px;width:0;height:0;border-left:6px solid transparent;border-right:6px solid transparent;border-bottom:6px solid #be2626} 2 | --------------------------------------------------------------------------------