1/*
2  Copyright (C) 2000-2005 SKYRIX Software AG
3
4  This file is part of SOPE.
5
6  SOPE is free software; you can redistribute it and/or modify it under
7  the terms of the GNU Lesser General Public License as published by the
8  Free Software Foundation; either version 2, or (at your option) any
9  later version.
10
11  SOPE is distributed in the hope that it will be useful, but WITHOUT ANY
12  WARRANTY; without even the implied warranty of MERCHANTABILITY or
13  FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public
14  License for more details.
15
16  You should have received a copy of the GNU Lesser General Public
17  License along with SOPE; see the file COPYING.  If not, write to the
18  Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
19  02111-1307, USA.
20*/
21
22#include "common.h"
23#include <string.h>
24
25@implementation NSData(MimeQPHeaderFieldDecoding)
26
27static Class NSStringClass   = Nil;
28static Class NGMimeTypeClass = Nil;
29
30- (id)decodeQuotedPrintableValueOfMIMEHeaderField:(NSString *)_name {
31  // check data for 8-bit headerfields (RFC 2047 (MIME PART III))
32  /*
33    TODO: document
34
35    This method returns an NSString or an NSData object (or nil).
36  */
37  enum {
38    NGMimeMessageParser_quoted_start   = 1,
39    NGMimeMessageParser_quoted_charSet = 2,
40    NGMimeMessageParser_quoted_qpData  = 3,
41    NGMimeMessageParser_quoted_end     = 4
42  } status = NGMimeMessageParser_quoted_start;
43  unsigned int        length;
44  const unsigned char *bytes, *firstEq;
45  BOOL foundQP = NO;
46
47  /* setup statics */
48  if (NSStringClass   == Nil) NSStringClass   = [NSString class];
49  if (NGMimeTypeClass == Nil) NGMimeTypeClass = [NGMimeType class];
50
51
52  /* begin */
53  length = [self length];
54
55  /* check whether the string is long enough to be quoted etc */
56  if (length <= 6)
57    return self;
58
59  /* check whether the string contains QP tokens ... */
60  bytes = [self bytes];
61
62  if ((firstEq = memchr(bytes, '=', length)) == NULL)
63    return self;
64
65  /* process data ... (quoting etc) */
66  {
67    unichar       *buffer;
68    unsigned int  bufLen, maxBufLen;
69    NSString      *charset;
70    BOOL          appendLC, encodedLastWord;
71    int           cnt, tmp;
72    unsigned char encoding;
73
74    buffer = calloc(length + 13, sizeof(unichar));
75
76    maxBufLen             = length + 3;
77    buffer[maxBufLen - 1] = '\0';
78    bufLen                = 0;
79
80    encoding = 0;
81    tmp      = -1;
82    appendLC = YES;
83    encodedLastWord = NO;
84    charset  = nil;
85    status   = NGMimeMessageParser_quoted_start;
86
87    /* copy data up to first '=' sign */
88    if ((cnt = (firstEq - bytes)) > 0) {
89      for (; bufLen < cnt; bufLen++)
90        buffer[bufLen] = bytes[bufLen];
91    }
92
93    for (; cnt < (length - 1); cnt++) {
94      appendLC = YES;
95
96      if (status == NGMimeMessageParser_quoted_start) {
97        if ((bytes[cnt] == '=') && (bytes[cnt + 1] == '?')) { // found begin
98          cnt++;
99          if (encodedLastWord && tmp != -1) {
100            // We must remove the whitespace added since `tmp`
101            int i;
102            for (i = bufLen - 1; i >= tmp; i--)
103              buffer[i] = '\0';
104            bufLen = tmp;
105          }
106          tmp = -1;
107          status = NGMimeMessageParser_quoted_charSet;
108        } else if (bytes[cnt] == ' ' || bytes[cnt] == '\r' ||
109                   bytes[cnt] == '\n' || bytes[cnt] == '\t') {
110          // We must ignore whitespace between qp encoded strings.
111          // Both are valid:
112          // (ab) -> (=?utf-8?q?a?=  =?utf-8?q?b?=)
113          // (ab) -> (=?utf-8?q?a?=
114          //             =?utf-8?q?b?=)
115          if (tmp == -1) // first whitespace found
116            tmp = bufLen;
117          buffer[bufLen++] = bytes[cnt];
118        } else {
119          encodedLastWord = NO;
120          buffer[bufLen++] = bytes[cnt];
121        }
122      }
123      else if (status == NGMimeMessageParser_quoted_charSet) {
124        if (tmp == -1)
125          tmp = cnt;
126
127        if (bytes[cnt] == '?') {
128          charset =
129	    [NSStringClass stringWithCString:(char *)(bytes + tmp)
130			   length:(cnt - tmp)];
131          tmp = -1;
132
133          if ((length - cnt) > 2) {
134	    // set encoding (eg 'q' for quoted printable)
135            cnt++; // skip '?'
136            encoding = bytes[cnt];
137            cnt++; // skip encoding
138            status = NGMimeMessageParser_quoted_qpData;
139          }
140          else { // unexpected end
141            NSLog(@"WARNING: unexpected end of header");
142            appendLC = NO;
143            break;
144          }
145        }
146      }
147      else if (status == NGMimeMessageParser_quoted_qpData) {
148        if (tmp == -1)
149          tmp = cnt;
150
151        if ((bytes[cnt] == '?') && (bytes[cnt + 1] == '=')) {
152          NSData           *tmpData;
153          NSString         *tmpStr;
154	  unsigned int     tmpLen;
155
156          tmpData = _rfc2047Decoding(encoding, (char *)bytes + tmp, cnt - tmp);
157	  foundQP = YES;
158
159	  /*
160	     create a temporary string for charset conversion ...
161	     Note: the headerfield is currently held in ISO Latin 1
162	  */
163	  tmpStr = [NSStringClass stringWithData:tmpData
164			      usingEncodingNamed:charset];
165
166          if (tmpStr == nil) {
167            NSStringEncoding enc;
168
169            enc    = [NGMimeTypeClass stringEncodingForCharset:charset];
170            tmpStr = [[[NSStringClass alloc] initWithData:tmpData encoding:enc]
171                                      autorelease];
172
173	    // Fall-back to ISO Latin 1 if the decoding using the specified charset
174	    // has failed. Note that we shouldn't be doing this here, we should let
175	    // the application decide what to do but SOPE is just too lame and does
176	    // not expose a failure vs. the initial value of the header
177	    if (!tmpStr)
178	      tmpStr = [[[NSStringClass alloc] initWithData:tmpData
179						   encoding:NSISOLatin1StringEncoding]
180			 autorelease];
181          }
182	  tmpLen = [tmpStr length];
183
184	  if ((tmpLen + bufLen) < maxBufLen) {
185	    [tmpStr getCharacters:(buffer + bufLen)];
186	    bufLen += tmpLen;
187	  }
188	  else {
189	    [self errorWithFormat:@"%s: quoted data to large --> ignored %@",
190		  __PRETTY_FUNCTION__, tmpStr];
191	  }
192          tmp = -1;
193          cnt++;
194          appendLC = YES;
195          status   = NGMimeMessageParser_quoted_start;
196          encodedLastWord = YES;
197        }
198      }
199    }
200    if (appendLC) {
201      if (cnt < length) {
202        buffer[bufLen] = bytes[cnt];
203        bufLen++;
204      }
205    }
206    buffer[bufLen] = '\0';
207    while(bufLen > 1 && buffer[bufLen-1] == '\0')
208      bufLen--;
209    {
210      id data;
211
212      data = nil;
213
214      if (buffer && foundQP) {
215        data = [[[NSStringClass alloc] initWithCharacters:buffer length:bufLen]
216                                autorelease];
217        if (data == nil) {
218          [self warnWithFormat:
219		  @"%s: got no string for buffer '%s', length '%i' !",
220                  __PRETTY_FUNCTION__, buffer, bufLen];
221        }
222      }
223
224      if (data == nil)
225        data = self; /* we return an NSData */
226
227      if (buffer != NULL) free(buffer); buffer = NULL;
228      return data;
229    }
230  }
231  return self;
232}
233
234@end /* NSData(MimeQPHeaderFieldDecoding) */
235