1 | /* -*- c++ -*- |
2 | SPDX-FileCopyrightText: 2002 Marc Mutz <mutz@kde.org> |
3 | |
4 | SPDX-License-Identifier: LGPL-2.0-or-later |
5 | */ |
6 | |
7 | #include "kcodecsuuencode.h" |
8 | |
9 | #include <QDebug> |
10 | |
11 | #include <cassert> |
12 | |
13 | using namespace KCodecs; |
14 | |
15 | namespace KCodecs |
16 | { |
17 | class UUDecoder : public Decoder |
18 | { |
19 | uint mStepNo; |
20 | uchar mAnnouncedOctetCount; // (on current line) |
21 | uchar mCurrentOctetCount; // (on current line) |
22 | uchar mOutbits; |
23 | bool mLastWasCRLF : 1; |
24 | bool mSawBegin : 1; // whether we already saw ^begin... |
25 | uint mIntoBeginLine : 3; // count #chars we compared against "begin" 0..5 |
26 | bool mSawEnd : 1; // whether we already saw ^end... |
27 | uint mIntoEndLine : 2; // count #chars we compared against "end" 0..3 |
28 | |
29 | void searchForBegin(const char *&scursor, const char *const send); |
30 | |
31 | protected: |
32 | friend class UUCodec; |
33 | UUDecoder(Codec::NewlineType newline = Codec::NewlineLF) |
34 | : Decoder(newline) |
35 | , mStepNo(0) |
36 | , mAnnouncedOctetCount(0) |
37 | , mCurrentOctetCount(0) |
38 | , mOutbits(0) |
39 | , mLastWasCRLF(true) |
40 | , mSawBegin(false) |
41 | , mIntoBeginLine(0) |
42 | , mSawEnd(false) |
43 | , mIntoEndLine(0) |
44 | { |
45 | } |
46 | |
47 | public: |
48 | ~UUDecoder() override |
49 | { |
50 | } |
51 | |
52 | bool decode(const char *&scursor, const char *const send, char *&dcursor, const char *const dend) override; |
53 | // ### really needs no finishing??? |
54 | bool finish(char *&dcursor, const char *const dend) override |
55 | { |
56 | Q_UNUSED(dcursor); |
57 | Q_UNUSED(dend); |
58 | return true; |
59 | } |
60 | }; |
61 | |
62 | Encoder *UUCodec::makeEncoder(NewlineType newline) const |
63 | { |
64 | Q_UNUSED(newline) |
65 | return nullptr; // encoding not supported |
66 | } |
67 | |
68 | Decoder *UUCodec::makeDecoder(NewlineType newline) const |
69 | { |
70 | return new UUDecoder(newline); |
71 | } |
72 | |
73 | /********************************************************/ |
74 | /********************************************************/ |
75 | /********************************************************/ |
76 | |
77 | void UUDecoder::searchForBegin(const char *&scursor, const char *const send) |
78 | { |
79 | static const char begin[] = "begin\n" ; |
80 | static const uint beginLength = 5; // sic! |
81 | |
82 | assert(!mSawBegin || mIntoBeginLine > 0); |
83 | |
84 | while (scursor != send) { |
85 | uchar ch = *scursor++; |
86 | if (ch == begin[mIntoBeginLine]) { |
87 | if (mIntoBeginLine < beginLength) { |
88 | // found another char |
89 | ++mIntoBeginLine; |
90 | if (mIntoBeginLine == beginLength) { |
91 | mSawBegin = true; // "begin" complete, now search the next \n... |
92 | } |
93 | } else { // mIntoBeginLine == beginLength |
94 | // found '\n': begin line complete |
95 | mLastWasCRLF = true; |
96 | mIntoBeginLine = 0; |
97 | return; |
98 | } |
99 | } else if (mSawBegin) { |
100 | // OK, skip stuff until the next \n |
101 | } else { |
102 | // qWarning() << "UUDecoder: garbage before \"begin\", resetting parser"; |
103 | mIntoBeginLine = 0; |
104 | } |
105 | } |
106 | } |
107 | |
108 | // uuencoding just shifts all 6-bit octets by 32 (SP/' '), except NUL, |
109 | // which gets mapped to 0x60 |
110 | static inline uchar uuDecode(uchar c) |
111 | { |
112 | return (c - ' ') // undo shift and |
113 | & 0x3F; // map 0x40 (0x60-' ') to 0... |
114 | } |
115 | |
116 | bool UUDecoder::decode(const char *&scursor, const char *const send, char *&dcursor, const char *const dend) |
117 | { |
118 | // First, check whether we still need to find the "begin" line: |
119 | if (!mSawBegin || mIntoBeginLine != 0) { |
120 | searchForBegin(scursor, send); |
121 | } else if (mSawEnd) { |
122 | // or if we are past the end line: |
123 | scursor = send; // do nothing anymore... |
124 | return true; |
125 | } |
126 | |
127 | while (dcursor != dend && scursor != send) { |
128 | uchar ch = *scursor++; |
129 | uchar value; |
130 | |
131 | // Check whether we need to look for the "end" line: |
132 | if (mIntoEndLine > 0) { |
133 | static const char end[] = "end" ; |
134 | static const uint endLength = 3; |
135 | |
136 | if (ch == end[mIntoEndLine]) { |
137 | ++mIntoEndLine; |
138 | if (mIntoEndLine == endLength) { |
139 | mSawEnd = true; |
140 | scursor = send; // shortcut to the end |
141 | return true; |
142 | } |
143 | continue; |
144 | } else { |
145 | // qWarning() << "UUDecoder: invalid line octet count looks like \"end\" (mIntoEndLine =" |
146 | // << mIntoEndLine << ")!"; |
147 | mIntoEndLine = 0; |
148 | // fall through... |
149 | } |
150 | } |
151 | |
152 | // Normal parsing: |
153 | |
154 | // The first char of a line is an encoding of the length of the |
155 | // current line. We simply ignore it: |
156 | if (mLastWasCRLF) { |
157 | // reset char-per-line counter: |
158 | mLastWasCRLF = false; |
159 | mCurrentOctetCount = 0; |
160 | |
161 | // try to decode the chars-on-this-line announcement: |
162 | if (ch == 'e') { // maybe the beginning of the "end"? ;-) |
163 | mIntoEndLine = 1; |
164 | } else if (ch > 0x60) { |
165 | // ### invalid line length char: what shall we do?? |
166 | } else if (ch > ' ') { |
167 | mAnnouncedOctetCount = uuDecode(c: ch); |
168 | } else if (ch == '\n') { |
169 | mLastWasCRLF = true; // oops, empty line |
170 | } |
171 | |
172 | continue; |
173 | } |
174 | |
175 | // try converting ch to a 6-bit value: |
176 | if (ch > 0x60) { |
177 | continue; // invalid char |
178 | } else if (ch > ' ') { |
179 | value = uuDecode(c: ch); |
180 | } else if (ch == '\n') { // line end |
181 | mLastWasCRLF = true; |
182 | continue; |
183 | } else { |
184 | continue; |
185 | } |
186 | |
187 | // add the new bits to the output stream and flush full octets: |
188 | switch (mStepNo) { |
189 | case 0: |
190 | mOutbits = value << 2; |
191 | break; |
192 | case 1: |
193 | if (mCurrentOctetCount < mAnnouncedOctetCount) { |
194 | *dcursor++ = (char)(mOutbits | value >> 4); |
195 | } |
196 | ++mCurrentOctetCount; |
197 | mOutbits = value << 4; |
198 | break; |
199 | case 2: |
200 | if (mCurrentOctetCount < mAnnouncedOctetCount) { |
201 | *dcursor++ = (char)(mOutbits | value >> 2); |
202 | } |
203 | ++mCurrentOctetCount; |
204 | mOutbits = value << 6; |
205 | break; |
206 | case 3: |
207 | if (mCurrentOctetCount < mAnnouncedOctetCount) { |
208 | *dcursor++ = (char)(mOutbits | value); |
209 | } |
210 | ++mCurrentOctetCount; |
211 | mOutbits = 0; |
212 | break; |
213 | default: |
214 | assert(0); |
215 | } |
216 | mStepNo = (mStepNo + 1) % 4; |
217 | |
218 | // check whether we ran over the announced octet count for this line: |
219 | if (mCurrentOctetCount == mAnnouncedOctetCount + 1) { |
220 | // qWarning() |
221 | // << "UUDecoder: mismatch between announced (" |
222 | // << mAnnouncedOctetCount << ") and actual line octet count!"; |
223 | } |
224 | } |
225 | |
226 | // return false when caller should call us again: |
227 | return scursor == send; |
228 | } // UUDecoder::decode() |
229 | |
230 | } // namespace KCodecs |
231 | |