Coverage Report

Created: 2026-10-28 06:08

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/resiprocate/resip/stack/MultipartMixedContents.cxx
Line
Count
Source
1
#if defined(HAVE_CONFIG_H)
2
#include "config.h"
3
#endif
4
5
#include "resip/stack/MultipartMixedContents.hxx"
6
#include "resip/stack/SipMessage.hxx"
7
#include "rutil/Logger.hxx"
8
#include "rutil/Random.hxx"
9
#include "rutil/BaseException.hxx"
10
#include "rutil/ParseBuffer.hxx"
11
#include "rutil/WinLeakCheck.hxx"
12
13
using namespace resip;
14
using namespace std;
15
16
#define RESIPROCATE_SUBSYSTEM Subsystem::CONTENTS
17
18
bool
19
MultipartMixedContents::init()
20
10
{
21
10
   static ContentsFactory<MultipartMixedContents> factory;
22
10
   (void)factory;
23
10
   return true;
24
10
}
25
26
MultipartMixedContents::MultipartMixedContents()
27
0
   : Contents(getStaticType()),
28
0
     mContents(),
29
0
     mRawFirstPart(),
30
0
     mDepth(0)
31
0
{
32
0
   setBoundary();
33
0
}
34
35
MultipartMixedContents::MultipartMixedContents(const Mime& contentsType)
36
0
   : Contents(contentsType),
37
0
     mContents(),
38
0
     mRawFirstPart(),
39
0
     mDepth(0)
40
0
{
41
0
   if (!mType.exists(p_boundary))
42
0
   {
43
0
      setBoundary();
44
0
   }
45
0
}
46
47
MultipartMixedContents::MultipartMixedContents(const HeaderFieldValue& hfv, const Mime& contentsType)
48
6.25k
   : Contents(hfv, contentsType),
49
6.25k
     mContents(),
50
6.25k
     mRawFirstPart(),
51
6.25k
     mDepth(0)
52
6.25k
{
53
6.25k
   if (!mType.exists(p_boundary))
54
6.25k
   {
55
6.25k
      setBoundary();
56
6.25k
   }
57
6.25k
}
58
59
// mRawFirstPart is deliberately not carried over: it is a view into the buffer
60
// rhs was parsed from, and the copy has a buffer of its own.  Whether the copy
61
// ends up with a record depends on when it was taken.  Copying an unparsed body
62
// leaves the copy unparsed as well, holding a deep copy of the buffer, and its
63
// first access parses that buffer and records a view into it.  Copying a body
64
// that was already parsed leaves the parts and no record, because nothing
65
// parses a second time, and checkSignature() then falls back to the
66
// re-encoding.  See getRawFirstPart() in the header.
67
MultipartMixedContents::MultipartMixedContents(const MultipartMixedContents& rhs)
68
0
   : Contents(rhs),
69
0
     mContents(),
70
0
     mRawFirstPart(),
71
0
     mDepth(rhs.mDepth)
