@@ -393,6 +393,157 @@ namespace HM
393393
394394 return true ;
395395 }
396+
397+ // GetParameter must not split on ';' inside a quoted value.
398+ bool TestGetParameterWithQuotedSemicolon ()
399+ {
400+ MimeField field;
401+ const char * line = " Content-Disposition: attachment; filename=\" semi;colon.dll\"\r\n " ;
402+ field.Load (line, strlen (line), false );
403+
404+ AnsiString value;
405+ if (!field.GetParameter (" filename" , value))
406+ return false ;
407+ return value == " semi;colon.dll" ;
408+ }
409+
410+ // A quoted value containing ';' must not consume the parameter that follows it.
411+ bool TestGetParameterNeighboringParamUnaffectedByQuotedSemicolon ()
412+ {
413+ MimeField field;
414+ const char * line = " Content-Disposition: attachment; filename=\" semi;colon.dll\" ; size=42\r\n " ;
415+ field.Load (line, strlen (line), false );
416+
417+ AnsiString filename;
418+ if (!field.GetParameter (" filename" , filename))
419+ return false ;
420+ if (filename != " semi;colon.dll" )
421+ return false ;
422+
423+ AnsiString size;
424+ if (!field.GetParameter (" size" , size))
425+ return false ;
426+ return size == " 42" ;
427+ }
428+
429+ // RemoveParameter must remove the named parameter while leaving others intact.
430+ bool TestRemoveParameterRemovesSimpleParam ()
431+ {
432+ MimeField field;
433+ const char * line = " Content-Type: text/plain; charset=utf-8; name=\" test.txt\"\r\n " ;
434+ field.Load (line, strlen (line), false );
435+
436+ field.RemoveParameter (" name" );
437+
438+ AnsiString val;
439+ if (field.GetParameter (" name" , val))
440+ return false ;
441+
442+ AnsiString charset;
443+ if (!field.GetParameter (" charset" , charset))
444+ return false ;
445+ return charset == " utf-8" ;
446+ }
447+
448+ // RemoveParameter("filename") must remove RFC 2231 continuation parameters
449+ // (filename*0, filename*1) so that a subsequent SetParameter produces a clean
450+ // single value that GetParameter returns first.
451+ bool TestRemoveParameterRemovesRfc2231Continuations ()
452+ {
453+ MimeField field;
454+ const char * line = " Content-Disposition: attachment; filename*0=\" long\" ; filename*1=\" name.dll\"\r\n " ;
455+ field.Load (line, strlen (line), false );
456+
457+ field.RemoveParameter (" filename" );
458+ field.SetParameter (" filename" , " new.dll" );
459+
460+ AnsiString val;
461+ if (!field.GetParameter (" filename" , val))
462+ return false ;
463+ return val == " new.dll" ;
464+ }
465+
466+ // SetFileName must remove RFC 2231 continuation filename parameters before installing
467+ // the new single filename= value. With the old code the continuations were left
468+ // in place and shadowed the new value returned by GetParameter.
469+ bool TestSetFileNameReplacesRfc2231ContinuationFilename ()
470+ {
471+ MimeHeader header;
472+ const char * headers =
473+ " Content-Type: application/octet-stream\r\n "
474+ " Content-Disposition: attachment; filename*0=\" oldpart0\" ; filename*1=\" oldpart1\"\r\n "
475+ " \r\n " ;
476+ header.Load (headers, strlen (headers));
477+
478+ header.SetFileName (L" replacement.dll" );
479+
480+ string result = header.GetParameter (CMimeConst::ContentDisposition (), CMimeConst::Filename ());
481+
482+ if (result == " oldpart0" || result == " oldpart1" )
483+ return false ;
484+
485+ return result.find (" replacement.dll" ) != string::npos;
486+ }
487+
488+ // SetFileName must also replace a single RFC 2231 encoded filename*= parameter
489+ // rather than appending a new filename= after it.
490+ bool TestSetFileNameReplacesEncodedFilenameVariant ()
491+ {
492+ MimeHeader header;
493+ const char * headers =
494+ " Content-Type: application/octet-stream\r\n "
495+ " Content-Disposition: attachment; filename*=UTF-8''old%20name.dll\r\n "
496+ " \r\n " ;
497+ header.Load (headers, strlen (headers));
498+
499+ header.SetFileName (L" replacement.dll" );
500+
501+ string result = header.GetParameter (CMimeConst::ContentDisposition (), CMimeConst::Filename ());
502+
503+ if (result.find (" old" ) != string::npos)
504+ return false ;
505+
506+ return result.find (" replacement.dll" ) != string::npos;
507+ }
508+
509+ // If there is no filename= on Content-Disposition, SetFileName falls back to
510+ // Content-Type name=. That path should also clear old RFC 2231-style name*
511+ // values before writing the new filename.
512+ bool TestSetFileNameReplacesContentTypeNameVariant ()
513+ {
514+ MimeHeader header;
515+ const char * headers =
516+ " Content-Type: application/octet-stream; name*=UTF-8''old%20name.dll\r\n "
517+ " \r\n " ;
518+ header.Load (headers, strlen (headers));
519+
520+ header.SetFileName (L" replacement.dll" );
521+
522+ string result = header.GetParameter (CMimeConst::ContentType (), CMimeConst::Name ());
523+
524+ if (result.find (" old" ) != string::npos)
525+ return false ;
526+
527+ return result.find (" replacement.dll" ) != string::npos;
528+ }
529+
530+ // An RFC 2231 unquoted value like filename*=UTF-8''hello.dll must not be truncated
531+ // at the apostrophes. The old code used IsToken which stops at non-token characters
532+ // such as apostrophe; the fix scans to ';' or end-of-string instead.
533+ bool TestRfc2231ApostropheInUnquotedValue ()
534+ {
535+ MimeField field;
536+ const char * line = " Content-Disposition: attachment; filename*=UTF-8''hello.dll\r\n " ;
537+ field.Load (line, strlen (line), false );
538+
539+ AnsiString val;
540+ if (!field.GetParameter (" filename" , val))
541+ return false ;
542+ // GetParameter decodes the RFC 2231 value; the decoded result must contain the
543+ // actual filename. With the old code the scan stopped at the first apostrophe
544+ // so only "UTF-8" was captured and "hello.dll" would be absent.
545+ return val.Find (" hello.dll" ) >= 0 ;
546+ }
396547 }
397548
398549 MimeTester::MimeTester (void )
@@ -406,7 +557,7 @@ namespace HM
406557 void
407558 MimeTester::Test ()
408559 {
409- if (!TestFindStringEdgeCases ())
560+ if (!TestFindStringEdgeCases ())
410561 throw ;
411562
412563 if (!TestMultipartWithoutFinalCrlf ())
@@ -474,5 +625,29 @@ namespace HM
474625
475626 if (!TestQPEncodeNoTrailingWhitespaceBeforeSoftBreak ())
476627 throw ;
628+ if (!TestGetParameterWithQuotedSemicolon ())
629+ throw ;
630+
631+ if (!TestGetParameterNeighboringParamUnaffectedByQuotedSemicolon ())
632+ throw ;
633+
634+ if (!TestRemoveParameterRemovesSimpleParam ())
635+ throw ;
636+
637+ if (!TestRemoveParameterRemovesRfc2231Continuations ())
638+ throw ;
639+
640+ if (!TestSetFileNameReplacesRfc2231ContinuationFilename ())
641+ throw ;
642+
643+ if (!TestSetFileNameReplacesEncodedFilenameVariant ())
644+ throw ;
645+
646+ if (!TestSetFileNameReplacesContentTypeNameVariant ())
647+ throw ;
648+
649+ if (!TestRfc2231ApostropheInUnquotedValue ())
650+ throw ;
651+
477652 }
478653}
0 commit comments