├── .gitignore ├── LICENSE.txt ├── README.md ├── conf ├── gunicorn.py ├── nginx.conf ├── restart.sh ├── start.sh ├── stop.sh └── update.sh ├── deploy.py ├── docs ├── deploy.md └── develop.md ├── requirements.txt └── shorturl ├── .gitignore ├── __init__.py ├── db.sql ├── index.py ├── libs ├── __init__.py ├── qrcode.py └── short_url.py ├── models.py ├── settings.py ├── static ├── dot.png ├── favicon.ico ├── main.css ├── main.js └── qrcode.js └── templates ├── base.html ├── index.html └── shorten.html /.gitignore: -------------------------------------------------------------------------------- 1 | *.swp 2 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2012-2013 mozillazg, https://github.com/mozillazg 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 10 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # shorturl 2 | 3 | A URL shortener site powered by Python and web.py. 4 | 5 | ## Features 6 | 7 | * Shorten URL. 8 | * QR Code. 9 | * Support all most URL scheme. 10 | 11 | ## Demo 12 | 13 | 14 | 15 | ## API 16 | 17 | Long -> Short 18 | 19 | URL: http://3sd.me/j/shorten 20 | Method: POST 21 | Parameters: url 22 | Return: JSON 23 | 24 | Examples: 25 | 26 | $ curl 3sd.me/j/shorten -d "url=baidu.com" 27 | {"shorten": "http://3sd.me/Jh8x3", "expand": "http://baidu.com"} 28 | 29 | Short -> Long 30 | 31 | URL: http://3sd.me/j/expand 32 | Method: POST 33 | Parameters: shorten 34 | Return: JSON 35 | 36 | Examples: 37 | 38 | $ curl 3sd.me/j/expand -d "shorten=Jh8x3" 39 | {"shorten": "http://3sd.me/Jh8x3", "expand": "http://baidu.com"} 40 | -------------------------------------------------------------------------------- /conf/gunicorn.py: -------------------------------------------------------------------------------- 1 | workers = 2 2 | bind = 'unix:/tmp/shorturl.sock' 3 | proc_name = 'shorturl' 4 | pidfile = '/tmp/shorturl.pid' 5 | user = 'www-data' 6 | group = 'www-data' 7 | errorlog = '/home/www/shorturl-conf/gunicorn_error.log' 8 | -------------------------------------------------------------------------------- /conf/nginx.conf: -------------------------------------------------------------------------------- 1 | upstream shorturl_server { 2 | # swap the commented lines below to switch between socket and port 3 | server unix:/tmp/shorturl.sock fail_timeout=0; 4 | } 5 | server { 6 | listen 80; 7 | server_name www.3sd.me; 8 | return 301 $scheme://3sd.me$request_uri; 9 | } 10 | 11 | server { 12 | listen 80; 13 | # client_max_body_size 512M; 14 | server_name 3sd.me; 15 | error_log /home/www/shorturl-conf/error.log; 16 | access_log /home/www/shorturl-conf/access.log; 17 | 18 | keepalive_timeout 5; 19 | 20 | location /orca.txt { 21 | alias /home/www/orca/shorturl.txt; 22 | } 23 | 24 | # path for static files 25 | location /static/ { 26 | alias /home/www/ShortURL/shorturl/static/; 27 | # autoindex on; 28 | expires max; 29 | } 30 | 31 | location / { 32 | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 33 | proxy_set_header Host $http_host; 34 | proxy_redirect off; 35 | 36 | if (!-f $request_filename) { 37 | proxy_pass http://shorturl_server; 38 | break; 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /conf/restart.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | ./stop.sh 4 | ./start.sh 5 | -------------------------------------------------------------------------------- /conf/start.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | gunicorn_conf="`pwd`/gunicorn.py" 4 | 5 | cd /home/www/ShortURL/ 6 | 7 | gunicorn deploy:app_wsgi -c $gunicorn_conf -D 8 | -------------------------------------------------------------------------------- /conf/stop.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | 4 | kill -9 $(cat '/tmp/shorturl.pid') 5 | -------------------------------------------------------------------------------- /conf/update.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | cd ../ShortURL/ 4 | 5 | git pull 6 | 7 | pip install -r requirements.txt 8 | 9 | cd ../shorturl-conf/ 10 | 11 | 12 | chown www-data:www-data /home/www -R 13 | 14 | 15 | 16 | ./stop.sh 17 | ./start.sh 18 | -------------------------------------------------------------------------------- /deploy.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | from shorturl.index import app 5 | 6 | app_wsgi = app.wsgifunc() 7 | -------------------------------------------------------------------------------- /docs/deploy.md: -------------------------------------------------------------------------------- 1 | # Deployment 2 | 3 | ## clone code 4 | 5 | cd /home/www/ 6 | git clone https://github.com/mozillazg/ShortURL.git 7 | 8 | ## install dependencies 9 | 10 | pip install gunicorn 11 | pip install -r requirements.txt 12 | 13 | ## database 14 | 15 | use shorturl/db.sql 16 | 17 | ## configure 18 | 19 | mkdir /home/www/shorturl-conf/ 20 | cp -r conf/* /home/www/shorturl-conf/ 21 | 22 | ### nginx 23 | 24 | add `include /home/www/*-conf/nginx.conf;` to `http {...}` (`/etc/nginx/nginx.conf`) 25 | 26 | change **server_name** (`/home/www/shorturl-conf/nginx.conf`) 27 | 28 | nginx -s reload 29 | 30 | ## run 31 | 32 | cd /home/www/shorturl-conf/ 33 | chmod +x *.sh 34 | * start 35 | `./start.sh` 36 | * stop 37 | `./stop.sh` 38 | * restart 39 | `./restart.sh` 40 | * update code 41 | `./update.sh` 42 | -------------------------------------------------------------------------------- /docs/develop.md: -------------------------------------------------------------------------------- 1 | # Development 2 | 3 | ## clone code: 4 | 5 | git clone https://github.com/mozillazg/ShortURL.git 6 | 7 | ## install dependencies 8 | 9 | pip install -r requirements.txt 10 | 11 | ## database 12 | 13 | use shorturl/db.sql 14 | 15 | ## run 16 | 17 | cd shorturl 18 | python index.py 19 | 20 | visit 21 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | web.py 2 | MySQL-python 3 | -------------------------------------------------------------------------------- /shorturl/.gitignore: -------------------------------------------------------------------------------- 1 | 2 | *.pyc 3 | *.swp -------------------------------------------------------------------------------- /shorturl/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozillazg/ShortURL/e0ee2ad772850bff474608c0e7a3aaf2b24ffcf6/shorturl/__init__.py -------------------------------------------------------------------------------- /shorturl/db.sql: -------------------------------------------------------------------------------- 1 | /* MySQL 数据库 */ 2 | /* 创建用户 'py',host 为 'localhost',密码为 'py_passwd',该用户对数据库 shorturl 下所有表拥有所有权限 */ 3 | grant all on shorturl.* to 'py'@'localhost' identified by "py_passwd"; 4 | 5 | create database shorturl default charset utf8; 6 | 7 | use shorturl; 8 | 9 | create table url ( 10 | id int(10) not null auto_increment, 11 | shorten char(8) not null, 12 | expand text not null, 13 | primary key(id) 14 | )engine=InnoDB default charset utf8; 15 | -------------------------------------------------------------------------------- /shorturl/index.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | import json 5 | import re 6 | import web 7 | from libs.qrcode import QRCode, ErrorCorrectLevel 8 | import settings 9 | import models 10 | 11 | debug = web.config.debug = settings.DEBUG 12 | render = web.template.render(settings.TEMPLATE_DIR, 13 | base=settings.BASE_TEMPLATE) 14 | app = web.application(settings.URLS, globals()) 15 | db = models.DB(settings.DATABASES) 16 | 17 | 18 | class Index(object): 19 | """首页""" 20 | def GET(self): 21 | return render.index() 22 | 23 | 24 | class Shorten(object): 25 | """网址缩短结果页""" 26 | def __init__(self): 27 | self.db = db 28 | 29 | def add_scheme(self, url): 30 | """给 URL 添加 scheme(qq.com -> http://qq.com)""" 31 | # 支持的 URL scheme 32 | # 常规 URL scheme 33 | scheme2 = re.compile(r'(?i)^[a-z][a-z0-9+.\-]*://') 34 | # 特殊 URL scheme 35 | scheme3 = ('git@', 'mailto:', 'javascript:', 'about:', 'opera:', 36 | 'afp:', 'aim:', 'apt:', 'attachment:', 'bitcoin:', 37 | 'callto:', 'cid:', 'data:', 'dav:', 'dns:', 'fax:', 'feed:', 38 | 'gg:', 'go:', 'gtalk:', 'h323:', 'iax:', 'im:', 'itms:', 39 | 'jar:', 'magnet:', 'maps:', 'message:', 'mid:', 'msnim:', 40 | 'mvn:', 'news:', 'palm:', 'paparazzi:', 'platform:', 41 | 'pres:', 'proxy:', 'psyc:', 'query:', 'session:', 'sip:', 42 | 'sips:', 'skype:', 'sms:', 'spotify:', 'steam:', 'tel:', 43 | 'things:', 'urn:', 'uuid:', 'view-source:', 'ws:', 'xfire:', 44 | 'xmpp:', 'ymsgr:', 'doi:', 45 | ) 46 | url_lower = url.lower() 47 | 48 | # 如果不包含规定的 URL scheme,则给网址添加 http:// 前缀 49 | scheme = scheme2.match(url_lower) 50 | if not scheme: 51 | for scheme in scheme3: 52 | url_splits = url_lower.split(scheme) 53 | if len(url_splits) > 1: 54 | break 55 | else: 56 | url = 'http://' + url 57 | return url 58 | 59 | def qrcode_table(self, data, type_number=4, error_correct_level='H'): 60 | """生成 QR Code html 表格,可以通过 css 控制黑白块的显示""" 61 | if error_correct_level == 'L': 62 | error_correct_level = ErrorCorrectLevel.L 63 | elif error_correct_level == 'M': 64 | error_correct_level = ErrorCorrectLevel.M 65 | elif error_correct_level == 'Q': 66 | error_correct_level = ErrorCorrectLevel.Q 67 | else: 68 | error_correct_level = ErrorCorrectLevel.H 69 | 70 | qr = QRCode() 71 | qr.setTypeNumber(type_number) 72 | qr.setErrorCorrectLevel(error_correct_level) 73 | qr.addData(data) 74 | qr.make() 75 | 76 | html = '' 77 | for r in range(qr.getModuleCount()): 78 | html += "" 79 | for c in range(qr.getModuleCount()): 80 | if qr.isDark(r, c): 81 | html += '' 85 | html += '
' 82 | else: 83 | html += '' 84 | html += '
' 86 | return html 87 | 88 | def POST(self, get_json=False): 89 | url = web.input(url='').url.strip() 90 | if not url: 91 | return web.badrequest() 92 | 93 | url = self.add_scheme(url) 94 | if debug: 95 | print repr(url) 96 | 97 | # 判断是否已存在相应的数据 98 | exists = self.db.exist_expand(url) 99 | if exists: 100 | shorten = exists.shorten 101 | else: 102 | shorten = self.db.add_url(url).shorten 103 | shorten = web.ctx.homedomain + '/' + shorten 104 | 105 | if get_json: 106 | # 返回 json 格式的数据 107 | web.header('Content-Type', 'application/json') 108 | return json.dumps({'shorten': shorten, 'expand': url}) 109 | else: 110 | shortens = web.storage({'url': shorten, 111 | 'qr_table': self.qrcode_table(shorten), 112 | }) 113 | return render.shorten(shortens) 114 | 115 | 116 | class Expand(object): 117 | """短网址跳转到相应的长网址""" 118 | def __init__(self): 119 | self.db = db 120 | 121 | def get_expand(self, shorten): 122 | result = self.db.get_expand(shorten) 123 | if result: 124 | return result.expand 125 | 126 | def GET(self, shorten): 127 | """解析短网址,并作 301 跳转""" 128 | if not shorten: 129 | return web.seeother('/') 130 | 131 | expand = self.get_expand(shorten) 132 | if debug: 133 | print repr(expand) 134 | if expand: 135 | return web.redirect(expand) # 301 跳转 136 | else: 137 | return web.index() 138 | 139 | def POST(self): 140 | """解析短网址,返回 json 数据""" 141 | shorten = web.input(shorten='').shorten.encode('utf8').strip() 142 | web.header('Content-Type', 'application/json') 143 | 144 | # 判断是否为有效短网址字符串 145 | if shorten and re.match('[a-zA-Z0-9]{5,}$', str(shorten)): 146 | expand = self.get_expand(shorten) 147 | 148 | if debug: 149 | print repr(expand) 150 | if expand: 151 | shorten = web.ctx.homedomain + '/' + shorten 152 | return json.dumps({'shorten': shorten, 'expand': expand}) 153 | else: 154 | return json.dumps({'shorten': '', 'expand': ''}) 155 | else: 156 | return json.dumps({'shorten': '', 'expand': ''}) 157 | 158 | 159 | if __name__ == '__main__': 160 | # 下面这条语句用于在服务器端通过 nginx + fastcgi 部署 web.py 应用 161 | # web.wsgi.runwsgi = lambda func, addr=None: web.wsgi.runfcgi(func, addr) 162 | app.run() 163 | -------------------------------------------------------------------------------- /shorturl/libs/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozillazg/ShortURL/e0ee2ad772850bff474608c0e7a3aaf2b24ffcf6/shorturl/libs/__init__.py -------------------------------------------------------------------------------- /shorturl/libs/qrcode.py: -------------------------------------------------------------------------------- 1 | # 2 | # QR Code Generator for Python 3 | # 4 | # Copyright (c) 2012 Kazuhiko Arase 5 | # 6 | # URL: http://www.d-project.com/ 7 | # 8 | # Licensed under the MIT license: 9 | # http://www.opensource.org/licenses/mit-license.php 10 | # 11 | # The word 'QR Code' is registered trademark of 12 | # DENSO WAVE INCORPORATED 13 | # http://www.denso-wave.com/qrcode/faqpatent-e.html 14 | # 15 | 16 | """QR Code Generator for Python 17 | 18 | from qrcode import QRCode, ErrorCorrectLevel 19 | 20 | # generate with explicit type number 21 | qr = QRCode() 22 | qr.setTypeNumber(4) 23 | qr.setErrorCorrectLevel(ErrorCorrectLevel.M) 24 | qr.addData('here comes qr!') 25 | qr.make() 26 | 27 | # generate with auto type number 28 | # qr = QRCode.getMinimumQRCode('here comes qr!', ErrorCorrectLevel.M) 29 | 30 | # create an image 31 | for r in range(qr.getModuleCount() ): 32 | for c in range(qr.getModuleCount() ): 33 | color = black if qr.isDark(r, c) else white 34 | # set pixel ... 35 | 36 | """ 37 | 38 | class QRCode: 39 | 40 | PAD0 = 0xEC 41 | PAD1 = 0x11 42 | 43 | def __init__(self): 44 | self.typeNumber = 1 45 | self.errorCorrectLevel = ErrorCorrectLevel.H 46 | self.qrDataList = [] 47 | self.modules = [] 48 | self.moduleCount = 0 49 | 50 | def getTypeNumber(self): 51 | return self.typeNumber 52 | def setTypeNumber(self, typeNumber): 53 | self.typeNumber = typeNumber 54 | 55 | def getErrorCorrectLevel(self): 56 | return self.errorCorrectLevel 57 | def setErrorCorrectLevel(self, errorCorrectLevel): 58 | self.errorCorrectLevel = errorCorrectLevel 59 | 60 | def clearData(self): 61 | self.qrDataList = [] 62 | 63 | def addData(self, data): 64 | self.qrDataList.append(QR8BitByte(data) ) 65 | 66 | def getDataCount(self): 67 | return len(self.qrDataList) 68 | 69 | def getData(self, index): 70 | return self.qrDataList[index] 71 | 72 | def isDark(self, row, col): 73 | return (self.modules[row][col] if self.modules[row][col] != None 74 | else False) 75 | 76 | def getModuleCount(self): 77 | return self.moduleCount 78 | 79 | def make(self): 80 | self._make(False, self._getBestMaskPattern() ) 81 | 82 | def _getBestMaskPattern(self): 83 | minLostPoint = 0 84 | pattern = 0 85 | for i in range(8): 86 | self._make(True, i) 87 | lostPoint = QRUtil.getLostPoint(self) 88 | if i == 0 or minLostPoint > lostPoint: 89 | minLostPoint = lostPoint 90 | pattern = i 91 | return pattern 92 | 93 | def _make(self, test, maskPattern): 94 | 95 | self.moduleCount = self.typeNumber * 4 + 17 96 | self.modules = [[None] * self.moduleCount 97 | for i in range(self.moduleCount)] 98 | 99 | self._setupPositionProbePattern(0, 0) 100 | self._setupPositionProbePattern(self.moduleCount - 7, 0) 101 | self._setupPositionProbePattern(0, self.moduleCount - 7) 102 | 103 | self._setupPositionAdjustPattern() 104 | self._setupTimingPattern() 105 | 106 | self._setupTypeInfo(test, maskPattern) 107 | 108 | if self.typeNumber >= 7: 109 | self._setupTypeNumber(test) 110 | 111 | data = QRCode._createData( 112 | self.typeNumber, 113 | self.errorCorrectLevel, 114 | self.qrDataList) 115 | 116 | self._mapData(data, maskPattern) 117 | 118 | def _mapData(self, data, maskPattern): 119 | 120 | rows = list(range(self.moduleCount) ) 121 | cols = [col - 1 if col <= 6 else col 122 | for col in range(self.moduleCount - 1, 0, -2)] 123 | maskFunc = QRUtil.getMaskFunction(maskPattern) 124 | 125 | byteIndex = 0 126 | bitIndex = 7 127 | 128 | for col in cols: 129 | rows.reverse() 130 | for row in rows: 131 | for c in range(2): 132 | if self.modules[row][col - c] == None: 133 | 134 | dark = False 135 | if byteIndex < len(data): 136 | dark = ( (data[byteIndex] >> bitIndex) & 1) == 1 137 | if maskFunc(row, col - c): 138 | dark = not dark 139 | self.modules[row][col - c] = dark 140 | 141 | bitIndex -= 1 142 | if bitIndex == -1: 143 | byteIndex += 1 144 | bitIndex = 7 145 | 146 | def _setupPositionAdjustPattern(self): 147 | pos = QRUtil.getPatternPosition(self.typeNumber) 148 | for row in pos: 149 | for col in pos: 150 | if self.modules[row][col] != None: 151 | continue 152 | for r in range(-2, 3): 153 | for c in range(-2, 3): 154 | self.modules[row + r][col + c] = ( 155 | r == -2 or r == 2 or c == -2 or c == 2 156 | or (r == 0 and c == 0) ) 157 | 158 | def _setupPositionProbePattern(self, row, col): 159 | for r in range(-1, 8): 160 | for c in range(-1, 8): 161 | if (row + r <= -1 or self.moduleCount <= row + r 162 | or col + c <= -1 or self.moduleCount <= col + c): 163 | continue 164 | self.modules[row + r][col + c] = ( 165 | (0 <= r and r <= 6 and (c == 0 or c == 6) ) 166 | or (0 <= c and c <= 6 and (r == 0 or r == 6) ) 167 | or (2 <= r and r <= 4 and 2 <= c and c <= 4) ) 168 | 169 | def _setupTimingPattern(self): 170 | for r in range(8, self.moduleCount - 8): 171 | if self.modules[r][6] != None: 172 | continue 173 | self.modules[r][6] = r % 2 == 0 174 | for c in range(8, self.moduleCount - 8): 175 | if self.modules[6][c] != None: 176 | continue 177 | self.modules[6][c] = c % 2 == 0 178 | 179 | def _setupTypeNumber(self, test): 180 | bits = QRUtil.getBCHTypeNumber(self.typeNumber) 181 | for i in range(18): 182 | self.modules[i // 3][i % 3 + self.moduleCount - 8 - 3] = ( 183 | not test and ( (bits >> i) & 1) == 1) 184 | for i in range(18): 185 | self.modules[i % 3 + self.moduleCount - 8 - 3][i // 3] = ( 186 | not test and ( (bits >> i) & 1) == 1) 187 | 188 | def _setupTypeInfo(self, test, maskPattern): 189 | 190 | data = (self.errorCorrectLevel << 3) | maskPattern 191 | bits = QRUtil.getBCHTypeInfo(data) 192 | 193 | # vertical 194 | for i in range(15): 195 | mod = not test and ( (bits >> i) & 1) == 1 196 | if i < 6: 197 | self.modules[i][8] = mod 198 | elif i < 8: 199 | self.modules[i + 1][8] = mod 200 | else: 201 | self.modules[self.moduleCount - 15 + i][8] = mod 202 | 203 | # horizontal 204 | for i in range(15): 205 | mod = not test and ( (bits >> i) & 1) == 1 206 | if i < 8: 207 | self.modules[8][self.moduleCount - i - 1] = mod 208 | elif i < 9: 209 | self.modules[8][15 - i - 1 + 1] = mod 210 | else: 211 | self.modules[8][15 - i - 1] = mod 212 | 213 | # fixed 214 | self.modules[self.moduleCount - 8][8] = not test 215 | 216 | @staticmethod 217 | def _createData(typeNumber, errorCorrectLevel, dataArray): 218 | 219 | rsBlocks = RSBlock.getRSBlocks(typeNumber, errorCorrectLevel) 220 | 221 | buffer = BitBuffer() 222 | 223 | for data in dataArray: 224 | buffer.put(data.getMode(), 4) 225 | buffer.put(data.getLength(), data.getLengthInBits(typeNumber) ) 226 | data.write(buffer) 227 | 228 | totalDataCount = sum(rsBlock.getDataCount() 229 | for rsBlock in rsBlocks) 230 | 231 | if buffer.getLengthInBits() > totalDataCount * 8: 232 | raise Exception('code length overflow. (%s>%s)' % 233 | (buffer.getLengthInBits(), totalDataCount * 8) ) 234 | 235 | # end code 236 | if buffer.getLengthInBits() + 4 <= totalDataCount * 8: 237 | buffer.put(0, 4) 238 | 239 | # padding 240 | while buffer.getLengthInBits() % 8 != 0: 241 | buffer.put(False) 242 | 243 | # padding 244 | while True: 245 | if buffer.getLengthInBits() >= totalDataCount * 8: 246 | break 247 | buffer.put(QRCode.PAD0, 8) 248 | if buffer.getLengthInBits() >= totalDataCount * 8: 249 | break 250 | buffer.put(QRCode.PAD1, 8) 251 | 252 | return QRCode._createBytes(buffer, rsBlocks) 253 | 254 | @staticmethod 255 | def _createBytes(buffer, rsBlocks): 256 | 257 | offset = 0 258 | 259 | maxDcCount = 0 260 | maxEcCount = 0 261 | 262 | dcdata = [None] * len(rsBlocks) 263 | ecdata = [None] * len(rsBlocks) 264 | 265 | for r in range(len(rsBlocks) ): 266 | 267 | dcCount = rsBlocks[r].getDataCount() 268 | ecCount = rsBlocks[r].getTotalCount() - dcCount 269 | 270 | maxDcCount = max(maxDcCount, dcCount) 271 | maxEcCount = max(maxEcCount, ecCount) 272 | 273 | dcdata[r] = [0] * dcCount 274 | for i in range(len(dcdata[r] ) ): 275 | dcdata[r][i] = 0xff & buffer.getBuffer()[i + offset] 276 | offset += dcCount 277 | 278 | rsPoly = QRUtil.getErrorCorrectPolynomial(ecCount) 279 | rawPoly = Polynomial(dcdata[r], rsPoly.getLength() - 1) 280 | 281 | modPoly = rawPoly.mod(rsPoly) 282 | ecdata[r] = [0] * (rsPoly.getLength() - 1) 283 | for i in range(len(ecdata[r]) ): 284 | modIndex = i + modPoly.getLength() - len(ecdata[r]) 285 | ecdata[r][i] = modPoly.get(modIndex) if modIndex >= 0 else 0 286 | 287 | totalCodeCount = sum(rsBlock.getTotalCount() 288 | for rsBlock in rsBlocks) 289 | 290 | data = [0] * totalCodeCount 291 | 292 | index = 0 293 | 294 | for i in range(maxDcCount): 295 | for r in range(len(rsBlocks) ): 296 | if i < len(dcdata[r] ): 297 | data[index] = dcdata[r][i] 298 | index += 1 299 | 300 | for i in range(maxEcCount): 301 | for r in range(len(rsBlocks) ): 302 | if i < len(ecdata[r] ): 303 | data[index] = ecdata[r][i] 304 | index += 1 305 | 306 | return data 307 | 308 | @staticmethod 309 | def getMinimumQRCode(data, errorCorrectLevel): 310 | mode = Mode.MODE_8BIT_BYTE # fixed to 8bit byte 311 | qr = QRCode() 312 | qr.setErrorCorrectLevel(errorCorrectLevel) 313 | qr.addData(data) 314 | length = qr.getData(0).getLength() 315 | for typeNumber in range(1, 11): 316 | if length <= QRUtil.getMaxLength( 317 | typeNumber, mode, errorCorrectLevel): 318 | qr.setTypeNumber(typeNumber) 319 | break 320 | qr.make() 321 | return qr 322 | 323 | class Mode: 324 | MODE_NUMBER = 1 << 0 325 | MODE_ALPHA_NUM = 1 << 1 326 | MODE_8BIT_BYTE = 1 << 2 327 | MODE_KANJI = 1 << 3 328 | 329 | class ErrorCorrectLevel: 330 | L = 1 # 7% 331 | M = 0 # 15% 332 | Q = 3 # 25% 333 | H = 2 # 30% 334 | 335 | class MaskPattern: 336 | PATTERN000 = 0 337 | PATTERN001 = 1 338 | PATTERN010 = 2 339 | PATTERN011 = 3 340 | PATTERN100 = 4 341 | PATTERN101 = 5 342 | PATTERN110 = 6 343 | PATTERN111 = 7 344 | 345 | class QRUtil: 346 | 347 | @staticmethod 348 | def getPatternPosition(typeNumber): 349 | return QRUtil.PATTERN_POSITION_TABLE[typeNumber - 1] 350 | 351 | PATTERN_POSITION_TABLE = [ 352 | [], 353 | [6, 18], 354 | [6, 22], 355 | [6, 26], 356 | [6, 30], 357 | [6, 34], 358 | [6, 22, 38], 359 | [6, 24, 42], 360 | [6, 26, 46], 361 | [6, 28, 50], 362 | [6, 30, 54], 363 | [6, 32, 58], 364 | [6, 34, 62], 365 | [6, 26, 46, 66], 366 | [6, 26, 48, 70], 367 | [6, 26, 50, 74], 368 | [6, 30, 54, 78], 369 | [6, 30, 56, 82], 370 | [6, 30, 58, 86], 371 | [6, 34, 62, 90], 372 | [6, 28, 50, 72, 94], 373 | [6, 26, 50, 74, 98], 374 | [6, 30, 54, 78, 102], 375 | [6, 28, 54, 80, 106], 376 | [6, 32, 58, 84, 110], 377 | [6, 30, 58, 86, 114], 378 | [6, 34, 62, 90, 118], 379 | [6, 26, 50, 74, 98, 122], 380 | [6, 30, 54, 78, 102, 126], 381 | [6, 26, 52, 78, 104, 130], 382 | [6, 30, 56, 82, 108, 134], 383 | [6, 34, 60, 86, 112, 138], 384 | [6, 30, 58, 86, 114, 142], 385 | [6, 34, 62, 90, 118, 146], 386 | [6, 30, 54, 78, 102, 126, 150], 387 | [6, 24, 50, 76, 102, 128, 154], 388 | [6, 28, 54, 80, 106, 132, 158], 389 | [6, 32, 58, 84, 110, 136, 162], 390 | [6, 26, 54, 82, 110, 138, 166], 391 | [6, 30, 58, 86, 114, 142, 170] 392 | ] 393 | 394 | MAX_LENGTH = [ 395 | [ [41, 25, 17, 10], [34, 20, 14, 8], [27, 16, 11, 7], [17, 10, 7, 4] ], 396 | [ [77, 47, 32, 20], [63, 38, 26, 16], [48, 29, 20, 12], [34, 20, 14, 8] ], 397 | [ [127, 77, 53, 32], [101, 61, 42, 26], [77, 47, 32, 20], [58, 35, 24, 15] ], 398 | [ [187, 114, 78, 48], [149, 90, 62, 38], [111, 67, 46, 28], [82, 50, 34, 21] ], 399 | [ [255, 154, 106, 65], [202, 122, 84, 52], [144, 87, 60, 37], [106, 64, 44, 27] ], 400 | [ [322, 195, 134, 82], [255, 154, 106, 65], [178, 108, 74, 45], [139, 84, 58, 36] ], 401 | [ [370, 224, 154, 95], [293, 178, 122, 75], [207, 125, 86, 53], [154, 93, 64, 39] ], 402 | [ [461, 279, 192, 118], [365, 221, 152, 93], [259, 157, 108, 66], [202, 122, 84, 52] ], 403 | [ [552, 335, 230, 141], [432, 262, 180, 111], [312, 189, 130, 80], [235, 143, 98, 60] ], 404 | [ [652, 395, 271, 167], [513, 311, 213, 131], [364, 221, 151, 93], [288, 174, 119, 74] ] 405 | ] 406 | 407 | @staticmethod 408 | def getMaxLength(typeNumber, mode, errorCorrectLevel): 409 | t = typeNumber - 1 410 | e = { 411 | ErrorCorrectLevel.L: 0, 412 | ErrorCorrectLevel.M: 1, 413 | ErrorCorrectLevel.Q: 2, 414 | ErrorCorrectLevel.H: 3 415 | }[errorCorrectLevel] 416 | m = { 417 | Mode.MODE_NUMBER: 0, 418 | Mode.MODE_ALPHA_NUM: 1, 419 | Mode.MODE_8BIT_BYTE: 2, 420 | Mode.MODE_KANJI: 3 421 | }[mode] 422 | return QRUtil.MAX_LENGTH[t][e][m] 423 | 424 | @staticmethod 425 | def getErrorCorrectPolynomial(errorCorrectLength): 426 | a = Polynomial([1]) 427 | for i in range(errorCorrectLength): 428 | a = a.multiply(Polynomial([1, QRMath.gexp(i)]) ) 429 | return a 430 | 431 | @staticmethod 432 | def getMaskFunction(maskPattern): 433 | return { 434 | MaskPattern.PATTERN000: 435 | lambda i, j: (i + j) % 2 == 0, 436 | MaskPattern.PATTERN001: 437 | lambda i, j: i % 2 == 0, 438 | MaskPattern.PATTERN010: 439 | lambda i, j: j % 3 == 0, 440 | MaskPattern.PATTERN011: 441 | lambda i, j: (i + j) % 3 == 0, 442 | MaskPattern.PATTERN100: 443 | lambda i, j: (i // 2 + j // 3) % 2 == 0, 444 | MaskPattern.PATTERN101: 445 | lambda i, j: (i * j) % 2 + (i * j) % 3 == 0, 446 | MaskPattern.PATTERN110: 447 | lambda i, j: ( (i * j) % 2 + (i * j) % 3) % 2 == 0, 448 | MaskPattern.PATTERN111: 449 | lambda i, j: ( (i * j) % 3 + (i + j) % 2) % 2 == 0 450 | }[maskPattern] 451 | 452 | @staticmethod 453 | def getLostPoint(qrcode): 454 | 455 | moduleCount = qrcode.getModuleCount() 456 | lostPoint = 0 457 | 458 | # LEVEL1 459 | for row in range(moduleCount): 460 | for col in range(moduleCount): 461 | sameCount = 0 462 | dark = qrcode.isDark(row, col) 463 | for r in range(-1, 2): 464 | if row + r < 0 or moduleCount <= row + r: 465 | continue 466 | for c in range(-1, 2): 467 | if col + c < 0 or moduleCount <= col + c: 468 | continue 469 | if r == 0 and c == 0: 470 | continue 471 | if dark == qrcode.isDark(row + r, col + c): 472 | sameCount += 1 473 | if sameCount > 5: 474 | lostPoint += (3 + sameCount - 5) 475 | 476 | # LEVEL2 477 | for row in range(moduleCount - 1): 478 | for col in range(moduleCount - 1): 479 | count = 0 480 | if qrcode.isDark(row, col): 481 | count += 1 482 | if qrcode.isDark(row + 1, col): 483 | count += 1 484 | if qrcode.isDark(row, col + 1): 485 | count += 1 486 | if qrcode.isDark(row + 1, col + 1): 487 | count += 1 488 | if count == 0 or count == 4: 489 | lostPoint += 3 490 | 491 | # LEVEL3 492 | for row in range(moduleCount): 493 | for col in range(moduleCount - 6): 494 | if (qrcode.isDark(row, col) 495 | and not qrcode.isDark(row, col + 1) 496 | and qrcode.isDark(row, col + 2) 497 | and qrcode.isDark(row, col + 3) 498 | and qrcode.isDark(row, col + 4) 499 | and not qrcode.isDark(row, col + 5) 500 | and qrcode.isDark(row, col + 6) ): 501 | lostPoint += 40 502 | 503 | for col in range(moduleCount): 504 | for row in range(moduleCount - 6): 505 | if (qrcode.isDark(row, col) 506 | and not qrcode.isDark(row + 1, col) 507 | and qrcode.isDark(row + 2, col) 508 | and qrcode.isDark(row + 3, col) 509 | and qrcode.isDark(row + 4, col) 510 | and not qrcode.isDark(row + 5, col) 511 | and qrcode.isDark(row + 6, col) ): 512 | lostPoint += 40 513 | 514 | # LEVEL4 515 | darkCount = 0 516 | for col in range(moduleCount): 517 | for row in range(moduleCount): 518 | if qrcode.isDark(row, col): 519 | darkCount += 1 520 | 521 | ratio = abs(100 * darkCount // moduleCount // moduleCount - 50) // 5 522 | lostPoint += ratio * 10 523 | 524 | return lostPoint 525 | 526 | G15 = ( (1 << 10) | (1 << 8) | (1 << 5) | (1 << 4) | 527 | (1 << 2) | (1 << 1) | (1 << 0) ) 528 | G18 = ( (1 << 12) | (1 << 11) | (1 << 10) | (1 << 9) | 529 | (1 << 8) | (1 << 5) | (1 << 2) | (1 << 0) ) 530 | G15_MASK = (1 << 14) | (1 << 12) | (1 << 10) | (1 << 4) | (1 << 1) 531 | 532 | @staticmethod 533 | def getBCHTypeInfo(data): 534 | d = data << 10 535 | while QRUtil.getBCHDigit(d) - QRUtil.getBCHDigit(QRUtil.G15) >= 0: 536 | d ^= (QRUtil.G15 << (QRUtil.getBCHDigit(d) - 537 | QRUtil.getBCHDigit(QRUtil.G15) ) ) 538 | return ( (data << 10) | d) ^ QRUtil.G15_MASK 539 | 540 | @staticmethod 541 | def getBCHTypeNumber(data): 542 | d = data << 12 543 | while QRUtil.getBCHDigit(d) - QRUtil.getBCHDigit(QRUtil.G18) >= 0: 544 | d ^= (QRUtil.G18 << (QRUtil.getBCHDigit(d) - 545 | QRUtil.getBCHDigit(QRUtil.G18) ) ) 546 | return (data << 12) | d 547 | 548 | @staticmethod 549 | def getBCHDigit(data): 550 | digit = 0 551 | while data != 0: 552 | digit += 1 553 | data >>= 1 554 | return digit 555 | 556 | @staticmethod 557 | def stringToBytes(s): 558 | return [ord(c) & 0xff for c in s] 559 | 560 | class QR8BitByte: 561 | 562 | def __init__(self, data): 563 | self.mode = Mode.MODE_8BIT_BYTE 564 | self.data = data 565 | 566 | def getMode(self): 567 | return self.mode 568 | 569 | def getData(self): 570 | return self.data 571 | 572 | ''' 573 | def write(self, buffer): raise Exception('not implemented.') 574 | def getLength(self): raise Exception('not implemented.') 575 | ''' 576 | 577 | def write(self, buffer): 578 | data = QRUtil.stringToBytes(self.getData() ) 579 | for d in data: 580 | buffer.put(d, 8) 581 | 582 | def getLength(self): 583 | return len(QRUtil.stringToBytes(self.getData() ) ) 584 | 585 | def getLengthInBits(self, type): 586 | if 1 <= type and type < 10: # 1 - 9 587 | return { 588 | Mode.MODE_NUMBER: 10, 589 | Mode.MODE_ALPHA_NUM: 9, 590 | Mode.MODE_8BIT_BYTE: 8, 591 | Mode.MODE_KANJI: 8 592 | }[self.mode] 593 | 594 | elif type < 27: # 10 - 26 595 | return { 596 | Mode.MODE_NUMBER: 12, 597 | Mode.MODE_ALPHA_NUM: 11, 598 | Mode.MODE_8BIT_BYTE: 16, 599 | Mode.MODE_KANJI: 10 600 | }[self.mode] 601 | 602 | elif type < 41: # 27 - 40 603 | return { 604 | Mode.MODE_NUMBER: 14, 605 | Mode.MODE_ALPHA_NUM: 13, 606 | Mode.MODE_8BIT_BYTE: 16, 607 | Mode.MODE_KANJI: 12 608 | }[self.mode] 609 | 610 | else: 611 | raise Exception('type:%s' % type) 612 | 613 | class QRMath: 614 | 615 | EXP_TABLE = None 616 | LOG_TABLE = None 617 | 618 | @staticmethod 619 | def _init(): 620 | 621 | QRMath.EXP_TABLE = [0] * 256 622 | for i in range(256): 623 | QRMath.EXP_TABLE[i] = (1 << i if i < 8 else 624 | QRMath.EXP_TABLE[i - 4] ^ QRMath.EXP_TABLE[i - 5] ^ 625 | QRMath.EXP_TABLE[i - 6] ^ QRMath.EXP_TABLE[i - 8]) 626 | 627 | QRMath.LOG_TABLE = [0] * 256 628 | for i in range(255): 629 | QRMath.LOG_TABLE[QRMath.EXP_TABLE[i] ] = i 630 | 631 | @staticmethod 632 | def glog(n): 633 | if n < 1: 634 | raise Exception('log(%s)' % n) 635 | return QRMath.LOG_TABLE[n] 636 | 637 | @staticmethod 638 | def gexp(n): 639 | while n < 0: 640 | n += 255 641 | while n >= 256: 642 | n -= 255 643 | return QRMath.EXP_TABLE[n] 644 | 645 | # initialize statics 646 | QRMath._init() 647 | 648 | class Polynomial: 649 | 650 | def __init__(self, num, shift=0): 651 | offset = 0 652 | length = len(num) 653 | while offset < length and num[offset] == 0: 654 | offset += 1 655 | self.num = num[offset:] + [0] * shift 656 | 657 | def get(self, index): 658 | return self.num[index] 659 | 660 | def getLength(self): 661 | return len(self.num) 662 | 663 | def __repr__(self): 664 | return ','.join( [str(self.get(i) ) 665 | for i in range(self.getLength() ) ] ) 666 | 667 | def toLogString(self): 668 | return ','.join( [str(QRMath.glog(self.get(i) ) ) 669 | for i in range(self.getLength() ) ] ) 670 | 671 | def multiply(self, e): 672 | num = [0] * (self.getLength() + e.getLength() - 1) 673 | for i in range(self.getLength() ): 674 | for j in range(e.getLength() ): 675 | num[i + j] ^= QRMath.gexp(QRMath.glog(self.get(i) ) + 676 | QRMath.glog(e.get(j) ) ) 677 | return Polynomial(num) 678 | 679 | def mod(self, e): 680 | if self.getLength() - e.getLength() < 0: 681 | return self 682 | ratio = QRMath.glog(self.get(0) ) - QRMath.glog(e.get(0) ) 683 | num = self.num[:] 684 | for i in range(e.getLength() ): 685 | num[i] ^= QRMath.gexp(QRMath.glog(e.get(i) ) + ratio) 686 | return Polynomial(num).mod(e) 687 | 688 | class RSBlock: 689 | 690 | RS_BLOCK_TABLE = [ 691 | 692 | # L 693 | # M 694 | # Q 695 | # H 696 | 697 | # 1 698 | [1, 26, 19], 699 | [1, 26, 16], 700 | [1, 26, 13], 701 | [1, 26, 9], 702 | 703 | # 2 704 | [1, 44, 34], 705 | [1, 44, 28], 706 | [1, 44, 22], 707 | [1, 44, 16], 708 | 709 | # 3 710 | [1, 70, 55], 711 | [1, 70, 44], 712 | [2, 35, 17], 713 | [2, 35, 13], 714 | 715 | # 4 716 | [1, 100, 80], 717 | [2, 50, 32], 718 | [2, 50, 24], 719 | [4, 25, 9], 720 | 721 | # 5 722 | [1, 134, 108], 723 | [2, 67, 43], 724 | [2, 33, 15, 2, 34, 16], 725 | [2, 33, 11, 2, 34, 12], 726 | 727 | # 6 728 | [2, 86, 68], 729 | [4, 43, 27], 730 | [4, 43, 19], 731 | [4, 43, 15], 732 | 733 | # 7 734 | [2, 98, 78], 735 | [4, 49, 31], 736 | [2, 32, 14, 4, 33, 15], 737 | [4, 39, 13, 1, 40, 14], 738 | 739 | # 8 740 | [2, 121, 97], 741 | [2, 60, 38, 2, 61, 39], 742 | [4, 40, 18, 2, 41, 19], 743 | [4, 40, 14, 2, 41, 15], 744 | 745 | # 9 746 | [2, 146, 116], 747 | [3, 58, 36, 2, 59, 37], 748 | [4, 36, 16, 4, 37, 17], 749 | [4, 36, 12, 4, 37, 13], 750 | 751 | # 10 752 | [2, 86, 68, 2, 87, 69], 753 | [4, 69, 43, 1, 70, 44], 754 | [6, 43, 19, 2, 44, 20], 755 | [6, 43, 15, 2, 44, 16] 756 | ] 757 | 758 | def __init__(self, totalCount, dataCount): 759 | self.totalCount = totalCount 760 | self.dataCount = dataCount 761 | 762 | def getDataCount(self): 763 | return self.dataCount 764 | 765 | def getTotalCount(self): 766 | return self.totalCount 767 | 768 | def __repr__(self): 769 | return ('(total=%s,data=%s)' % (self.totalCount, self.dataCount) ) 770 | 771 | @staticmethod 772 | def getRSBlocks(typeNumber, errorCorrectLevel): 773 | rsBlock = RSBlock.getRsBlockTable(typeNumber, errorCorrectLevel) 774 | length = len(rsBlock) // 3 775 | list = [] 776 | for i in range(length): 777 | count = rsBlock[i * 3 + 0] 778 | totalCount = rsBlock[i * 3 + 1] 779 | dataCount = rsBlock[i * 3 + 2] 780 | list += [RSBlock(totalCount, dataCount)] * count 781 | return list 782 | 783 | @staticmethod 784 | def getRsBlockTable(typeNumber, errorCorrectLevel): 785 | return { 786 | ErrorCorrectLevel.L: 787 | RSBlock.RS_BLOCK_TABLE[ (typeNumber - 1) * 4 + 0], 788 | ErrorCorrectLevel.M: 789 | RSBlock.RS_BLOCK_TABLE[ (typeNumber - 1) * 4 + 1], 790 | ErrorCorrectLevel.Q: 791 | RSBlock.RS_BLOCK_TABLE[ (typeNumber - 1) * 4 + 2], 792 | ErrorCorrectLevel.H: 793 | RSBlock.RS_BLOCK_TABLE[ (typeNumber - 1) * 4 + 3] 794 | }[errorCorrectLevel] 795 | 796 | class BitBuffer: 797 | 798 | def __init__(self, inclements=32): 799 | self.inclements = inclements 800 | self.buffer = [0] * self.inclements 801 | self.length = 0 802 | 803 | def getBuffer(self): 804 | return self.buffer 805 | 806 | def getLengthInBits(self): 807 | return self.length 808 | 809 | def get(self, index): 810 | return ( (self.buffer[index // 8] >> (7 - index % 8) ) & 1) == 1 811 | 812 | def putBit(self, bit): 813 | if self.length == len(self.buffer) * 8: 814 | self.buffer += [0] * self.inclements 815 | if bit: 816 | self.buffer[self.length // 8] |= (0x80 >> (self.length % 8) ) 817 | self.length += 1 818 | 819 | def put(self, num, length): 820 | for i in range(length): 821 | self.putBit( ( (num >> (length - i - 1) ) & 1) == 1) 822 | 823 | def __repr__(self): 824 | return ''.join('1' if self.get(i) else '0' 825 | for i in range(self.getLengthInBits() ) ) 826 | -------------------------------------------------------------------------------- /shorturl/libs/short_url.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Short URL Generator 3 | =================== 4 | 5 | Python implementation for generating Tiny URL- and bit.ly-like URLs. 6 | 7 | A bit-shuffling approach is used to avoid generating consecutive, predictable 8 | URLs. However, the algorithm is deterministic and will guarantee that no 9 | collisions will occur. 10 | 11 | The URL alphabet is fully customizable and may contain any number of 12 | characters. By default, digits and lower-case letters are used, with 13 | some removed to avoid confusion between characters like o, O and 0. The 14 | default alphabet is shuffled and has a prime number of characters to further 15 | improve the results of the algorithm. 16 | 17 | The block size specifies how many bits will be shuffled. The lower BLOCK_SIZE 18 | bits are reversed. Any bits higher than BLOCK_SIZE will remain as is. 19 | BLOCK_SIZE of 0 will leave all bits unaffected and the algorithm will simply 20 | be converting your integer to a different base. 21 | 22 | The intended use is that incrementing, consecutive integers will be used as 23 | keys to generate the short URLs. For example, when creating a new URL, the 24 | unique integer ID assigned by a database could be used to generate the URL 25 | by using this module. Or a simple counter may be used. As long as the same 26 | integer is not used twice, the same short URL will not be generated twice. 27 | 28 | The module supports both encoding and decoding of URLs. The min_length 29 | parameter allows you to pad the URL if you want it to be a specific length. 30 | 31 | Sample Usage: 32 | 33 | >>> import short_url 34 | >>> url = short_url.encode_url(12) 35 | >>> print url 36 | LhKA 37 | >>> key = short_url.decode_url(url) 38 | >>> print key 39 | 12 40 | 41 | Use the functions in the top-level of the module to use the default encoder. 42 | Otherwise, you may create your own UrlEncoder object and use its encode_url 43 | and decode_url methods. 44 | 45 | Author: Michael Fogleman 46 | License: MIT 47 | Link: http://code.activestate.com/recipes/576918/ 48 | ''' 49 | 50 | # DEFAULT_ALPHABET = 'mn6j2c4rv8bpygw95z7hsdaetxuk3fq' 51 | DEFAULT_ALPHABET = 'JedR8LNFY2j6MrhkBSADUyfP5amuH9xQCX4VqbgpsGtnW7vc3TwKE' 52 | DEFAULT_BLOCK_SIZE = 24 53 | MIN_LENGTH = 5 54 | 55 | class UrlEncoder(object): 56 | def __init__(self, alphabet=DEFAULT_ALPHABET, block_size=DEFAULT_BLOCK_SIZE): 57 | self.alphabet = alphabet 58 | self.block_size = block_size 59 | self.mask = (1 << block_size) - 1 60 | self.mapping = range(block_size) 61 | self.mapping.reverse() 62 | def encode_url(self, n, min_length=MIN_LENGTH): 63 | return self.enbase(self.encode(n), min_length) 64 | def decode_url(self, n): 65 | return self.decode(self.debase(n)) 66 | def encode(self, n): 67 | return (n & ~self.mask) | self._encode(n & self.mask) 68 | def _encode(self, n): 69 | result = 0 70 | for i, b in enumerate(self.mapping): 71 | if n & (1 << i): 72 | result |= (1 << b) 73 | return result 74 | def decode(self, n): 75 | return (n & ~self.mask) | self._decode(n & self.mask) 76 | def _decode(self, n): 77 | result = 0 78 | for i, b in enumerate(self.mapping): 79 | if n & (1 << b): 80 | result |= (1 << i) 81 | return result 82 | def enbase(self, x, min_length=MIN_LENGTH): 83 | result = self._enbase(x) 84 | padding = self.alphabet[0] * (min_length - len(result)) 85 | return '%s%s' % (padding, result) 86 | def _enbase(self, x): 87 | n = len(self.alphabet) 88 | if x < n: 89 | return self.alphabet[x] 90 | return self._enbase(x / n) + self.alphabet[x % n] 91 | def debase(self, x): 92 | n = len(self.alphabet) 93 | result = 0 94 | for i, c in enumerate(reversed(x)): 95 | result += self.alphabet.index(c) * (n ** i) 96 | return result 97 | 98 | DEFAULT_ENCODER = UrlEncoder() 99 | 100 | def encode(n): 101 | return DEFAULT_ENCODER.encode(n) 102 | 103 | def decode(n): 104 | return DEFAULT_ENCODER.decode(n) 105 | 106 | def enbase(n, min_length=MIN_LENGTH): 107 | return DEFAULT_ENCODER.enbase(n, min_length) 108 | 109 | def debase(n): 110 | return DEFAULT_ENCODER.debase(n) 111 | 112 | def encode_url(n, min_length=MIN_LENGTH): 113 | return DEFAULT_ENCODER.encode_url(n, min_length) 114 | 115 | def decode_url(n): 116 | return DEFAULT_ENCODER.decode_url(n) 117 | 118 | if __name__ == '__main__': 119 | for a in range(0, 200000, 37): 120 | b = encode(a) 121 | c = enbase(b) 122 | d = debase(c) 123 | e = decode(d) 124 | assert a == e 125 | assert b == d 126 | c = (' ' * (7 - len(c))) + c 127 | print '%6d %12d %s %12d %6d' % (a, b, c, d, e) 128 | -------------------------------------------------------------------------------- /shorturl/models.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | import web 5 | from libs import short_url 6 | 7 | 8 | class DB(object): 9 | def __init__(self, db_kwargs): 10 | self.db = web.database(**db_kwargs) 11 | 12 | def exist_expand(self, long_url): 13 | """检查数据库中是否已有相关记录,有则返回短 URL 14 | """ 15 | result = self.db.where(table='url', what='shorten', 16 | expand=long_url) 17 | if result: 18 | return result[0] 19 | 20 | def add_url(self, long_url): 21 | """添加 URL,返回短 URL 22 | """ 23 | id_ = self.db.insert(tablename='url', seqname='id', shorten='', 24 | expand=long_url) 25 | shorten = short_url.encode_url(id_) 26 | self.db.update(tables='url', shorten=shorten, 27 | where='id=$id_', vars=locals()) 28 | return web.storage(shorten=shorten) 29 | 30 | def get_expand(self, shorten): 31 | """根据短 URL 返回原始 URL 32 | """ 33 | result = self.db.where(table='url', what='expand', 34 | shorten=shorten) 35 | if result: 36 | return result[0] 37 | -------------------------------------------------------------------------------- /shorturl/settings.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | import os 5 | 6 | SITE_ROOT = os.path.dirname(os.path.abspath(__file__)) 7 | 8 | DEBUG = True # 调试模式 9 | TEMPLATE_DIR = os.path.join(SITE_ROOT, 'templates') # 模板目录 10 | BASE_TEMPLATE = 'base' # 基础模板 11 | 12 | # URL 映射 13 | URLS = ( 14 | '/', 'Index', 15 | '(/j)?/shorten', 'Shorten', 16 | '/([0-9a-zA-Z]{5,})', 'Expand', 17 | '/j/expand', 'Expand', 18 | '/.*', 'Index', 19 | ) 20 | 21 | # 数据库配置 22 | DATABASES = { 23 | 'dbn': 'mysql', 24 | 'db': 'shorturl', 25 | 'user': 'py', 26 | 'pw': 'py_passwd', 27 | 'host': 'localhost', 28 | 'port': 3306, 29 | } 30 | -------------------------------------------------------------------------------- /shorturl/static/dot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozillazg/ShortURL/e0ee2ad772850bff474608c0e7a3aaf2b24ffcf6/shorturl/static/dot.png -------------------------------------------------------------------------------- /shorturl/static/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozillazg/ShortURL/e0ee2ad772850bff474608c0e7a3aaf2b24ffcf6/shorturl/static/favicon.ico -------------------------------------------------------------------------------- /shorturl/static/main.css: -------------------------------------------------------------------------------- 1 | body { 2 | /* background-image: url("/static/dot.png"); */ 3 | background-attachment: fixed; 4 | background-color: transparent; 5 | margin: 0; 6 | padding: 0; 7 | } 8 | #main { 9 | width: 50%; 10 | margin-top: 2px; 11 | margin-left: auto; 12 | margin-right: auto; 13 | font-size: 110%; 14 | } 15 | #main input { 16 | margin: 1px auto; 17 | border-radius: 5px; 18 | border: 1px solid #CCC; 19 | font-size: 110%; 20 | color: #AAA; 21 | } 22 | #main input:focus { 23 | box-shadow: 0 0 3px #6699BB; 24 | color: #000; 25 | } 26 | #form { 27 | width: 100%; 28 | margin-top: 20%; 29 | padding: 5px 0; 30 | } 31 | #form input.warning { 32 | box-shadow: 0 0 5px red; 33 | } 34 | #form input#url { 35 | width: 100%; 36 | min-height: 28px; 37 | padding: 5px 0; 38 | } 39 | #form input#submit { 40 | margin-right: 0; 41 | font-size: 110%; 42 | float: right; 43 | padding: 5px; 44 | margin-bottom: 5px; 45 | } 46 | #form input.pointer { 47 | cursor: pointer; 48 | } 49 | #form input.progress { 50 | cursor: progress; 51 | } 52 | #form input#submit:hover { 53 | color: #000; 54 | } 55 | #msg.hidden { 56 | display: none; 57 | } 58 | #msg { 59 | margin-top: 5px; 60 | margin-bottom: 5px; 61 | color: red; 62 | font-size: 110%; 63 | } 64 | #result { 65 | clear: both; 66 | width: auto; 67 | margin: 10px auto; 68 | min-height: 250px; 69 | } 70 | #result #shorten, #result #qrcode{ 71 | display: block; 72 | width: 80%; 73 | clear: both; 74 | margin: 5px auto; 75 | } 76 | #result #shorten { 77 | width: 50%; 78 | } 79 | #result input { 80 | min-height: 25px; 81 | margin-bottom: 10px; 82 | padding: 5px; 83 | } 84 | #shorten-page { 85 | margin-top: 30%; 86 | } 87 | #result img:-moz-broken { 88 | -moz-force-broken-image-icon: 1; 89 | } 90 | #result div#qrcode { 91 | width: 200px; 92 | height: 200px; 93 | background-color: #fff; 94 | padding: 20px; 95 | border-radius: 5px; 96 | margin: auto; 97 | } 98 | table#qrcode-table { 99 | border: 0 none #0000ff; 100 | border-collapse: collapse; 101 | width: 200px; 102 | height: 200px; 103 | margin: auto; 104 | } 105 | table#qrcode-table tr td { 106 | border: 0 none #0000ff !important; 107 | border-collapse: collapse !important; 108 | padding: 0 !important; 109 | margin: 0 !important; 110 | width: 4px !important; 111 | } 112 | table#qrcode-table tr td.dark { 113 | background-color: #000 !important; 114 | } 115 | table#qrcode-table tr td.white { 116 | background-color: #fff !important; 117 | } 118 | 119 | #footer { 120 | clear: both; 121 | /*position: relative;*/ 122 | /*top: 95%;*/ 123 | /*right: 5%;*/ 124 | margin-top: 2%; 125 | margin-bottom: 2%; 126 | font-size: 100%; 127 | /*float: right;*/ 128 | text-align: center; 129 | } 130 | #footer #source-code { 131 | } 132 | a { 133 | color: #999; 134 | text-decoration: none; 135 | border-bottom: 1px dotted #AAA; 136 | } 137 | span a { 138 | padding: 2px; 139 | } 140 | a:hover { 141 | background-color: #EEE; 142 | color: #000; 143 | } 144 | 145 | @media screen and (max-device-width: 680px) { 146 | #main { 147 | width: auto !important; 148 | } 149 | #result { 150 | width: auto !important; 151 | } 152 | #result input#shorten { 153 | width: auto !important; 154 | } 155 | } 156 | -------------------------------------------------------------------------------- /shorturl/static/main.js: -------------------------------------------------------------------------------- 1 | // 用于更新 window.onload 事件 2 | function addLoadEvent(func) { 3 | var oldonload = window.onload; 4 | if (typeof window.onload != 'function') { 5 | window.onload = func; 6 | } else { 7 | window.onload = function() { 8 | oldonload(); 9 | func(); 10 | } 11 | } 12 | } 13 | 14 | 15 | // 去除字符串首尾空白符(" dog ".trim() === "dog") 16 | // http://stackoverflow.com/questions/1418050/string-strip-for-javascript 17 | if (typeof(String.prototype.trim) === "undefined") { 18 | String.prototype.trim = function() { 19 | return String(this).replace(/^\s+|\s+$/g, ''); 20 | }; 21 | } 22 | 23 | // 表单验证 24 | function validForm() { 25 | if (!document.getElementById) return false; 26 | var url = document.getElementById("url").value.trim(); 27 | var msg = document.getElementById("msg"); 28 | if (!url) { 29 | document.getElementById("url").className = "warning"; 30 | msg.innerHTML = "Can't be white-space chars!"; 31 | msg.className = "visible"; 32 | return false; 33 | } else { 34 | document.getElementById("url").className = ""; 35 | document.getElementById("url").value = addScheme(url); 36 | } 37 | //msg.className = "hidden"; 38 | msg.innerHTML = ""; 39 | return true; 40 | } 41 | 42 | 43 | // 生成 QR Code 44 | function create_qrcode(text) {//, typeNumber, errorCorrectLevel) { 45 | var qr = new QRCode(4, QRErrorCorrectLevel.H); 46 | var html; 47 | qr.addData(text); 48 | qr.make(); 49 | html = ''; 50 | for (var r = 0; r < qr.getModuleCount(); r++) { 51 | html +=""; 52 | for (var c = 0; c < qr.getModuleCount(); c++) { 53 | if (qr.isDark(r, c) ) { 54 | html += '"; 60 | } 61 | html += "
'; 55 | } else { 56 | html += ''; 57 | } 58 | } 59 | html += "
"; 62 | return html; 63 | } 64 | // 处理用户输入的 URL(添加 URL scheme) 65 | function addScheme(url) { 66 | var url = url; 67 | var hasScheme; 68 | // 支持的 URL 协议 69 | var scheme2 = /^[a-z][a-z0-9+.\-]*:\/\//i; 70 | var scheme3 = ['git@', 'mailto:', 'javascript:', 'about:', 'opera:', 71 | 'afp:', 'aim:', 'apt:', 'attachment:', 'bitcoin:', 72 | 'callto:', 'cid:', 'data:', 'dav:', 'dns:', 'fax:', 'feed:', 73 | 'gg:', 'go:', 'gtalk:', 'h323:', 'iax:', 'im:', 'itms:', 74 | 'jar:', 'magnet:', 'maps:', 'message:', 'mid:', 'msnim:', 75 | 'mvn:', 'news:', 'palm:', 'paparazzi:', 'platform:', 76 | 'pres:', 'proxy:', 'psyc:', 'query:', 'session:', 'sip:', 77 | 'sips:', 'skype:', 'sms:', 'spotify:', 'steam:', 'tel:', 78 | 'things:', 'urn:', 'uuid:', 'view-source:', 'ws:', 'xfire:', 79 | 'xmpp:', 'ymsgr:', 'doi:']; 80 | var url_lower = url.toLowerCase(); 81 | var scheme = scheme2.test(url_lower); 82 | if (!scheme) { 83 | for (var i=0; i 1) { 86 | hasScheme = true; 87 | break; 88 | } 89 | } 90 | if (!hasScheme) { 91 | url = 'http://' + url; 92 | } 93 | } 94 | return url 95 | } 96 | 97 | // 处理服务器返回的 json 数据 98 | function displayResult(request) { 99 | if (!document.getElementById) return false; 100 | if (!JSON) { 101 | JSON = {}; 102 | JSON.parse = function (json) { 103 | return eval("(" + json + ")"); 104 | }; 105 | } 106 | if ((request.readyState == 4) && request.status == 200) { 107 | var responseJson = JSON.parse(request.responseText); 108 | var shorten = responseJson.shorten; 109 | var result = document.getElementById("result"); 110 | var qrcode = document.getElementById("qrcode"); 111 | var qrcodeTable = create_qrcode(shorten); 112 | if (!qrcode) { 113 | qrcode = document.createElement('div'); 114 | qrcode.id = "qrcode"; 115 | qrcode.innerHTML = qrcodeTable; 116 | result.appendChild(qrcode); 117 | } 118 | else { 119 | qrcode.innerHTML = qrcodeTable; 120 | } 121 | document.getElementById("shorten").value = shorten; 122 | //qrcode.alt = "QR Code for URL " + shorten; 123 | document.getElementById("result").style.visibility = "visible"; 124 | document.getElementById("shorten").focus(); 125 | document.getElementById("shorten").select(); 126 | document.getElementById("submit").className = "pointer"; 127 | } 128 | } 129 | // 检测 XMLHttpRequest 对象是否可用 130 | // 当可用时返回一个 XMLHttpRequest 对象 131 | function getHTTPObject() { 132 | if (typeof XMLHttpRequest == "undefined") { 133 | XMLHttpRequest = function() { 134 | try { return new ActiveXObject("Msxml2.XMLHTTP.6.0"); 135 | } catch (e) {} 136 | try { return new ActiveXObject("Msxml2.XMLHTTP.3.0"); 137 | } catch (e) {} 138 | return false; 139 | } 140 | } 141 | return new XMLHttpRequest(); 142 | } 143 | // ajax 提交 URL 数据,返回短网址信息 144 | function postFormData() { 145 | var request = getHTTPObject(); 146 | if (request) { 147 | var url = document.getElementById("url").value.trim(); 148 | var data = "url=" + url; 149 | request.open("POST", "/j/shorten", true); 150 | request.onreadystatechange = function() {displayResult(request);}; 151 | request.setRequestHeader("Content-Type", "application/x-www-form-urlencoded"); 152 | request.send(data); 153 | document.getElementById("submit").className = "progress"; 154 | document.getElementById("result").style.visibility = "hidden"; 155 | } 156 | } 157 | 158 | 159 | // 拦截 submit 事件,提交前验证数据并改用 ajax 发送数据 160 | function submitEvent() { 161 | if (!document.getElementById) return false; 162 | if (!document.getElementById("submit")) return false; 163 | document.getElementById("submit").onclick = function () { 164 | if (validForm()) { 165 | postFormData(); 166 | } 167 | return false; 168 | } 169 | } 170 | 171 | addLoadEvent(submitEvent); 172 | 173 | 174 | // 当文本框获得焦点时,全选文本框内容 175 | function selectAll() { 176 | if (!document.getElementsByTagName) return false; 177 | if (!document.getElementsByTagName("input")) return false; 178 | var textInputs = document.getElementsByTagName("input"); 179 | for (var i=0; i= 7) { 98 | this.setupTypeNumber(test); 99 | } 100 | 101 | if (this.dataCache == null) { 102 | this.dataCache = QRCode.createData(this.typeNumber, this.errorCorrectLevel, this.dataList); 103 | } 104 | 105 | this.mapData(this.dataCache, maskPattern); 106 | }, 107 | 108 | setupPositionProbePattern : function(row, col) { 109 | 110 | for (var r = -1; r <= 7; r++) { 111 | 112 | if (row + r <= -1 || this.moduleCount <= row + r) continue; 113 | 114 | for (var c = -1; c <= 7; c++) { 115 | 116 | if (col + c <= -1 || this.moduleCount <= col + c) continue; 117 | 118 | if ( (0 <= r && r <= 6 && (c == 0 || c == 6) ) 119 | || (0 <= c && c <= 6 && (r == 0 || r == 6) ) 120 | || (2 <= r && r <= 4 && 2 <= c && c <= 4) ) { 121 | this.modules[row + r][col + c] = true; 122 | } else { 123 | this.modules[row + r][col + c] = false; 124 | } 125 | } 126 | } 127 | }, 128 | 129 | getBestMaskPattern : function() { 130 | 131 | var minLostPoint = 0; 132 | var pattern = 0; 133 | 134 | for (var i = 0; i < 8; i++) { 135 | 136 | this.makeImpl(true, i); 137 | 138 | var lostPoint = QRUtil.getLostPoint(this); 139 | 140 | if (i == 0 || minLostPoint > lostPoint) { 141 | minLostPoint = lostPoint; 142 | pattern = i; 143 | } 144 | } 145 | 146 | return pattern; 147 | }, 148 | 149 | createMovieClip : function(target_mc, instance_name, depth) { 150 | 151 | var qr_mc = target_mc.createEmptyMovieClip(instance_name, depth); 152 | var cs = 1; 153 | 154 | this.make(); 155 | 156 | for (var row = 0; row < this.modules.length; row++) { 157 | 158 | var y = row * cs; 159 | 160 | for (var col = 0; col < this.modules[row].length; col++) { 161 | 162 | var x = col * cs; 163 | var dark = this.modules[row][col]; 164 | 165 | if (dark) { 166 | qr_mc.beginFill(0, 100); 167 | qr_mc.moveTo(x, y); 168 | qr_mc.lineTo(x + cs, y); 169 | qr_mc.lineTo(x + cs, y + cs); 170 | qr_mc.lineTo(x, y + cs); 171 | qr_mc.endFill(); 172 | } 173 | } 174 | } 175 | 176 | return qr_mc; 177 | }, 178 | 179 | setupTimingPattern : function() { 180 | 181 | for (var r = 8; r < this.moduleCount - 8; r++) { 182 | if (this.modules[r][6] != null) { 183 | continue; 184 | } 185 | this.modules[r][6] = (r % 2 == 0); 186 | } 187 | 188 | for (var c = 8; c < this.moduleCount - 8; c++) { 189 | if (this.modules[6][c] != null) { 190 | continue; 191 | } 192 | this.modules[6][c] = (c % 2 == 0); 193 | } 194 | }, 195 | 196 | setupPositionAdjustPattern : function() { 197 | 198 | var pos = QRUtil.getPatternPosition(this.typeNumber); 199 | 200 | for (var i = 0; i < pos.length; i++) { 201 | 202 | for (var j = 0; j < pos.length; j++) { 203 | 204 | var row = pos[i]; 205 | var col = pos[j]; 206 | 207 | if (this.modules[row][col] != null) { 208 | continue; 209 | } 210 | 211 | for (var r = -2; r <= 2; r++) { 212 | 213 | for (var c = -2; c <= 2; c++) { 214 | 215 | if (r == -2 || r == 2 || c == -2 || c == 2 216 | || (r == 0 && c == 0) ) { 217 | this.modules[row + r][col + c] = true; 218 | } else { 219 | this.modules[row + r][col + c] = false; 220 | } 221 | } 222 | } 223 | } 224 | } 225 | }, 226 | 227 | setupTypeNumber : function(test) { 228 | 229 | var bits = QRUtil.getBCHTypeNumber(this.typeNumber); 230 | 231 | for (var i = 0; i < 18; i++) { 232 | var mod = (!test && ( (bits >> i) & 1) == 1); 233 | this.modules[Math.floor(i / 3)][i % 3 + this.moduleCount - 8 - 3] = mod; 234 | } 235 | 236 | for (var i = 0; i < 18; i++) { 237 | var mod = (!test && ( (bits >> i) & 1) == 1); 238 | this.modules[i % 3 + this.moduleCount - 8 - 3][Math.floor(i / 3)] = mod; 239 | } 240 | }, 241 | 242 | setupTypeInfo : function(test, maskPattern) { 243 | 244 | var data = (this.errorCorrectLevel << 3) | maskPattern; 245 | var bits = QRUtil.getBCHTypeInfo(data); 246 | 247 | // vertical 248 | for (var i = 0; i < 15; i++) { 249 | 250 | var mod = (!test && ( (bits >> i) & 1) == 1); 251 | 252 | if (i < 6) { 253 | this.modules[i][8] = mod; 254 | } else if (i < 8) { 255 | this.modules[i + 1][8] = mod; 256 | } else { 257 | this.modules[this.moduleCount - 15 + i][8] = mod; 258 | } 259 | } 260 | 261 | // horizontal 262 | for (var i = 0; i < 15; i++) { 263 | 264 | var mod = (!test && ( (bits >> i) & 1) == 1); 265 | 266 | if (i < 8) { 267 | this.modules[8][this.moduleCount - i - 1] = mod; 268 | } else if (i < 9) { 269 | this.modules[8][15 - i - 1 + 1] = mod; 270 | } else { 271 | this.modules[8][15 - i - 1] = mod; 272 | } 273 | } 274 | 275 | // fixed module 276 | this.modules[this.moduleCount - 8][8] = (!test); 277 | 278 | }, 279 | 280 | mapData : function(data, maskPattern) { 281 | 282 | var inc = -1; 283 | var row = this.moduleCount - 1; 284 | var bitIndex = 7; 285 | var byteIndex = 0; 286 | 287 | for (var col = this.moduleCount - 1; col > 0; col -= 2) { 288 | 289 | if (col == 6) col--; 290 | 291 | while (true) { 292 | 293 | for (var c = 0; c < 2; c++) { 294 | 295 | if (this.modules[row][col - c] == null) { 296 | 297 | var dark = false; 298 | 299 | if (byteIndex < data.length) { 300 | dark = ( ( (data[byteIndex] >>> bitIndex) & 1) == 1); 301 | } 302 | 303 | var mask = QRUtil.getMask(maskPattern, row, col - c); 304 | 305 | if (mask) { 306 | dark = !dark; 307 | } 308 | 309 | this.modules[row][col - c] = dark; 310 | bitIndex--; 311 | 312 | if (bitIndex == -1) { 313 | byteIndex++; 314 | bitIndex = 7; 315 | } 316 | } 317 | } 318 | 319 | row += inc; 320 | 321 | if (row < 0 || this.moduleCount <= row) { 322 | row -= inc; 323 | inc = -inc; 324 | break; 325 | } 326 | } 327 | } 328 | 329 | } 330 | 331 | }; 332 | 333 | QRCode.PAD0 = 0xEC; 334 | QRCode.PAD1 = 0x11; 335 | 336 | QRCode.createData = function(typeNumber, errorCorrectLevel, dataList) { 337 | 338 | var rsBlocks = QRRSBlock.getRSBlocks(typeNumber, errorCorrectLevel); 339 | 340 | var buffer = new QRBitBuffer(); 341 | 342 | for (var i = 0; i < dataList.length; i++) { 343 | var data = dataList[i]; 344 | buffer.put(data.mode, 4); 345 | buffer.put(data.getLength(), QRUtil.getLengthInBits(data.mode, typeNumber) ); 346 | data.write(buffer); 347 | } 348 | 349 | // calc num max data. 350 | var totalDataCount = 0; 351 | for (var i = 0; i < rsBlocks.length; i++) { 352 | totalDataCount += rsBlocks[i].dataCount; 353 | } 354 | 355 | if (buffer.getLengthInBits() > totalDataCount * 8) { 356 | throw new Error("code length overflow. (" 357 | + buffer.getLengthInBits() 358 | + ">" 359 | + totalDataCount * 8 360 | + ")"); 361 | } 362 | 363 | // end code 364 | if (buffer.getLengthInBits() + 4 <= totalDataCount * 8) { 365 | buffer.put(0, 4); 366 | } 367 | 368 | // padding 369 | while (buffer.getLengthInBits() % 8 != 0) { 370 | buffer.putBit(false); 371 | } 372 | 373 | // padding 374 | while (true) { 375 | 376 | if (buffer.getLengthInBits() >= totalDataCount * 8) { 377 | break; 378 | } 379 | buffer.put(QRCode.PAD0, 8); 380 | 381 | if (buffer.getLengthInBits() >= totalDataCount * 8) { 382 | break; 383 | } 384 | buffer.put(QRCode.PAD1, 8); 385 | } 386 | 387 | return QRCode.createBytes(buffer, rsBlocks); 388 | } 389 | 390 | QRCode.createBytes = function(buffer, rsBlocks) { 391 | 392 | var offset = 0; 393 | 394 | var maxDcCount = 0; 395 | var maxEcCount = 0; 396 | 397 | var dcdata = new Array(rsBlocks.length); 398 | var ecdata = new Array(rsBlocks.length); 399 | 400 | for (var r = 0; r < rsBlocks.length; r++) { 401 | 402 | var dcCount = rsBlocks[r].dataCount; 403 | var ecCount = rsBlocks[r].totalCount - dcCount; 404 | 405 | maxDcCount = Math.max(maxDcCount, dcCount); 406 | maxEcCount = Math.max(maxEcCount, ecCount); 407 | 408 | dcdata[r] = new Array(dcCount); 409 | 410 | for (var i = 0; i < dcdata[r].length; i++) { 411 | dcdata[r][i] = 0xff & buffer.buffer[i + offset]; 412 | } 413 | offset += dcCount; 414 | 415 | var rsPoly = QRUtil.getErrorCorrectPolynomial(ecCount); 416 | var rawPoly = new QRPolynomial(dcdata[r], rsPoly.getLength() - 1); 417 | 418 | var modPoly = rawPoly.mod(rsPoly); 419 | ecdata[r] = new Array(rsPoly.getLength() - 1); 420 | for (var i = 0; i < ecdata[r].length; i++) { 421 | var modIndex = i + modPoly.getLength() - ecdata[r].length; 422 | ecdata[r][i] = (modIndex >= 0)? modPoly.get(modIndex) : 0; 423 | } 424 | 425 | } 426 | 427 | var totalCodeCount = 0; 428 | for (var i = 0; i < rsBlocks.length; i++) { 429 | totalCodeCount += rsBlocks[i].totalCount; 430 | } 431 | 432 | var data = new Array(totalCodeCount); 433 | var index = 0; 434 | 435 | for (var i = 0; i < maxDcCount; i++) { 436 | for (var r = 0; r < rsBlocks.length; r++) { 437 | if (i < dcdata[r].length) { 438 | data[index++] = dcdata[r][i]; 439 | } 440 | } 441 | } 442 | 443 | for (var i = 0; i < maxEcCount; i++) { 444 | for (var r = 0; r < rsBlocks.length; r++) { 445 | if (i < ecdata[r].length) { 446 | data[index++] = ecdata[r][i]; 447 | } 448 | } 449 | } 450 | 451 | return data; 452 | 453 | } 454 | 455 | //--------------------------------------------------------------------- 456 | // QRMode 457 | //--------------------------------------------------------------------- 458 | 459 | var QRMode = { 460 | MODE_NUMBER : 1 << 0, 461 | MODE_ALPHA_NUM : 1 << 1, 462 | MODE_8BIT_BYTE : 1 << 2, 463 | MODE_KANJI : 1 << 3 464 | }; 465 | 466 | //--------------------------------------------------------------------- 467 | // QRErrorCorrectLevel 468 | //--------------------------------------------------------------------- 469 | 470 | var QRErrorCorrectLevel = { 471 | L : 1, 472 | M : 0, 473 | Q : 3, 474 | H : 2 475 | }; 476 | 477 | //--------------------------------------------------------------------- 478 | // QRMaskPattern 479 | //--------------------------------------------------------------------- 480 | 481 | var QRMaskPattern = { 482 | PATTERN000 : 0, 483 | PATTERN001 : 1, 484 | PATTERN010 : 2, 485 | PATTERN011 : 3, 486 | PATTERN100 : 4, 487 | PATTERN101 : 5, 488 | PATTERN110 : 6, 489 | PATTERN111 : 7 490 | }; 491 | 492 | //--------------------------------------------------------------------- 493 | // QRUtil 494 | //--------------------------------------------------------------------- 495 | 496 | var QRUtil = { 497 | 498 | PATTERN_POSITION_TABLE : [ 499 | [], 500 | [6, 18], 501 | [6, 22], 502 | [6, 26], 503 | [6, 30], 504 | [6, 34], 505 | [6, 22, 38], 506 | [6, 24, 42], 507 | [6, 26, 46], 508 | [6, 28, 50], 509 | [6, 30, 54], 510 | [6, 32, 58], 511 | [6, 34, 62], 512 | [6, 26, 46, 66], 513 | [6, 26, 48, 70], 514 | [6, 26, 50, 74], 515 | [6, 30, 54, 78], 516 | [6, 30, 56, 82], 517 | [6, 30, 58, 86], 518 | [6, 34, 62, 90], 519 | [6, 28, 50, 72, 94], 520 | [6, 26, 50, 74, 98], 521 | [6, 30, 54, 78, 102], 522 | [6, 28, 54, 80, 106], 523 | [6, 32, 58, 84, 110], 524 | [6, 30, 58, 86, 114], 525 | [6, 34, 62, 90, 118], 526 | [6, 26, 50, 74, 98, 122], 527 | [6, 30, 54, 78, 102, 126], 528 | [6, 26, 52, 78, 104, 130], 529 | [6, 30, 56, 82, 108, 134], 530 | [6, 34, 60, 86, 112, 138], 531 | [6, 30, 58, 86, 114, 142], 532 | [6, 34, 62, 90, 118, 146], 533 | [6, 30, 54, 78, 102, 126, 150], 534 | [6, 24, 50, 76, 102, 128, 154], 535 | [6, 28, 54, 80, 106, 132, 158], 536 | [6, 32, 58, 84, 110, 136, 162], 537 | [6, 26, 54, 82, 110, 138, 166], 538 | [6, 30, 58, 86, 114, 142, 170] 539 | ], 540 | 541 | G15 : (1 << 10) | (1 << 8) | (1 << 5) | (1 << 4) | (1 << 2) | (1 << 1) | (1 << 0), 542 | G18 : (1 << 12) | (1 << 11) | (1 << 10) | (1 << 9) | (1 << 8) | (1 << 5) | (1 << 2) | (1 << 0), 543 | G15_MASK : (1 << 14) | (1 << 12) | (1 << 10) | (1 << 4) | (1 << 1), 544 | 545 | getBCHTypeInfo : function(data) { 546 | var d = data << 10; 547 | while (QRUtil.getBCHDigit(d) - QRUtil.getBCHDigit(QRUtil.G15) >= 0) { 548 | d ^= (QRUtil.G15 << (QRUtil.getBCHDigit(d) - QRUtil.getBCHDigit(QRUtil.G15) ) ); 549 | } 550 | return ( (data << 10) | d) ^ QRUtil.G15_MASK; 551 | }, 552 | 553 | getBCHTypeNumber : function(data) { 554 | var d = data << 12; 555 | while (QRUtil.getBCHDigit(d) - QRUtil.getBCHDigit(QRUtil.G18) >= 0) { 556 | d ^= (QRUtil.G18 << (QRUtil.getBCHDigit(d) - QRUtil.getBCHDigit(QRUtil.G18) ) ); 557 | } 558 | return (data << 12) | d; 559 | }, 560 | 561 | getBCHDigit : function(data) { 562 | 563 | var digit = 0; 564 | 565 | while (data != 0) { 566 | digit++; 567 | data >>>= 1; 568 | } 569 | 570 | return digit; 571 | }, 572 | 573 | getPatternPosition : function(typeNumber) { 574 | return QRUtil.PATTERN_POSITION_TABLE[typeNumber - 1]; 575 | }, 576 | 577 | getMask : function(maskPattern, i, j) { 578 | 579 | switch (maskPattern) { 580 | 581 | case QRMaskPattern.PATTERN000 : return (i + j) % 2 == 0; 582 | case QRMaskPattern.PATTERN001 : return i % 2 == 0; 583 | case QRMaskPattern.PATTERN010 : return j % 3 == 0; 584 | case QRMaskPattern.PATTERN011 : return (i + j) % 3 == 0; 585 | case QRMaskPattern.PATTERN100 : return (Math.floor(i / 2) + Math.floor(j / 3) ) % 2 == 0; 586 | case QRMaskPattern.PATTERN101 : return (i * j) % 2 + (i * j) % 3 == 0; 587 | case QRMaskPattern.PATTERN110 : return ( (i * j) % 2 + (i * j) % 3) % 2 == 0; 588 | case QRMaskPattern.PATTERN111 : return ( (i * j) % 3 + (i + j) % 2) % 2 == 0; 589 | 590 | default : 591 | throw new Error("bad maskPattern:" + maskPattern); 592 | } 593 | }, 594 | 595 | getErrorCorrectPolynomial : function(errorCorrectLength) { 596 | 597 | var a = new QRPolynomial([1], 0); 598 | 599 | for (var i = 0; i < errorCorrectLength; i++) { 600 | a = a.multiply(new QRPolynomial([1, QRMath.gexp(i)], 0) ); 601 | } 602 | 603 | return a; 604 | }, 605 | 606 | getLengthInBits : function(mode, type) { 607 | 608 | if (1 <= type && type < 10) { 609 | 610 | // 1 - 9 611 | 612 | switch(mode) { 613 | case QRMode.MODE_NUMBER : return 10; 614 | case QRMode.MODE_ALPHA_NUM : return 9; 615 | case QRMode.MODE_8BIT_BYTE : return 8; 616 | case QRMode.MODE_KANJI : return 8; 617 | default : 618 | throw new Error("mode:" + mode); 619 | } 620 | 621 | } else if (type < 27) { 622 | 623 | // 10 - 26 624 | 625 | switch(mode) { 626 | case QRMode.MODE_NUMBER : return 12; 627 | case QRMode.MODE_ALPHA_NUM : return 11; 628 | case QRMode.MODE_8BIT_BYTE : return 16; 629 | case QRMode.MODE_KANJI : return 10; 630 | default : 631 | throw new Error("mode:" + mode); 632 | } 633 | 634 | } else if (type < 41) { 635 | 636 | // 27 - 40 637 | 638 | switch(mode) { 639 | case QRMode.MODE_NUMBER : return 14; 640 | case QRMode.MODE_ALPHA_NUM : return 13; 641 | case QRMode.MODE_8BIT_BYTE : return 16; 642 | case QRMode.MODE_KANJI : return 12; 643 | default : 644 | throw new Error("mode:" + mode); 645 | } 646 | 647 | } else { 648 | throw new Error("type:" + type); 649 | } 650 | }, 651 | 652 | getLostPoint : function(qrCode) { 653 | 654 | var moduleCount = qrCode.getModuleCount(); 655 | 656 | var lostPoint = 0; 657 | 658 | // LEVEL1 659 | 660 | for (var row = 0; row < moduleCount; row++) { 661 | 662 | for (var col = 0; col < moduleCount; col++) { 663 | 664 | var sameCount = 0; 665 | var dark = qrCode.isDark(row, col); 666 | 667 | for (var r = -1; r <= 1; r++) { 668 | 669 | if (row + r < 0 || moduleCount <= row + r) { 670 | continue; 671 | } 672 | 673 | for (var c = -1; c <= 1; c++) { 674 | 675 | if (col + c < 0 || moduleCount <= col + c) { 676 | continue; 677 | } 678 | 679 | if (r == 0 && c == 0) { 680 | continue; 681 | } 682 | 683 | if (dark == qrCode.isDark(row + r, col + c) ) { 684 | sameCount++; 685 | } 686 | } 687 | } 688 | 689 | if (sameCount > 5) { 690 | lostPoint += (3 + sameCount - 5); 691 | } 692 | } 693 | } 694 | 695 | // LEVEL2 696 | 697 | for (var row = 0; row < moduleCount - 1; row++) { 698 | for (var col = 0; col < moduleCount - 1; col++) { 699 | var count = 0; 700 | if (qrCode.isDark(row, col ) ) count++; 701 | if (qrCode.isDark(row + 1, col ) ) count++; 702 | if (qrCode.isDark(row, col + 1) ) count++; 703 | if (qrCode.isDark(row + 1, col + 1) ) count++; 704 | if (count == 0 || count == 4) { 705 | lostPoint += 3; 706 | } 707 | } 708 | } 709 | 710 | // LEVEL3 711 | 712 | for (var row = 0; row < moduleCount; row++) { 713 | for (var col = 0; col < moduleCount - 6; col++) { 714 | if (qrCode.isDark(row, col) 715 | && !qrCode.isDark(row, col + 1) 716 | && qrCode.isDark(row, col + 2) 717 | && qrCode.isDark(row, col + 3) 718 | && qrCode.isDark(row, col + 4) 719 | && !qrCode.isDark(row, col + 5) 720 | && qrCode.isDark(row, col + 6) ) { 721 | lostPoint += 40; 722 | } 723 | } 724 | } 725 | 726 | for (var col = 0; col < moduleCount; col++) { 727 | for (var row = 0; row < moduleCount - 6; row++) { 728 | if (qrCode.isDark(row, col) 729 | && !qrCode.isDark(row + 1, col) 730 | && qrCode.isDark(row + 2, col) 731 | && qrCode.isDark(row + 3, col) 732 | && qrCode.isDark(row + 4, col) 733 | && !qrCode.isDark(row + 5, col) 734 | && qrCode.isDark(row + 6, col) ) { 735 | lostPoint += 40; 736 | } 737 | } 738 | } 739 | 740 | // LEVEL4 741 | 742 | var darkCount = 0; 743 | 744 | for (var col = 0; col < moduleCount; col++) { 745 | for (var row = 0; row < moduleCount; row++) { 746 | if (qrCode.isDark(row, col) ) { 747 | darkCount++; 748 | } 749 | } 750 | } 751 | 752 | var ratio = Math.abs(100 * darkCount / moduleCount / moduleCount - 50) / 5; 753 | lostPoint += ratio * 10; 754 | 755 | return lostPoint; 756 | } 757 | 758 | }; 759 | 760 | 761 | //--------------------------------------------------------------------- 762 | // QRMath 763 | //--------------------------------------------------------------------- 764 | 765 | var QRMath = { 766 | 767 | glog : function(n) { 768 | 769 | if (n < 1) { 770 | throw new Error("glog(" + n + ")"); 771 | } 772 | 773 | return QRMath.LOG_TABLE[n]; 774 | }, 775 | 776 | gexp : function(n) { 777 | 778 | while (n < 0) { 779 | n += 255; 780 | } 781 | 782 | while (n >= 256) { 783 | n -= 255; 784 | } 785 | 786 | return QRMath.EXP_TABLE[n]; 787 | }, 788 | 789 | EXP_TABLE : new Array(256), 790 | 791 | LOG_TABLE : new Array(256) 792 | 793 | }; 794 | 795 | for (var i = 0; i < 8; i++) { 796 | QRMath.EXP_TABLE[i] = 1 << i; 797 | } 798 | for (var i = 8; i < 256; i++) { 799 | QRMath.EXP_TABLE[i] = QRMath.EXP_TABLE[i - 4] 800 | ^ QRMath.EXP_TABLE[i - 5] 801 | ^ QRMath.EXP_TABLE[i - 6] 802 | ^ QRMath.EXP_TABLE[i - 8]; 803 | } 804 | for (var i = 0; i < 255; i++) { 805 | QRMath.LOG_TABLE[QRMath.EXP_TABLE[i] ] = i; 806 | } 807 | 808 | //--------------------------------------------------------------------- 809 | // QRPolynomial 810 | //--------------------------------------------------------------------- 811 | 812 | function QRPolynomial(num, shift) { 813 | 814 | if (num.length == undefined) { 815 | throw new Error(num.length + "/" + shift); 816 | } 817 | 818 | var offset = 0; 819 | 820 | while (offset < num.length && num[offset] == 0) { 821 | offset++; 822 | } 823 | 824 | this.num = new Array(num.length - offset + shift); 825 | for (var i = 0; i < num.length - offset; i++) { 826 | this.num[i] = num[i + offset]; 827 | } 828 | } 829 | 830 | QRPolynomial.prototype = { 831 | 832 | get : function(index) { 833 | return this.num[index]; 834 | }, 835 | 836 | getLength : function() { 837 | return this.num.length; 838 | }, 839 | 840 | multiply : function(e) { 841 | 842 | var num = new Array(this.getLength() + e.getLength() - 1); 843 | 844 | for (var i = 0; i < this.getLength(); i++) { 845 | for (var j = 0; j < e.getLength(); j++) { 846 | num[i + j] ^= QRMath.gexp(QRMath.glog(this.get(i) ) + QRMath.glog(e.get(j) ) ); 847 | } 848 | } 849 | 850 | return new QRPolynomial(num, 0); 851 | }, 852 | 853 | mod : function(e) { 854 | 855 | if (this.getLength() - e.getLength() < 0) { 856 | return this; 857 | } 858 | 859 | var ratio = QRMath.glog(this.get(0) ) - QRMath.glog(e.get(0) ); 860 | 861 | var num = new Array(this.getLength() ); 862 | 863 | for (var i = 0; i < this.getLength(); i++) { 864 | num[i] = this.get(i); 865 | } 866 | 867 | for (var i = 0; i < e.getLength(); i++) { 868 | num[i] ^= QRMath.gexp(QRMath.glog(e.get(i) ) + ratio); 869 | } 870 | 871 | // recursive call 872 | return new QRPolynomial(num, 0).mod(e); 873 | } 874 | }; 875 | 876 | //--------------------------------------------------------------------- 877 | // QRRSBlock 878 | //--------------------------------------------------------------------- 879 | 880 | function QRRSBlock(totalCount, dataCount) { 881 | this.totalCount = totalCount; 882 | this.dataCount = dataCount; 883 | } 884 | 885 | QRRSBlock.RS_BLOCK_TABLE = [ 886 | 887 | // L 888 | // M 889 | // Q 890 | // H 891 | 892 | // 1 893 | [1, 26, 19], 894 | [1, 26, 16], 895 | [1, 26, 13], 896 | [1, 26, 9], 897 | 898 | // 2 899 | [1, 44, 34], 900 | [1, 44, 28], 901 | [1, 44, 22], 902 | [1, 44, 16], 903 | 904 | // 3 905 | [1, 70, 55], 906 | [1, 70, 44], 907 | [2, 35, 17], 908 | [2, 35, 13], 909 | 910 | // 4 911 | [1, 100, 80], 912 | [2, 50, 32], 913 | [2, 50, 24], 914 | [4, 25, 9], 915 | 916 | // 5 917 | [1, 134, 108], 918 | [2, 67, 43], 919 | [2, 33, 15, 2, 34, 16], 920 | [2, 33, 11, 2, 34, 12], 921 | 922 | // 6 923 | [2, 86, 68], 924 | [4, 43, 27], 925 | [4, 43, 19], 926 | [4, 43, 15], 927 | 928 | // 7 929 | [2, 98, 78], 930 | [4, 49, 31], 931 | [2, 32, 14, 4, 33, 15], 932 | [4, 39, 13, 1, 40, 14], 933 | 934 | // 8 935 | [2, 121, 97], 936 | [2, 60, 38, 2, 61, 39], 937 | [4, 40, 18, 2, 41, 19], 938 | [4, 40, 14, 2, 41, 15], 939 | 940 | // 9 941 | [2, 146, 116], 942 | [3, 58, 36, 2, 59, 37], 943 | [4, 36, 16, 4, 37, 17], 944 | [4, 36, 12, 4, 37, 13], 945 | 946 | // 10 947 | [2, 86, 68, 2, 87, 69], 948 | [4, 69, 43, 1, 70, 44], 949 | [6, 43, 19, 2, 44, 20], 950 | [6, 43, 15, 2, 44, 16] 951 | 952 | ]; 953 | 954 | QRRSBlock.getRSBlocks = function(typeNumber, errorCorrectLevel) { 955 | 956 | var rsBlock = QRRSBlock.getRsBlockTable(typeNumber, errorCorrectLevel); 957 | 958 | if (rsBlock == undefined) { 959 | throw new Error("bad rs block @ typeNumber:" + typeNumber + "/errorCorrectLevel:" + errorCorrectLevel); 960 | } 961 | 962 | var length = rsBlock.length / 3; 963 | 964 | var list = new Array(); 965 | 966 | for (var i = 0; i < length; i++) { 967 | 968 | var count = rsBlock[i * 3 + 0]; 969 | var totalCount = rsBlock[i * 3 + 1]; 970 | var dataCount = rsBlock[i * 3 + 2]; 971 | 972 | for (var j = 0; j < count; j++) { 973 | list.push(new QRRSBlock(totalCount, dataCount) ); 974 | } 975 | } 976 | 977 | return list; 978 | } 979 | 980 | QRRSBlock.getRsBlockTable = function(typeNumber, errorCorrectLevel) { 981 | 982 | switch(errorCorrectLevel) { 983 | case QRErrorCorrectLevel.L : 984 | return QRRSBlock.RS_BLOCK_TABLE[(typeNumber - 1) * 4 + 0]; 985 | case QRErrorCorrectLevel.M : 986 | return QRRSBlock.RS_BLOCK_TABLE[(typeNumber - 1) * 4 + 1]; 987 | case QRErrorCorrectLevel.Q : 988 | return QRRSBlock.RS_BLOCK_TABLE[(typeNumber - 1) * 4 + 2]; 989 | case QRErrorCorrectLevel.H : 990 | return QRRSBlock.RS_BLOCK_TABLE[(typeNumber - 1) * 4 + 3]; 991 | default : 992 | return undefined; 993 | } 994 | } 995 | 996 | //--------------------------------------------------------------------- 997 | // QRBitBuffer 998 | //--------------------------------------------------------------------- 999 | 1000 | function QRBitBuffer() { 1001 | this.buffer = new Array(); 1002 | this.length = 0; 1003 | } 1004 | 1005 | QRBitBuffer.prototype = { 1006 | 1007 | get : function(index) { 1008 | var bufIndex = Math.floor(index / 8); 1009 | return ( (this.buffer[bufIndex] >>> (7 - index % 8) ) & 1) == 1; 1010 | }, 1011 | 1012 | put : function(num, length) { 1013 | for (var i = 0; i < length; i++) { 1014 | this.putBit( ( (num >>> (length - i - 1) ) & 1) == 1); 1015 | } 1016 | }, 1017 | 1018 | getLengthInBits : function() { 1019 | return this.length; 1020 | }, 1021 | 1022 | putBit : function(bit) { 1023 | 1024 | var bufIndex = Math.floor(this.length / 8); 1025 | if (this.buffer.length <= bufIndex) { 1026 | this.buffer.push(0); 1027 | } 1028 | 1029 | if (bit) { 1030 | this.buffer[bufIndex] |= (0x80 >>> (this.length % 8) ); 1031 | } 1032 | 1033 | this.length++; 1034 | } 1035 | }; 1036 | -------------------------------------------------------------------------------- /shorturl/templates/base.html: -------------------------------------------------------------------------------- 1 | $def with(content) 2 | 3 | 4 | 5 | 6 | 7 | $content.title 8 |    9 | 10 | 11 | 12 | 13 | 14 | $:content 15 | 16 | 17 | 18 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /shorturl/templates/index.html: -------------------------------------------------------------------------------- 1 | $var title: URL Shortener 2 | 3 |
4 |
5 |
6 | 7 | 8 | 9 |
10 |
11 | 14 |
15 | -------------------------------------------------------------------------------- /shorturl/templates/shorten.html: -------------------------------------------------------------------------------- 1 | $def with (shorten) 2 | $var title: Shorten Result 3 | 4 |
5 | 6 |
7 |
8 |
9 |
$:shorten.qr_table
10 |
11 |
12 | 13 |
14 | --------------------------------------------------------------------------------