1 /*
2 * This file is part of nzbget. See <http://nzbget.net>.
3 *
4 * Copyright (C) 2004 Sven Henkel <sidddy@users.sourceforge.net>
5 * Copyright (C) 2007-2016 Andrey Prygunkov <hugbug@users.sourceforge.net>
6 *
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 2 of the License, or
10 * (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program. If not, see <http://www.gnu.org/licenses/>.
19 */
20
21
22 #include "nzbget.h"
23 #include "NzbFile.h"
24 #include "Log.h"
25 #include "DownloadInfo.h"
26 #include "Options.h"
27 #include "DiskState.h"
28 #include "Util.h"
29 #include "FileSystem.h"
30
NzbFile(const char * fileName,const char * category)31 NzbFile::NzbFile(const char* fileName, const char* category) :
32 m_fileName(fileName)
33 {
34 debug("Creating NZBFile");
35
36 m_nzbInfo = std::make_unique<NzbInfo>();
37 m_nzbInfo->SetFilename(fileName);
38 m_nzbInfo->SetCategory(category);
39 m_nzbInfo->BuildDestDirName();
40 }
41
LogDebugInfo()42 void NzbFile::LogDebugInfo()
43 {
44 info(" NZBFile %s", *m_fileName);
45 }
46
AddArticle(FileInfo * fileInfo,std::unique_ptr<ArticleInfo> articleInfo)47 void NzbFile::AddArticle(FileInfo* fileInfo, std::unique_ptr<ArticleInfo> articleInfo)
48 {
49 int index = articleInfo->GetPartNumber() - 1;
50
51 // make Article-List big enough
52 if (index >= (int)fileInfo->GetArticles()->size())
53 {
54 fileInfo->GetArticles()->resize(index + 1);
55 }
56
57 (*fileInfo->GetArticles())[index] = std::move(articleInfo);
58 }
59
AddFileInfo(std::unique_ptr<FileInfo> fileInfo)60 void NzbFile::AddFileInfo(std::unique_ptr<FileInfo> fileInfo)
61 {
62 // calculate file size and delete empty articles
63
64 int64 size = 0;
65 int64 missedSize = 0;
66 int64 oneSize = 0;
67 int uncountedArticles = 0;
68 int missedArticles = 0;
69 int totalArticles = (int)fileInfo->GetArticles()->size();
70 int i = 0;
71 for (ArticleList::iterator it = fileInfo->GetArticles()->begin(); it != fileInfo->GetArticles()->end(); )
72 {
73 ArticleInfo* article = (*it).get();
74 if (!article)
75 {
76 fileInfo->GetArticles()->erase(it);
77 it = fileInfo->GetArticles()->begin() + i;
78 missedArticles++;
79 if (oneSize > 0)
80 {
81 missedSize += oneSize;
82 }
83 else
84 {
85 uncountedArticles++;
86 }
87 }
88 else
89 {
90 size += article->GetSize();
91 if (oneSize == 0)
92 {
93 oneSize = article->GetSize();
94 }
95 it++;
96 i++;
97 }
98 }
99
100 if (fileInfo->GetArticles()->empty())
101 {
102 return;
103 }
104
105 missedSize += uncountedArticles * oneSize;
106 size += missedSize;
107 fileInfo->SetNzbInfo(m_nzbInfo.get());
108 fileInfo->SetSize(size);
109 fileInfo->SetRemainingSize(size - missedSize);
110 fileInfo->SetMissedSize(missedSize);
111 fileInfo->SetTotalArticles(totalArticles);
112 fileInfo->SetMissedArticles(missedArticles);
113 m_nzbInfo->GetFileList()->Add(std::move(fileInfo));
114 }
115
ParseSubject(FileInfo * fileInfo,bool TryQuotes)116 void NzbFile::ParseSubject(FileInfo* fileInfo, bool TryQuotes)
117 {
118 // Example subject: some garbage "title" yEnc (10/99)
119
120 if (!fileInfo->GetSubject())
121 {
122 // Malformed file element without subject. We generate subject using internal element id.
123 fileInfo->SetSubject(CString::FormatStr("%d", fileInfo->GetId()));
124 }
125
126 // strip the "yEnc (10/99)"-suffix
127 BString<1024> subject = fileInfo->GetSubject();
128 char* end = subject + strlen(subject) - 1;
129 if (*end == ')')
130 {
131 end--;
132 while (strchr("0123456789", *end) && end > subject) end--;
133 if (*end == '/')
134 {
135 end--;
136 while (strchr("0123456789", *end) && end > subject) end--;
137 if (end - 6 > subject && !strncmp(end - 6, " yEnc (", 7))
138 {
139 end[-6] = '\0';
140 }
141 }
142 }
143
144 if (TryQuotes)
145 {
146 // try to use the filename in quatation marks
147 char* p = subject;
148 char* start = strchr(p, '\"');
149 if (start)
150 {
151 start++;
152 char* end = strchr(start + 1, '\"');
153 if (end)
154 {
155 int len = (int)(end - start);
156 char* point = strchr(start + 1, '.');
157 if (point && point < end)
158 {
159 BString<1024> filename;
160 filename.Set(start, len);
161 fileInfo->SetFilename(filename);
162 return;
163 }
164 }
165 }
166 }
167
168 // tokenize subject, considering spaces as separators and quotation
169 // marks as non separatable token delimiters.
170 // then take the last token containing dot (".") as a filename
171
172 typedef std::vector<CString> TokenList;
173 TokenList tokens;
174
175 // tokenizing
176 char* p = subject;
177 char* start = p;
178 bool quot = false;
179 while (true)
180 {
181 char ch = *p;
182 bool sep = (ch == '\"') || (!quot && ch == ' ') || (ch == '\0');
183 if (sep)
184 {
185 // end of token
186 int len = (int)(p - start);
187 if (len > 0)
188 {
189 tokens.emplace_back(start, len);
190 }
191 start = p;
192 if (ch != '\"' || quot)
193 {
194 start++;
195 }
196 quot = *start == '\"';
197 if (quot)
198 {
199 start++;
200 char* q = strchr(start, '\"');
201 if (q)
202 {
203 p = q - 1;
204 }
205 else
206 {
207 quot = false;
208 }
209 }
210 }
211 if (ch == '\0')
212 {
213 break;
214 }
215 p++;
216 }
217
218 if (!tokens.empty())
219 {
220 // finding the best candidate for being a filename
221 char* besttoken = tokens.back();
222 for (TokenList::reverse_iterator it = tokens.rbegin(); it != tokens.rend(); it++)
223 {
224 char* s = *it;
225 char* p = strchr(s, '.');
226 if (p && (p[1] != '\0'))
227 {
228 besttoken = s;
229 break;
230 }
231 }
232 fileInfo->SetFilename(besttoken);
233 }
234 else
235 {
236 // subject is empty or contains only separators?
237 debug("Could not extract Filename from Subject: %s. Using Subject as Filename", fileInfo->GetSubject());
238 fileInfo->SetFilename(fileInfo->GetSubject());
239 }
240 }
241
HasDuplicateFilenames()242 bool NzbFile::HasDuplicateFilenames()
243 {
244 for (FileList::iterator it = m_nzbInfo->GetFileList()->begin(); it != m_nzbInfo->GetFileList()->end(); it++)
245 {
246 FileInfo* fileInfo1 = (*it).get();
247 int dupe = 1;
248 for (FileList::iterator it2 = it + 1; it2 != m_nzbInfo->GetFileList()->end(); it2++)
249 {
250 FileInfo* fileInfo2 = (*it2).get();
251 if (!strcmp(fileInfo1->GetFilename(), fileInfo2->GetFilename()) &&
252 strcmp(fileInfo1->GetSubject(), fileInfo2->GetSubject()))
253 {
254 dupe++;
255 }
256 }
257
258 // If more than two files have the same parsed filename but different subjects,
259 // this means, that the parsing was not correct.
260 // in this case we take subjects as filenames to prevent
261 // false "duplicate files"-alarm.
262 // It's Ok for just two files to have the same filename, this is
263 // an often case by posting-errors to repost bad files
264 if (dupe > 2 || (dupe == 2 && m_nzbInfo->GetFileList()->size() == 2))
265 {
266 return true;
267 }
268 }
269
270 return false;
271 }
272
273 /**
274 * Generate filenames from subjects and check if the parsing of subject was correct
275 */
BuildFilenames()276 void NzbFile::BuildFilenames()
277 {
278 for (FileInfo* fileInfo : m_nzbInfo->GetFileList())
279 {
280 ParseSubject(fileInfo, true);
281 }
282
283 if (HasDuplicateFilenames())
284 {
285 for (FileInfo* fileInfo : m_nzbInfo->GetFileList())
286 {
287 ParseSubject(fileInfo, false);
288 }
289 }
290
291 if (HasDuplicateFilenames())
292 {
293 m_nzbInfo->SetManyDupeFiles(true);
294 for (FileInfo* fileInfo : m_nzbInfo->GetFileList())
295 {
296 fileInfo->SetFilename(fileInfo->GetSubject());
297 }
298 }
299 }
300
CalcHashes()301 void NzbFile::CalcHashes()
302 {
303 RawFileList sortedFiles;
304
305 for (FileInfo* fileInfo : m_nzbInfo->GetFileList())
306 {
307 sortedFiles.push_back(fileInfo);
308 }
309
310 std::sort(sortedFiles.begin(), sortedFiles.end(),
311 [](FileInfo* first, FileInfo* second)
312 {
313 return strcmp(first->GetFilename(), second->GetFilename()) > 0;
314 });
315
316 uint32 fullContentHash = 0;
317 uint32 filteredContentHash = 0;
318 int useForFilteredCount = 0;
319
320 for (FileInfo* fileInfo : sortedFiles)
321 {
322 // check file extension
323 bool skip = !fileInfo->GetParFile() &&
324 Util::MatchFileExt(fileInfo->GetFilename(), g_Options->GetParIgnoreExt(), ",;");
325
326 for (ArticleInfo* article: fileInfo->GetArticles())
327 {
328 int len = strlen(article->GetMessageId());
329 fullContentHash = Util::HashBJ96(article->GetMessageId(), len, fullContentHash);
330 if (!skip)
331 {
332 filteredContentHash = Util::HashBJ96(article->GetMessageId(), len, filteredContentHash);
333 useForFilteredCount++;
334 }
335 }
336 }
337
338 // if filtered hash is based on less than a half of files - do not use filtered hash at all
339 if (useForFilteredCount < (int)sortedFiles.size() / 2)
340 {
341 filteredContentHash = 0;
342 }
343
344 m_nzbInfo->SetFullContentHash(fullContentHash);
345 m_nzbInfo->SetFilteredContentHash(filteredContentHash);
346 }
347
ProcessFiles()348 void NzbFile::ProcessFiles()
349 {
350 BuildFilenames();
351
352 for (FileInfo* fileInfo : m_nzbInfo->GetFileList())
353 {
354 fileInfo->MakeValidFilename();
355
356 BString<1024> loFileName = fileInfo->GetFilename();
357 for (char* p = loFileName; *p; p++) *p = tolower(*p); // convert string to lowercase
358 bool parFile = strstr(loFileName, ".par2");
359
360 m_nzbInfo->SetFileCount(m_nzbInfo->GetFileCount() + 1);
361 m_nzbInfo->SetTotalArticles(m_nzbInfo->GetTotalArticles() + fileInfo->GetTotalArticles());
362 m_nzbInfo->SetFailedArticles(m_nzbInfo->GetFailedArticles() + fileInfo->GetMissedArticles());
363 m_nzbInfo->SetCurrentFailedArticles(m_nzbInfo->GetCurrentFailedArticles() + fileInfo->GetMissedArticles());
364 m_nzbInfo->SetSize(m_nzbInfo->GetSize() + fileInfo->GetSize());
365 m_nzbInfo->SetRemainingSize(m_nzbInfo->GetRemainingSize() + fileInfo->GetRemainingSize());
366 m_nzbInfo->SetFailedSize(m_nzbInfo->GetFailedSize() + fileInfo->GetMissedSize());
367 m_nzbInfo->SetCurrentFailedSize(m_nzbInfo->GetFailedSize());
368
369 fileInfo->SetParFile(parFile);
370 if (parFile)
371 {
372 m_nzbInfo->SetParSize(m_nzbInfo->GetParSize() + fileInfo->GetSize());
373 m_nzbInfo->SetParFailedSize(m_nzbInfo->GetParFailedSize() + fileInfo->GetMissedSize());
374 m_nzbInfo->SetParCurrentFailedSize(m_nzbInfo->GetParFailedSize());
375 m_nzbInfo->SetRemainingParCount(m_nzbInfo->GetRemainingParCount() + 1);
376 }
377 }
378
379 m_nzbInfo->UpdateMinMaxTime();
380
381 CalcHashes();
382
383 if (g_Options->GetServerMode())
384 {
385 for (FileInfo* fileInfo : m_nzbInfo->GetFileList())
386 {
387 g_DiskState->SaveFile(fileInfo);
388 fileInfo->GetArticles()->clear();
389 }
390 }
391
392 if (m_password)
393 {
394 ReadPassword();
395 }
396 }
397
398 /**
399 * Password read using XML-parser may have special characters (such as TAB) stripped.
400 * This function rereads password directly from file to keep all characters intact.
401 */
ReadPassword()402 void NzbFile::ReadPassword()
403 {
404 DiskFile file;
405 if (!file.Open(m_fileName, DiskFile::omRead))
406 {
407 return;
408 }
409
410 // obtain file size.
411 file.Seek(0, DiskFile::soEnd);
412 int size = (int)file.Position();
413 file.Seek(0, DiskFile::soSet);
414
415 // reading first 4KB of the file
416
417 CharBuffer buf(4096);
418
419 size = size < 4096 ? size : 4096;
420
421 // copy the file into the buffer.
422 file.Read(buf, size);
423
424 file.Close();
425
426 buf[size-1] = '\0';
427
428 char* metaPassword = strstr(buf, "<meta type=\"password\">");
429 if (metaPassword)
430 {
431 metaPassword += 22; // length of '<meta type="password">'
432 char* end = strstr(metaPassword, "</meta>");
433 if (end)
434 {
435 *end = '\0';
436 WebUtil::XmlDecode(metaPassword);
437 m_password = metaPassword;
438 }
439 }
440 }
441
442 #ifdef WIN32
Parse()443 bool NzbFile::Parse()
444 {
445 CoInitialize(nullptr);
446
447 HRESULT hr;
448
449 MSXML::IXMLDOMDocumentPtr doc;
450 hr = doc.CreateInstance(MSXML::CLSID_DOMDocument);
451 if (FAILED(hr))
452 {
453 return false;
454 }
455
456 // Load the XML document file...
457 doc->put_resolveExternals(VARIANT_FALSE);
458 doc->put_validateOnParse(VARIANT_FALSE);
459 doc->put_async(VARIANT_FALSE);
460
461 _variant_t vFilename(*WString(*m_fileName));
462
463 // 1. first trying to load via filename without URL-encoding (certain charaters doesn't work when encoded)
464 VARIANT_BOOL success = doc->load(vFilename);
465 if (success == VARIANT_FALSE)
466 {
467 // 2. now trying filename encoded as URL
468 char url[2048];
469 EncodeUrl(m_fileName, url, 2048);
470 debug("url=\"%s\"", url);
471 _variant_t vUrl(url);
472
473 success = doc->load(vUrl);
474 }
475
476 if (success == VARIANT_FALSE)
477 {
478 _bstr_t r(doc->GetparseError()->reason);
479 const char* errMsg = r;
480 m_nzbInfo->AddMessage(Message::mkError, BString<1024>("Error parsing nzb-file %s: %s",
481 FileSystem::BaseFileName(m_fileName), errMsg));
482 return false;
483 }
484
485 if (!ParseNzb(doc))
486 {
487 return false;
488 }
489
490 if (m_nzbInfo->GetFileList()->empty())
491 {
492 m_nzbInfo->AddMessage(Message::mkError, BString<1024>(
493 "Error parsing nzb-file %s: file has no content", FileSystem::BaseFileName(m_fileName)));
494 return false;
495 }
496
497 ProcessFiles();
498
499 return true;
500 }
501
EncodeUrl(const char * filename,char * url,int bufLen)502 void NzbFile::EncodeUrl(const char* filename, char* url, int bufLen)
503 {
504 WString widefilename(filename);
505
506 char* end = url + bufLen;
507 for (wchar_t* p = widefilename; *p && url < end - 3; p++)
508 {
509 wchar_t ch = *p;
510 if (('0' <= ch && ch <= '9') ||
511 ('a' <= ch && ch <= 'z') ||
512 ('A' <= ch && ch <= 'Z') ||
513 ch == '-' || ch == '.' || ch == '_' || ch == '~')
514 {
515 *url++ = (char)ch;
516 }
517 else
518 {
519 *url++ = '%';
520 uint32 a = (uint32)ch >> 4;
521 *url++ = a > 9 ? a - 10 + 'A' : a + '0';
522 a = ch & 0xF;
523 *url++ = a > 9 ? a - 10 + 'A' : a + '0';
524 }
525 }
526 *url = '\0';
527 }
528
ParseNzb(IUnknown * nzb)529 bool NzbFile::ParseNzb(IUnknown* nzb)
530 {
531 MSXML::IXMLDOMDocumentPtr doc = nzb;
532 MSXML::IXMLDOMNodePtr root = doc->documentElement;
533
534 MSXML::IXMLDOMNodePtr node = root->selectSingleNode("/nzb/head/meta[@type='password']");
535 if (node)
536 {
537 _bstr_t password(node->Gettext());
538 m_password = password;
539 }
540
541 MSXML::IXMLDOMNodeListPtr fileList = root->selectNodes("/nzb/file");
542 for (int i = 0; i < fileList->Getlength(); i++)
543 {
544 node = fileList->Getitem(i);
545 MSXML::IXMLDOMNodePtr attribute = node->Getattributes()->getNamedItem("subject");
546 if (!attribute) return false;
547 _bstr_t subject(attribute->Gettext());
548
549 std::unique_ptr<FileInfo> fileInfo = std::make_unique<FileInfo>();
550 fileInfo->SetSubject(subject);
551
552 attribute = node->Getattributes()->getNamedItem("date");
553 if (attribute)
554 {
555 _bstr_t date(attribute->Gettext());
556 fileInfo->SetTime(atoi(date));
557 }
558
559 MSXML::IXMLDOMNodeListPtr groupList = node->selectNodes("groups/group");
560 for (int g = 0; g < groupList->Getlength(); g++)
561 {
562 MSXML::IXMLDOMNodePtr node = groupList->Getitem(g);
563 _bstr_t group = node->Gettext();
564 fileInfo->GetGroups()->push_back((const char*)group);
565 }
566
567 MSXML::IXMLDOMNodeListPtr segmentList = node->selectNodes("segments/segment");
568 for (int g = 0; g < segmentList->Getlength(); g++)
569 {
570 MSXML::IXMLDOMNodePtr node = segmentList->Getitem(g);
571 _bstr_t bid = node->Gettext();
572 BString<1024> id("<%s>", (const char*)bid);
573
574 MSXML::IXMLDOMNodePtr attribute = node->Getattributes()->getNamedItem("number");
575 if (!attribute) return false;
576 _bstr_t number(attribute->Gettext());
577
578 attribute = node->Getattributes()->getNamedItem("bytes");
579 if (!attribute) return false;
580 _bstr_t bytes(attribute->Gettext());
581
582 int partNumber = atoi(number);
583 int lsize = atoi(bytes);
584
585 if (partNumber > 0)
586 {
587 std::unique_ptr<ArticleInfo> article = std::make_unique<ArticleInfo>();
588 article->SetPartNumber(partNumber);
589 article->SetMessageId(id);
590 article->SetSize(lsize);
591 AddArticle(fileInfo.get(), std::move(article));
592 }
593 }
594
595 AddFileInfo(std::move(fileInfo));
596 }
597 return true;
598 }
599
600 #else
601
Parse()602 bool NzbFile::Parse()
603 {
604 #ifdef DISABLE_LIBXML2
605 error("Could not parse rss feed, program was compiled without libxml2 support");
606 return false;
607 #else
608 xmlSAXHandler SAX_handler = {0};
609 SAX_handler.startElement = reinterpret_cast<startElementSAXFunc>(SAX_StartElement);
610 SAX_handler.endElement = reinterpret_cast<endElementSAXFunc>(SAX_EndElement);
611 SAX_handler.characters = reinterpret_cast<charactersSAXFunc>(SAX_characters);
612 SAX_handler.error = reinterpret_cast<errorSAXFunc>(SAX_error);
613 SAX_handler.getEntity = reinterpret_cast<getEntitySAXFunc>(SAX_getEntity);
614
615 m_ignoreNextError = false;
616
617 int ret = xmlSAXUserParseFile(&SAX_handler, this, m_fileName);
618
619 if (ret != 0)
620 {
621 m_nzbInfo->AddMessage(Message::mkError, BString<1024>(
622 "Error parsing nzb-file %s", FileSystem::BaseFileName(m_fileName)));
623 return false;
624 }
625
626 if (m_nzbInfo->GetFileList()->empty())
627 {
628 m_nzbInfo->AddMessage(Message::mkError, BString<1024>(
629 "Error parsing nzb-file %s: file has no content", FileSystem::BaseFileName(m_fileName)));
630 return false;
631 }
632
633 ProcessFiles();
634
635 return true;
636 #endif
637 }
638
Parse_StartElement(const char * name,const char ** atts)639 void NzbFile::Parse_StartElement(const char *name, const char **atts)
640 {
641 BString<1024> tagAttrMessage("Malformed nzb-file, tag <%s> must have attributes", name);
642
643 m_tagContent.Clear();
644
645 if (!strcmp("file", name))
646 {
647 m_fileInfo = std::make_unique<FileInfo>();
648 m_fileInfo->SetFilename(m_fileName);
649
650 if (!atts)
651 {
652 m_nzbInfo->AddMessage(Message::mkWarning, tagAttrMessage);
653 return;
654 }
655
656 for (int i = 0; atts[i]; i += 2)
657 {
658 const char* attrname = atts[i];
659 const char* attrvalue = atts[i + 1];
660 if (!strcmp("subject", attrname))
661 {
662 m_fileInfo->SetSubject(attrvalue);
663 }
664 if (!strcmp("date", attrname))
665 {
666 m_fileInfo->SetTime(atoi(attrvalue));
667 }
668 }
669 }
670 else if (!strcmp("segment", name))
671 {
672 if (!m_fileInfo)
673 {
674 m_nzbInfo->AddMessage(Message::mkWarning, "Malformed nzb-file, tag <segment> without tag <file>");
675 return;
676 }
677
678 if (!atts)
679 {
680 m_nzbInfo->AddMessage(Message::mkWarning, tagAttrMessage);
681 return;
682 }
683
684 int64 lsize = -1;
685 int partNumber = -1;
686
687 for (int i = 0; atts[i]; i += 2)
688 {
689 const char* attrname = atts[i];
690 const char* attrvalue = atts[i + 1];
691 if (!strcmp("bytes", attrname))
692 {
693 lsize = atol(attrvalue);
694 }
695 if (!strcmp("number", attrname))
696 {
697 partNumber = atol(attrvalue);
698 }
699 }
700
701 if (partNumber > 0)
702 {
703 // new segment, add it!
704 std::unique_ptr<ArticleInfo> article = std::make_unique<ArticleInfo>();
705 article->SetPartNumber(partNumber);
706 article->SetSize(lsize);
707 m_article = article.get();
708 AddArticle(m_fileInfo.get(), std::move(article));
709 }
710 }
711 else if (!strcmp("meta", name))
712 {
713 if (!atts)
714 {
715 m_nzbInfo->AddMessage(Message::mkWarning, tagAttrMessage);
716 return;
717 }
718 m_hasPassword = atts[0] && atts[1] && !strcmp("type", atts[0]) && !strcmp("password", atts[1]);
719 }
720 }
721
Parse_EndElement(const char * name)722 void NzbFile::Parse_EndElement(const char *name)
723 {
724 if (!strcmp("file", name))
725 {
726 // Close the file element, add the new file to file-list
727 AddFileInfo(std::move(m_fileInfo));
728 m_article = nullptr;
729 }
730 else if (!strcmp("group", name))
731 {
732 if (!m_fileInfo)
733 {
734 // error: bad nzb-file
735 return;
736 }
737
738 m_fileInfo->GetGroups()->push_back(*m_tagContent);
739 m_tagContent.Clear();
740 }
741 else if (!strcmp("segment", name))
742 {
743 if (!m_fileInfo || !m_article)
744 {
745 // error: bad nzb-file
746 return;
747 }
748
749 // Get the #text part
750 BString<1024> id("<%s>", *m_tagContent);
751 m_article->SetMessageId(id);
752 m_article = nullptr;
753 }
754 else if (!strcmp("meta", name) && m_hasPassword)
755 {
756 m_password = m_tagContent;
757 }
758 }
759
Parse_Content(const char * buf,int len)760 void NzbFile::Parse_Content(const char *buf, int len)
761 {
762 m_tagContent.Append(buf, len);
763 }
764
SAX_StartElement(NzbFile * file,const char * name,const char ** atts)765 void NzbFile::SAX_StartElement(NzbFile* file, const char *name, const char **atts)
766 {
767 file->Parse_StartElement(name, atts);
768 }
769
SAX_EndElement(NzbFile * file,const char * name)770 void NzbFile::SAX_EndElement(NzbFile* file, const char *name)
771 {
772 file->Parse_EndElement(name);
773 }
774
SAX_characters(NzbFile * file,const char * xmlstr,int len)775 void NzbFile::SAX_characters(NzbFile* file, const char * xmlstr, int len)
776 {
777 char* str = (char*)xmlstr;
778
779 // trim starting blanks
780 int off = 0;
781 for (int i = 0; i < len; i++)
782 {
783 char ch = str[i];
784 if (ch == ' ' || ch == 10 || ch == 13 || ch == 9)
785 {
786 off++;
787 }
788 else
789 {
790 break;
791 }
792 }
793
794 int newlen = len - off;
795
796 // trim ending blanks
797 for (int i = len - 1; i >= off; i--)
798 {
799 char ch = str[i];
800 if (ch == ' ' || ch == 10 || ch == 13 || ch == 9)
801 {
802 newlen--;
803 }
804 else
805 {
806 break;
807 }
808 }
809
810 if (newlen > 0)
811 {
812 // interpret tag content
813 file->Parse_Content(str + off, newlen);
814 }
815 }
816
SAX_getEntity(NzbFile * file,const char * name)817 void* NzbFile::SAX_getEntity(NzbFile* file, const char * name)
818 {
819 #ifdef DISABLE_LIBXML2
820 void* e = nullptr;
821 #else
822 xmlEntityPtr e = xmlGetPredefinedEntity((xmlChar* )name);
823 #endif
824 if (!e)
825 {
826 file->m_nzbInfo->AddMessage(Message::mkWarning, "entity not found");
827 file->m_ignoreNextError = true;
828 }
829
830 return e;
831 }
832
SAX_error(NzbFile * file,const char * msg,...)833 void NzbFile::SAX_error(NzbFile* file, const char *msg, ...)
834 {
835 if (file->m_ignoreNextError)
836 {
837 file->m_ignoreNextError = false;
838 return;
839 }
840
841 va_list argp;
842 va_start(argp, msg);
843 char errMsg[1024];
844 vsnprintf(errMsg, sizeof(errMsg), msg, argp);
845 errMsg[1024-1] = '\0';
846 va_end(argp);
847
848 // remove trailing CRLF
849 for (char* pend = errMsg + strlen(errMsg) - 1; pend >= errMsg && (*pend == '\n' || *pend == '\r' || *pend == ' '); pend--) *pend = '\0';
850
851 file->m_nzbInfo->AddMessage(Message::mkError, BString<1024>("Error parsing nzb-file: %s", errMsg));
852 }
853 #endif
854