Passed
Push — main ( a8595a...1afaaf )
by Garbuz
03:24
created

Coder::jsonEncode()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 7
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 6

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 4
c 1
b 0
f 0
dl 0
loc 7
ccs 0
cts 5
cp 0
rs 10
cc 2
nc 2
nop 1
crap 6
1
<?php
2
3
namespace Garbuzivan\Laraveltokens;
4
5
use Exception;
6
7
class Coder
8
{
9
    /**
10
     * @param array  $payload
11
     * @param string $key
12
     * @param array  $head [exp - expiration]
13
     *
14
     * @return string
15
     * @throws Exception
16
     */
17
    public function encode(array $payload, string $key, array $head = []): string
18
    {
19
        $header = ['typ' => 'JWT', 'alg' => 'HS256'];
20
        $header = \array_merge($head, $header);
21
        $segments = [];
22
        $segments[] = $this->urlsafeB64Encode($this->jsonEncode($header));
23
        $segments[] = $this->urlsafeB64Encode($this->jsonEncode($payload));
24
        $signing_input = \implode('.', $segments);
25
        $signature = $this->sign($signing_input, $key);
26
        $segments[] = $this->urlsafeB64Encode($signature);
27
        return \implode('.', $segments);
28
    }
29
30
    public function decode(string $token, string $key): array
31
    {
32
        $data = [];
33
        $tks = \explode('.', $token);
34
        if (\count($tks) != 3) {
35
            return $data;
36
        }
37
        [$headb64, $bodyb64, $cryptob64] = $tks;
38
        if (null === $header = $this->jsonDecode($this->urlsafeB64Decode($headb64))) {
0 ignored issues
show
introduced by
The condition null === $header = $this...afeB64Decode($headb64)) is always false.
Loading history...
39
            return $data;
40
        }
41
        if (null === $payload = $this->jsonDecode($this->urlsafeB64Decode($bodyb64))) {
0 ignored issues
show
introduced by
The condition null === $payload = $thi...afeB64Decode($bodyb64)) is always false.
Loading history...
42
            return $data;
43
        }
44
        if (false === $sig = $this->urlsafeB64Decode($cryptob64)) {
45
            return $data;
46
        }
47
        $segments = [];
48
        $segments[] = $this->urlsafeB64Encode($this->jsonEncode($header));
49
        $segments[] = $this->urlsafeB64Encode($this->jsonEncode($payload));
50
        $signing_input = \implode('.', $segments);
51
        if ($this->sign($signing_input, $key) == $sig) {
52
            return \array_merge($header, $payload);
53
        }
54
        return $data;
55
    }
56
57
    /**
58
     * Decode a JSON string into a PHP object.
59
     *
60
     * @param string $input JSON string
61
     *
62
     * @return array Object representation of JSON string
63
     *
64
     * @throws Exception Provided string was invalid JSON
65
     */
66
    public function jsonDecode(string $input): array
67
    {
68
        if (\version_compare(PHP_VERSION, '5.4.0', '>=')
69
            && !(\defined('JSON_C_VERSION') && PHP_INT_SIZE > 4)
70
        ) {
71
            /** In PHP >=5.4.0, json_decode() accepts an options parameter, that allows you
72
             * to specify that large ints (like Steam Transaction IDs) should be treated as
73
             * strings, rather than the PHP default behaviour of converting them to floats.
74
             */
75
            $obj = \json_decode($input, true, 512, JSON_BIGINT_AS_STRING);
76
        } else {
77
            /** Not all servers will support that, however, so for older versions we must
78
             * manually detect large ints in the JSON string and quote them (thus converting
79
             *them to strings) before decoding, hence the preg_replace() call.
80
             */
81
            $maxLength = \strlen((string)PHP_INT_MAX) - 1;
82
            $withoutBigints = \preg_replace('/:\s*(-?\d{' . $maxLength . ',})/', ': "$1"', $input);
83
            $obj = \json_decode($withoutBigints, true);
84
        }
85
86
        if ($errno = \json_last_error()) {
87
            $this->handleJsonError($errno);
88
        } elseif ($obj === null && $input !== 'null') {
89
            throw new Exception('Null result with non-null input');
90
        }
91
        return $obj;
92
    }
93
94
    /**
95
     * Encode a PHP object into a JSON string.
96
     *
97
     * @param array $input A PHP object or array
98
     *
99
     * @return string JSON representation of the PHP object or array
100
     *
101
     * @throws Exception Provided object could not be encoded to valid JSON
102
     */
103
    public function jsonEncode(array $input): string
104
    {
105
        $json = \json_encode($input);
106
        if ($errno = \json_last_error()) {
107
            $this->handleJsonError($errno);
108
        }
109
        return $json;
110
    }
111
112
    /**
113
     * Helper method to create a JSON error.
114
     *
115
     * @param int $errno An error number from json_last_error()
116
     *
117
     * @return void
118
     * @throws Exception
119
     */
120
    private function handleJsonError(int $errno)
121
    {
122
        $messages = [
123
            JSON_ERROR_DEPTH          => 'Maximum stack depth exceeded',
124
            JSON_ERROR_STATE_MISMATCH => 'Invalid or malformed JSON',
125
            JSON_ERROR_CTRL_CHAR      => 'Unexpected control character found',
126
            JSON_ERROR_SYNTAX         => 'Syntax error, malformed JSON',
127
            JSON_ERROR_UTF8           => 'Malformed UTF-8 characters' //PHP >= 5.3.3
128
        ];
129
        throw new Exception($messages[$errno] ?? 'Unknown JSON error: ' . $errno);
130
    }
131
132
    /**
133
     * @param string $msg
134
     * @param string $key
135
     *
136
     * @return false|string
137
     */
138
    public function sign(string $msg, string $key)
139
    {
140
        return \hash_hmac('SHA256', $msg, $key, true);
141
    }
142
143
    /**
144
     * Decode a string with URL-safe Base64.
145
     *
146
     * @param string $input A Base64 encoded string
147
     *
148
     * @return string A decoded string
149
     */
150
    public function urlsafeB64Decode($input)
151
    {
152
        $remainder = \strlen($input) % 4;
153
        if ($remainder) {
154
            $padlen = 4 - $remainder;
155
            $input .= \str_repeat('=', $padlen);
156
        }
157
        return \base64_decode(\strtr($input, '-_', '+/'));
158
    }
159
160
    /**
161
     * Encode a string with URL-safe Base64.
162
     *
163
     * @param string $input The string you want encoded
164
     *
165
     * @return string The base64 encode of what you passed in
166
     */
167
    public function urlsafeB64Encode($input)
168
    {
169
        return \str_replace('=', '', \strtr(\base64_encode($input), '+/', '-_'));
170
    }
171
}
172