]> git.wh0rd.org - tt-rss.git/blob - lib/phpqrcode/qrinput.php
implement one time passwords using TOTP
[tt-rss.git] / lib / phpqrcode / qrinput.php
1 <?php
2 /*
3 * PHP QR Code encoder
4 *
5 * Input encoding class
6 *
7 * Based on libqrencode C library distributed under LGPL 2.1
8 * Copyright (C) 2006, 2007, 2008, 2009 Kentaro Fukuchi <fukuchi@megaui.net>
9 *
10 * PHP QR Code is distributed under LGPL 3
11 * Copyright (C) 2010 Dominik Dzienia <deltalab at poczta dot fm>
12 *
13 * This library is free software; you can redistribute it and/or
14 * modify it under the terms of the GNU Lesser General Public
15 * License as published by the Free Software Foundation; either
16 * version 3 of the License, or any later version.
17 *
18 * This library is distributed in the hope that it will be useful,
19 * but WITHOUT ANY WARRANTY; without even the implied warranty of
20 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
21 * Lesser General Public License for more details.
22 *
23 * You should have received a copy of the GNU Lesser General Public
24 * License along with this library; if not, write to the Free Software
25 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
26 */
27
28 define('STRUCTURE_HEADER_BITS', 20);
29 define('MAX_STRUCTURED_SYMBOLS', 16);
30
31 class QRinputItem {
32
33 public $mode;
34 public $size;
35 public $data;
36 public $bstream;
37
38 public function __construct($mode, $size, $data, $bstream = null)
39 {
40 $setData = array_slice($data, 0, $size);
41
42 if (count($setData) < $size) {
43 $setData = array_merge($setData, array_fill(0,$size-count($setData),0));
44 }
45
46 if(!QRinput::check($mode, $size, $setData)) {
47 throw new Exception('Error m:'.$mode.',s:'.$size.',d:'.join(',',$setData));
48 return null;
49 }
50
51 $this->mode = $mode;
52 $this->size = $size;
53 $this->data = $setData;
54 $this->bstream = $bstream;
55 }
56
57 //----------------------------------------------------------------------
58 public function encodeModeNum($version)
59 {
60 try {
61
62 $words = (int)($this->size / 3);
63 $bs = new QRbitstream();
64
65 $val = 0x1;
66 $bs->appendNum(4, $val);
67 $bs->appendNum(QRspec::lengthIndicator(QR_MODE_NUM, $version), $this->size);
68
69 for($i=0; $i<$words; $i++) {
70 $val = (ord($this->data[$i*3 ]) - ord('0')) * 100;
71 $val += (ord($this->data[$i*3+1]) - ord('0')) * 10;
72 $val += (ord($this->data[$i*3+2]) - ord('0'));
73 $bs->appendNum(10, $val);
74 }
75
76 if($this->size - $words * 3 == 1) {
77 $val = ord($this->data[$words*3]) - ord('0');
78 $bs->appendNum(4, $val);
79 } else if($this->size - $words * 3 == 2) {
80 $val = (ord($this->data[$words*3 ]) - ord('0')) * 10;
81 $val += (ord($this->data[$words*3+1]) - ord('0'));
82 $bs->appendNum(7, $val);
83 }
84
85 $this->bstream = $bs;
86 return 0;
87
88 } catch (Exception $e) {
89 return -1;
90 }
91 }
92
93 //----------------------------------------------------------------------
94 public function encodeModeAn($version)
95 {
96 try {
97 $words = (int)($this->size / 2);
98 $bs = new QRbitstream();
99
100 $bs->appendNum(4, 0x02);
101 $bs->appendNum(QRspec::lengthIndicator(QR_MODE_AN, $version), $this->size);
102
103 for($i=0; $i<$words; $i++) {
104 $val = (int)QRinput::lookAnTable(ord($this->data[$i*2 ])) * 45;
105 $val += (int)QRinput::lookAnTable(ord($this->data[$i*2+1]));
106
107 $bs->appendNum(11, $val);
108 }
109
110 if($this->size & 1) {
111 $val = QRinput::lookAnTable(ord($this->data[$words * 2]));
112 $bs->appendNum(6, $val);
113 }
114
115 $this->bstream = $bs;
116 return 0;
117
118 } catch (Exception $e) {
119 return -1;
120 }
121 }
122
123 //----------------------------------------------------------------------
124 public function encodeMode8($version)
125 {
126 try {
127 $bs = new QRbitstream();
128
129 $bs->appendNum(4, 0x4);
130 $bs->appendNum(QRspec::lengthIndicator(QR_MODE_8, $version), $this->size);
131
132 for($i=0; $i<$this->size; $i++) {
133 $bs->appendNum(8, ord($this->data[$i]));
134 }
135
136 $this->bstream = $bs;
137 return 0;
138
139 } catch (Exception $e) {
140 return -1;
141 }
142 }
143
144 //----------------------------------------------------------------------
145 public function encodeModeKanji($version)
146 {
147 try {
148
149 $bs = new QRbitrtream();
150
151 $bs->appendNum(4, 0x8);
152 $bs->appendNum(QRspec::lengthIndicator(QR_MODE_KANJI, $version), (int)($this->size / 2));
153
154 for($i=0; $i<$this->size; $i+=2) {
155 $val = (ord($this->data[$i]) << 8) | ord($this->data[$i+1]);
156 if($val <= 0x9ffc) {
157 $val -= 0x8140;
158 } else {
159 $val -= 0xc140;
160 }
161
162 $h = ($val >> 8) * 0xc0;
163 $val = ($val & 0xff) + $h;
164
165 $bs->appendNum(13, $val);
166 }
167
168 $this->bstream = $bs;
169 return 0;
170
171 } catch (Exception $e) {
172 return -1;
173 }
174 }
175
176 //----------------------------------------------------------------------
177 public function encodeModeStructure()
178 {
179 try {
180 $bs = new QRbitstream();
181
182 $bs->appendNum(4, 0x03);
183 $bs->appendNum(4, ord($this->data[1]) - 1);
184 $bs->appendNum(4, ord($this->data[0]) - 1);
185 $bs->appendNum(8, ord($this->data[2]));
186
187 $this->bstream = $bs;
188 return 0;
189
190 } catch (Exception $e) {
191 return -1;
192 }
193 }
194
195 //----------------------------------------------------------------------
196 public function estimateBitStreamSizeOfEntry($version)
197 {
198 $bits = 0;
199
200 if($version == 0)
201 $version = 1;
202
203 switch($this->mode) {
204 case QR_MODE_NUM: $bits = QRinput::estimateBitsModeNum($this->size); break;
205 case QR_MODE_AN: $bits = QRinput::estimateBitsModeAn($this->size); break;
206 case QR_MODE_8: $bits = QRinput::estimateBitsMode8($this->size); break;
207 case QR_MODE_KANJI: $bits = QRinput::estimateBitsModeKanji($this->size);break;
208 case QR_MODE_STRUCTURE: return STRUCTURE_HEADER_BITS;
209 default:
210 return 0;
211 }
212
213 $l = QRspec::lengthIndicator($this->mode, $version);
214 $m = 1 << $l;
215 $num = (int)(($this->size + $m - 1) / $m);
216
217 $bits += $num * (4 + $l);
218
219 return $bits;
220 }
221
222 //----------------------------------------------------------------------
223 public function encodeBitStream($version)
224 {
225 try {
226
227 unset($this->bstream);
228 $words = QRspec::maximumWords($this->mode, $version);
229
230 if($this->size > $words) {
231
232 $st1 = new QRinputItem($this->mode, $words, $this->data);
233 $st2 = new QRinputItem($this->mode, $this->size - $words, array_slice($this->data, $words));
234
235 $st1->encodeBitStream($version);
236 $st2->encodeBitStream($version);
237
238 $this->bstream = new QRbitstream();
239 $this->bstream->append($st1->bstream);
240 $this->bstream->append($st2->bstream);
241
242 unset($st1);
243 unset($st2);
244
245 } else {
246
247 $ret = 0;
248
249 switch($this->mode) {
250 case QR_MODE_NUM: $ret = $this->encodeModeNum($version); break;
251 case QR_MODE_AN: $ret = $this->encodeModeAn($version); break;
252 case QR_MODE_8: $ret = $this->encodeMode8($version); break;
253 case QR_MODE_KANJI: $ret = $this->encodeModeKanji($version);break;
254 case QR_MODE_STRUCTURE: $ret = $this->encodeModeStructure(); break;
255
256 default:
257 break;
258 }
259
260 if($ret < 0)
261 return -1;
262 }
263
264 return $this->bstream->size();
265
266 } catch (Exception $e) {
267 return -1;
268 }
269 }
270 };
271
272 //##########################################################################
273
274 class QRinput {
275
276 public $items;
277
278 private $version;
279 private $level;
280
281 //----------------------------------------------------------------------
282 public function __construct($version = 0, $level = QR_ECLEVEL_L)
283 {
284 if ($version < 0 || $version > QRSPEC_VERSION_MAX || $level > QR_ECLEVEL_H) {
285 throw new Exception('Invalid version no');
286 return NULL;
287 }
288
289 $this->version = $version;
290 $this->level = $level;
291 }
292
293 //----------------------------------------------------------------------
294 public function getVersion()
295 {
296 return $this->version;
297 }
298
299 //----------------------------------------------------------------------
300 public function setVersion($version)
301 {
302 if($version < 0 || $version > QRSPEC_VERSION_MAX) {
303 throw new Exception('Invalid version no');
304 return -1;
305 }
306
307 $this->version = $version;
308
309 return 0;
310 }
311
312 //----------------------------------------------------------------------
313 public function getErrorCorrectionLevel()
314 {
315 return $this->level;
316 }
317
318 //----------------------------------------------------------------------
319 public function setErrorCorrectionLevel($level)
320 {
321 if($level > QR_ECLEVEL_H) {
322 throw new Exception('Invalid ECLEVEL');
323 return -1;
324 }
325
326 $this->level = $level;
327
328 return 0;
329 }
330
331 //----------------------------------------------------------------------
332 public function appendEntry(QRinputItem $entry)
333 {
334 $this->items[] = $entry;
335 }
336
337 //----------------------------------------------------------------------
338 public function append($mode, $size, $data)
339 {
340 try {
341 $entry = new QRinputItem($mode, $size, $data);
342 $this->items[] = $entry;
343 return 0;
344 } catch (Exception $e) {
345 return -1;
346 }
347 }
348
349 //----------------------------------------------------------------------
350
351 public function insertStructuredAppendHeader($size, $index, $parity)
352 {
353 if( $size > MAX_STRUCTURED_SYMBOLS ) {
354 throw new Exception('insertStructuredAppendHeader wrong size');
355 }
356
357 if( $index <= 0 || $index > MAX_STRUCTURED_SYMBOLS ) {
358 throw new Exception('insertStructuredAppendHeader wrong index');
359 }
360
361 $buf = array($size, $index, $parity);
362
363 try {
364 $entry = new QRinputItem(QR_MODE_STRUCTURE, 3, buf);
365 array_unshift($this->items, $entry);
366 return 0;
367 } catch (Exception $e) {
368 return -1;
369 }
370 }
371
372 //----------------------------------------------------------------------
373 public function calcParity()
374 {
375 $parity = 0;
376
377 foreach($this->items as $item) {
378 if($item->mode != QR_MODE_STRUCTURE) {
379 for($i=$item->size-1; $i>=0; $i--) {
380 $parity ^= $item->data[$i];
381 }
382 }
383 }
384
385 return $parity;
386 }
387
388 //----------------------------------------------------------------------
389 public static function checkModeNum($size, $data)
390 {
391 for($i=0; $i<$size; $i++) {
392 if((ord($data[$i]) < ord('0')) || (ord($data[$i]) > ord('9'))){
393 return false;
394 }
395 }
396
397 return true;
398 }
399
400 //----------------------------------------------------------------------
401 public static function estimateBitsModeNum($size)
402 {
403 $w = (int)$size / 3;
404 $bits = $w * 10;
405
406 switch($size - $w * 3) {
407 case 1:
408 $bits += 4;
409 break;
410 case 2:
411 $bits += 7;
412 break;
413 default:
414 break;
415 }
416
417 return $bits;
418 }
419
420 //----------------------------------------------------------------------
421 public static $anTable = array(
422 -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
423 -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
424 36, -1, -1, -1, 37, 38, -1, -1, -1, -1, 39, 40, -1, 41, 42, 43,
425 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 44, -1, -1, -1, -1, -1,
426 -1, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24,
427 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, -1, -1, -1, -1, -1,
428 -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
429 -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1
430 );
431
432 //----------------------------------------------------------------------
433 public static function lookAnTable($c)
434 {
435 return (($c > 127)?-1:self::$anTable[$c]);
436 }
437
438 //----------------------------------------------------------------------
439 public static function checkModeAn($size, $data)
440 {
441 for($i=0; $i<$size; $i++) {
442 if (self::lookAnTable(ord($data[$i])) == -1) {
443 return false;
444 }
445 }
446
447 return true;
448 }
449
450 //----------------------------------------------------------------------
451 public static function estimateBitsModeAn($size)
452 {
453 $w = (int)($size / 2);
454 $bits = $w * 11;
455
456 if($size & 1) {
457 $bits += 6;
458 }
459
460 return $bits;
461 }
462
463 //----------------------------------------------------------------------
464 public static function estimateBitsMode8($size)
465 {
466 return $size * 8;
467 }
468
469 //----------------------------------------------------------------------
470 public function estimateBitsModeKanji($size)
471 {
472 return (int)(($size / 2) * 13);
473 }
474
475 //----------------------------------------------------------------------
476 public static function checkModeKanji($size, $data)
477 {
478 if($size & 1)
479 return false;
480
481 for($i=0; $i<$size; $i+=2) {
482 $val = (ord($data[$i]) << 8) | ord($data[$i+1]);
483 if( $val < 0x8140
484 || ($val > 0x9ffc && $val < 0xe040)
485 || $val > 0xebbf) {
486 return false;
487 }
488 }
489
490 return true;
491 }
492
493 /***********************************************************************
494 * Validation
495 **********************************************************************/
496
497 public static function check($mode, $size, $data)
498 {
499 if($size <= 0)
500 return false;
501
502 switch($mode) {
503 case QR_MODE_NUM: return self::checkModeNum($size, $data); break;
504 case QR_MODE_AN: return self::checkModeAn($size, $data); break;
505 case QR_MODE_KANJI: return self::checkModeKanji($size, $data); break;
506 case QR_MODE_8: return true; break;
507 case QR_MODE_STRUCTURE: return true; break;
508
509 default:
510 break;
511 }
512
513 return false;
514 }
515
516
517 //----------------------------------------------------------------------
518 public function estimateBitStreamSize($version)
519 {
520 $bits = 0;
521
522 foreach($this->items as $item) {
523 $bits += $item->estimateBitStreamSizeOfEntry($version);
524 }
525
526 return $bits;
527 }
528
529 //----------------------------------------------------------------------
530 public function estimateVersion()
531 {
532 $version = 0;
533 $prev = 0;
534 do {
535 $prev = $version;
536 $bits = $this->estimateBitStreamSize($prev);
537 $version = QRspec::getMinimumVersion((int)(($bits + 7) / 8), $this->level);
538 if ($version < 0) {
539 return -1;
540 }
541 } while ($version > $prev);
542
543 return $version;
544 }
545
546 //----------------------------------------------------------------------
547 public static function lengthOfCode($mode, $version, $bits)
548 {
549 $payload = $bits - 4 - QRspec::lengthIndicator($mode, $version);
550 switch($mode) {
551 case QR_MODE_NUM:
552 $chunks = (int)($payload / 10);
553 $remain = $payload - $chunks * 10;
554 $size = $chunks * 3;
555 if($remain >= 7) {
556 $size += 2;
557 } else if($remain >= 4) {
558 $size += 1;
559 }
560 break;
561 case QR_MODE_AN:
562 $chunks = (int)($payload / 11);
563 $remain = $payload - $chunks * 11;
564 $size = $chunks * 2;
565 if($remain >= 6)
566 $size++;
567 break;
568 case QR_MODE_8:
569 $size = (int)($payload / 8);
570 break;
571 case QR_MODE_KANJI:
572 $size = (int)(($payload / 13) * 2);
573 break;
574 case QR_MODE_STRUCTURE:
575 $size = (int)($payload / 8);
576 break;
577 default:
578 $size = 0;
579 break;
580 }
581
582 $maxsize = QRspec::maximumWords($mode, $version);
583 if($size < 0) $size = 0;
584 if($size > $maxsize) $size = $maxsize;
585
586 return $size;
587 }
588
589 //----------------------------------------------------------------------
590 public function createBitStream()
591 {
592 $total = 0;
593
594 foreach($this->items as $item) {
595 $bits = $item->encodeBitStream($this->version);
596
597 if($bits < 0)
598 return -1;
599
600 $total += $bits;
601 }
602
603 return $total;
604 }
605
606 //----------------------------------------------------------------------
607 public function convertData()
608 {
609 $ver = $this->estimateVersion();
610 if($ver > $this->getVersion()) {
611 $this->setVersion($ver);
612 }
613
614 for(;;) {
615 $bits = $this->createBitStream();
616
617 if($bits < 0)
618 return -1;
619
620 $ver = QRspec::getMinimumVersion((int)(($bits + 7) / 8), $this->level);
621 if($ver < 0) {
622 throw new Exception('WRONG VERSION');
623 return -1;
624 } else if($ver > $this->getVersion()) {
625 $this->setVersion($ver);
626 } else {
627 break;
628 }
629 }
630
631 return 0;
632 }
633
634 //----------------------------------------------------------------------
635 public function appendPaddingBit(&$bstream)
636 {
637 $bits = $bstream->size();
638 $maxwords = QRspec::getDataLength($this->version, $this->level);
639 $maxbits = $maxwords * 8;
640
641 if ($maxbits == $bits) {
642 return 0;
643 }
644
645 if ($maxbits - $bits < 5) {
646 return $bstream->appendNum($maxbits - $bits, 0);
647 }
648
649 $bits += 4;
650 $words = (int)(($bits + 7) / 8);
651
652 $padding = new QRbitstream();
653 $ret = $padding->appendNum($words * 8 - $bits + 4, 0);
654
655 if($ret < 0)
656 return $ret;
657
658 $padlen = $maxwords - $words;
659
660 if($padlen > 0) {
661
662 $padbuf = array();
663 for($i=0; $i<$padlen; $i++) {
664 $padbuf[$i] = ($i&1)?0x11:0xec;
665 }
666
667 $ret = $padding->appendBytes($padlen, $padbuf);
668
669 if($ret < 0)
670 return $ret;
671
672 }
673
674 $ret = $bstream->append($padding);
675
676 return $ret;
677 }
678
679 //----------------------------------------------------------------------
680 public function mergeBitStream()
681 {
682 if($this->convertData() < 0) {
683 return null;
684 }
685
686 $bstream = new QRbitstream();
687
688 foreach($this->items as $item) {
689 $ret = $bstream->append($item->bstream);
690 if($ret < 0) {
691 return null;
692 }
693 }
694
695 return $bstream;
696 }
697
698 //----------------------------------------------------------------------
699 public function getBitStream()
700 {
701
702 $bstream = $this->mergeBitStream();
703
704 if($bstream == null) {
705 return null;
706 }
707
708 $ret = $this->appendPaddingBit($bstream);
709 if($ret < 0) {
710 return null;
711 }
712
713 return $bstream;
714 }
715
716 //----------------------------------------------------------------------
717 public function getByteStream()
718 {
719 $bstream = $this->getBitStream();
720 if($bstream == null) {
721 return null;
722 }
723
724 return $bstream->toByte();
725 }
726 }
727
728
729