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