72
0
{
73
0
   vector<Contents*>::const_iterator j;
74
75
   // .bwc. Don't trigger a parse of the original by calling parts()
76
0
   const vector<Contents*>& list = rhs.mContents;
77
   
78
0
   for ( j = list.begin(); 
79
0
         j != list.end(); ++j)
80
0
   {
81
0
      resip_assert( *j );
82
0
      mContents.push_back( (*j)->clone() );
83
0
   }
84
0
}
85
86
void
87
MultipartMixedContents::setBoundary()
88
6.25k
{
89
6.25k
   Data boundaryToken = Random::getRandomHex(8);
90
6.25k
   mType.param(p_boundary) = boundaryToken;
91
6.25k
}
92
93
void
94
MultipartMixedContents::setBoundary(const Data& boundary)
95
0
{
96
0
    mType.param(p_boundary) = boundary;
97
0
}
98
99
Data
100
MultipartMixedContents::getRawFirstPart() const
101
0
{
102
   // The const overload of checkParsed() parses without marking the object
103
   // DIRTY.  Calling the non-const one here would drop the very bytes this
104
   // function exists to return.
105
0
   checkParsed();
106
0
   return mRawFirstPart;
107
0
}
108
109
void
110
MultipartMixedContents::clear()
111
6.25k
{
112
6.25k
   for (vector<Contents*>::iterator i = mContents.begin(); 
113
6.25k
        i != mContents.end(); ++i)
114
0
   {
115
0
      delete *i;
116
0
   }
117
   // Assign a fresh Data rather than clear(): clear() keeps the shared pointer
118
   // and only sets the size to zero.
119
6.25k
   mRawFirstPart = Data();
120
6.25k
}
121
122
MultipartMixedContents::~MultipartMixedContents()
123
6.25k
{
124
6.25k
   clear();
125
6.25k
}
126
127
MultipartMixedContents&
128
MultipartMixedContents::operator=(const MultipartMixedContents& rhs)
129
0
{
130
0
   if (this != &rhs)
131
0
   {
132
      // Drop the view first: Contents::operator=() replaces the buffer it
133
      // points into.  clear() below would do it too, but only after that, and
134
      // a reader should not have to reason about the order.  Whether a record
135
      // reappears afterwards depends on when the assignment happened, exactly
136
      // as for the copy constructor: from a source nothing has read, this
137
      // object stays unparsed with a buffer of its own and its first access
138
      // records a view into that buffer; from a source that was already
139
      // parsed, no record.
140
0
      mRawFirstPart = Data();
141
0
      Contents::operator=(rhs);
142
0
      clear();
143
0
      mDepth = rhs.mDepth;
144
145
0
      for (vector<Contents*>::const_iterator i = rhs.mContents.begin(); 
146
0
           i != rhs.mContents.end(); ++i)
147
0
      {
148
0
         mContents.push_back( (*i)->clone() );
149
0
      }
150
0
   }
151
0
   return *this;
152
0
}
153
154
Contents* 
155
MultipartMixedContents::clone() const
156
0
{
157
0
   return new MultipartMixedContents(*this);
158
0
}
159
160
const Mime& 
161
MultipartMixedContents::getStaticType() 
162
2
{
163
2
   static Mime type("multipart","mixed");
164
2
   return type;
165
2
}
166
167
EncodeStream& 
168
MultipartMixedContents::encodeParsed(EncodeStream& str) const
169
0
{
170
0
   const Data& boundaryToken = mType.param(p_boundary);
171
0
   Data boundary(boundaryToken.size() + 2, Data::Preallocate);
172
0
   boundary = Symbols::DASHDASH;
173
0
   boundary += boundaryToken;
174
0
   boundary.replace("\"", ""); // remove quotes
175
176
0
   resip_assert( mContents.size() > 0 );
177
   
178
0
   bool first = true;
179
0
   for (vector<Contents*>::const_iterator i = mContents.begin(); 
180
0
        i != mContents.end(); ++i)
181
0
   {
182
0
      if (!first)
183
0
      {
184
0
         str << Symbols::CRLF;
185
0
      }
186
0
      else
187
0
      {
188
0
         first = false;
189
0
      }
190
0
      str << boundary << Symbols::CRLF;
191
0
      (*i)->encodeHeaders(str);
192
0
      (*i)->encode(str);
193
0
   }
194
195
   // Note:  last CRLF isn't strictly required by RFC2046 ABNF, but some implementations seem to require it so we include it
196
0
   str << Symbols::CRLF << boundary << Symbols::DASHDASH << Symbols::CRLF;
197
0
   return str;
198
0
}
199
200
// The boundary delimiter MUST occur at the beginning of a line, i.e., following
201
// a CRLF, and the initial CRLF is considered to be attached to the boundary
202
// delimiter line rather than part of the preceding part.
203
void
204
MultipartMixedContents::parse(ParseBuffer& pb)
205
6.25k
{
206
   // The depth is stamped on this object when the enclosing part created it
207
   // (below), so this holds whether the parse runs right away or much later
208
   // from a tree walk such as Helper::getSdp().
209
6.25k
   if (mDepth > MaxNestingDepth)
210
0
   {
211
0
      throw Exception("Multipart nesting too deep", __FILE__, __LINE__);
212
0
   }
213
214
6.25k
   const Data& boundaryToken = mType.param(p_boundary);
215
   
216
6.25k
   Data boundary(boundaryToken.size() + 4, Data::Preallocate);
217
6.25k
   boundary += Symbols::CRLF;
218
6.25k
   boundary += Symbols::DASHDASH;
219
6.25k
   boundary += boundaryToken;
220
221
6.25k
   Data boundaryNoCRLF(boundaryToken.size() + 2, Data::Preallocate);
222
6.25k
   boundaryNoCRLF += Symbols::DASHDASH;
223
6.25k
   boundaryNoCRLF += boundaryToken;
224
225
6.25k
   pb.skipToChars(boundaryNoCRLF);
226
6.25k
   pb.skipN(boundaryNoCRLF.size());
227
6.25k
   pb.assertNotEof();
228
229
   // Start from a known state instead of inferring "first part" from mContents
230
   // being empty: clear() deletes the parts but leaves the vector filled.
231
6.25k
   bool firstPartSeen = false;
232
6.25k
   mRawFirstPart = Data();
233
234
6.25k
   do
235
6.25k
   {
236
      // skip over boundary
237
 
238
6.25k
      if( pb.eof() || *pb.position() != Symbols::CR[0] )
239
0
      {
240
0
         throw Exception("Invalid line ending, missing CR",__FILE__,__LINE__);
241
0
      }
242
6.25k
      pb.skipChar();
243
6.25k
      if( pb.eof() || *pb.position() != Symbols::LF[0] )
244
0
      {
245
0
         throw Exception("Invalid line ending, missing LF",__FILE__,__LINE__);
246
0
      }
247
6.25k
      pb.skipChar();
248
      
249
6.25k
      pb.assertNotEof();
250
251
6.25k
      const char* headerStart = pb.position();
252
253
      // pull out contents type only
254
6.25k
      pb.skipToChars("Content-Type");
255
6.25k
      if (pb.eof())
256
0
      {
257
0
         pb.reset(headerStart);
258
0
         pb.skipToChars("CONTENT-TYPE");
259
0
      }
260
6.25k
      pb.assertNotEof();
261
262
6.25k
      pb.skipToChar(Symbols::COLON[0]);
263
6.25k
      pb.skipChar();
264
6.25k
      pb.assertNotEof();
265
      
266
6.25k
      pb.skipWhitespace();
267
6.25k
      const char* typeStart = pb.position();
268
6.25k
      pb.assertNotEof();
269
      
270
      // determine contents-type header buffer
271
6.25k
      pb.skipToTermCRLF();
272
6.25k
      pb.assertNotEof();
273
274
6.25k
      ParseBuffer subPb(typeStart, pb.position() - typeStart);
275
6.25k
      Mime contentType;
276
6.25k
      contentType.parse(subPb);
277
      
278
6.25k
      pb.assertNotEof();
279
280
      // determine body start
281
6.25k
      pb.reset(typeStart);
282
6.25k
      const char* bodyStart = pb.skipToChars(Symbols::CRLFCRLF);
283
6.25k
      pb.assertNotEof();
284
6.25k
      bodyStart += 4;
285
286
      // determine contents body buffer
287
6.25k
      pb.skipToChars(boundary);
288
6.25k
      pb.assertNotEof();
289
6.25k
      Data tmp;
290
6.25k
      pb.data(tmp, bodyStart);
291
6.25k
      if (!firstPartSeen)
292
0
      {
293
0
         firstPartSeen = true;
294
         // Keep a view of the first part exactly as it arrived, header block
295
         // included.  The parse position sits on the CRLF that belongs to the
296
         // next boundary (RFC 2046 5.1.1), so that CRLF is correctly left out.
297
         // Data::Share means this points into the buffer being parsed instead
298
         // of copying it, so an ordinary multipart message costs nothing here.
299
         // Only the first part is kept: it is the one RFC 1847 signs.
300
0
         mRawFirstPart = Data(Data::Share, headerStart,
301
0
                              static_cast<Data::size_type>(pb.position() - headerStart));
302
0
      }
303
      // create contents against body
304
6.25k
      mContents.push_back(createContents(contentType, tmp));
305
      // A nested multipart part (of any subtype) sits one level deeper.
306
6.25k
      MultipartMixedContents* nested = dynamic_cast<MultipartMixedContents*>(mContents.back());
307
6.25k
      if (nested)
308
0
      {
309
0
         nested->mDepth = mDepth + 1;
310
0
      }
311
      // pre-parse headers
312
6.25k
      ParseBuffer headersPb(headerStart, bodyStart-4-headerStart);
313
6.25k
      mContents.back()->preParseHeaders(headersPb);
314
315
6.25k
      pb.skipN(boundary.size());
316
317
6.25k
      const char* loc = pb.position();
318
6.25k
      pb.skipChar();
319
6.25k
      pb.skipChar();
320
6.25k
      Data next;
321
6.25k
      pb.data(next, loc);
322
323
6.25k
      if ( next == Symbols::DASHDASH )
324
0
      {
325
0
         break;
326
0
      }
327
6.25k
      pb.reset( loc );
328
6.25k
   }
329
6.25k
   while ( !pb.eof() );
330
331
6.25k
}
332
333
/* ====================================================================
334
 * The Vovida Software License, Version 1.0 
335
 * 
336
 * Copyright (c) 2026 SIP Spectrum, Inc. https://www.sipspectrum.com
337
 * Copyright (c) 2000 Vovida Networks, Inc.  All rights reserved.
338
 * 
339
 * Redistribution and use in source and binary forms, with or without
340
 * modification, are permitted provided that the following conditions
341
 * are met:
342
 * 
343
 * 1. Redistributions of source code must retain the above copyright
344
 *    notice, this list of conditions and the following disclaimer.
345
 * 
346
 * 2. Redistributions in binary form must reproduce the above copyright
347
 *    notice, this list of conditions and the following disclaimer in
348
 *    the documentation and/or other materials provided with the
349
 *    distribution.
350
 * 
351
 * 3. The names "VOCAL", "Vovida Open Communication Application Library",
352
 *    and "Vovida Open Communication Application Library (VOCAL)" must
353
 *    not be used to endorse or promote products derived from this
354
 *    software without prior written permission. For written
355
 *    permission, please contact vocal@vovida.org.
356
 *
357
 * 4. Products derived from this software may not be called "VOCAL", nor
358
 *    may "VOCAL" appear in their name, without prior written
359
 *    permission of Vovida Networks, Inc.
360
 * 
361
 * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESSED OR IMPLIED
362
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
363
 * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE AND
364
 * NON-INFRINGEMENT ARE DISCLAIMED.  IN NO EVENT SHALL VOVIDA
365
 * NETWORKS, INC. OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT DAMAGES
366
 * IN EXCESS OF $1,000, NOR FOR ANY INDIRECT, INCIDENTAL, SPECIAL,
367
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
368
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
369
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
370
 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
371
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
372
 * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
373
 * DAMAGE.
374
 * 
375
 * ====================================================================
376
 * 
377
 * This software consists of voluntary contributions made by Vovida
378
 * Networks, Inc. and many individuals on behalf of Vovida Networks,
379
 * Inc.  For more information on Vovida Networks, Inc., please see
380
 * <http://www.vovida.org/>.
381
 *
382
 */