Chromium Code Reviews| Index: content/browser/download/mhtml_generation_manager.cc |
| diff --git a/content/browser/download/mhtml_generation_manager.cc b/content/browser/download/mhtml_generation_manager.cc |
| index 001d30d59f469fbfc9696f80015393a5d031b473..d6e9d7ca29c3575265eeb739de4ac5a85a11fe07 100644 |
| --- a/content/browser/download/mhtml_generation_manager.cc |
| +++ b/content/browser/download/mhtml_generation_manager.cc |
| @@ -18,6 +18,7 @@ |
| #include "base/stl_util.h" |
| #include "base/strings/string_util.h" |
| #include "base/strings/stringprintf.h" |
| +#include "base/strings/utf_string_conversion_utils.h" |
| #include "base/time/time.h" |
| #include "base/trace_event/trace_event.h" |
| #include "content/browser/bad_message.h" |
| @@ -38,6 +39,58 @@ namespace { |
| const char kContentLocation[] = "Content-Location: "; |
| const char kContentType[] = "Content-Type: "; |
| int kInvalidFileSize = -1; |
| + |
| +// This is used for compatibility with IE. |
| +bool IsASCIIPrintable(base::char16 c) { |
| + return c >= ' ' && c <= '~'; |
| +} |
| + |
| +constexpr int DAY_OF_WEEK_COUNT = 7; |
| +const char weekdays[DAY_OF_WEEK_COUNT][4] = {"Sun", "Mon", "Tue", "Wed", |
| + "Thu", "Fri", "Sat"}; |
| + |
| +constexpr int MONTH_COUNT = 12; |
| +const char months[MONTH_COUNT][4] = {"Jan", "Feb", "Mar", "Apr", "May", "Jun", |
| + "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"}; |
| + |
| +const char* GetDayOfWeek(int day_of_week) { |
| + DCHECK(day_of_week >= 0); |
| + DCHECK(day_of_week < DAY_OF_WEEK_COUNT); |
| + return weekdays[day_of_week]; |
| +} |
| + |
| +const char* GetMonthOfYear(int month_of_year) { |
| + DCHECK(month_of_year >= 0); |
| + DCHECK(month_of_year < MONTH_COUNT); |
| + return months[month_of_year]; |
| +} |
| + |
| +// See http://tools.ietf.org/html/rfc2822#section-3.3 for more information. |
| +std::string MakeRFC2822DateString(base::Time time) { |
| + base::Time::Exploded utc_exploded; |
| + time.UTCExplode(&utc_exploded); |
| + return base::StringPrintf( |
| + "Date: %3.3s, %.2u %3.3s %u %.2u:%.2u:%.2u GMT", |
| + GetDayOfWeek(utc_exploded.day_of_week), utc_exploded.day_of_month, |
| + // Month of year is 1-based. |
| + GetMonthOfYear(utc_exploded.month - 1), utc_exploded.year, |
| + utc_exploded.hour, utc_exploded.minute, utc_exploded.second); |
| +} |
| + |
| +static std::string ReplaceNonPrintableCharacters(const base::string16& text) { |
| + base::string16 quoted_text; |
| + base::string16 question_mark = base::ASCIIToUTF16("?"); |
| + |
| + for (size_t i = 0; i < text.length(); ++i) { |
| + base::char16 c = text[i]; |
| + if (IsASCIIPrintable(c)) |
| + quoted_text.append(1, c); |
| + else |
| + quoted_text.append(question_mark); |
| + } |
| + return base::UTF16ToUTF8(quoted_text); |
| +} |
| + |
| } // namespace |
| namespace content { |
| @@ -53,6 +106,7 @@ class MHTMLGenerationManager::Job : public RenderProcessHostObserver { |
| ~Job() override; |
| int id() const { return job_id_; } |
| + |
| void set_browser_file(base::File file) { browser_file_ = std::move(file); } |
| base::TimeTicks creation_time() const { return creation_time_; } |
| @@ -82,6 +136,9 @@ class MHTMLGenerationManager::Job : public RenderProcessHostObserver { |
| return !waiting_for_response_from_renderer && no_more_requests_to_send; |
| } |
| + // Generates the MHTML header. |
| + std::string CreateHeader(); |
| + |
| // Write the MHTML footer and close the file on the file thread and respond |
| // back on the UI thread with the updated status and file size (which will be |
| // negative in case of errors). |
| @@ -167,6 +224,14 @@ class MHTMLGenerationManager::Job : public RenderProcessHostObserver { |
| // MIME multipart boundary to use in the MHTML doc. |
| const std::string mhtml_boundary_marker_; |
| + // Title of the main page (extracted from the WebContents) for use in the |
| + // header. |
| + const base::string16 title_; |
| + |
| + // Mime type of the main page (extracted from the WebContents) for use in the |
| + // header. |
| + const std::string mime_type_; |
| + |
| // Digests of URIs of already generated MHTML parts. |
| std::set<std::string> digests_of_already_serialized_uris_; |
| std::string salt_; |
| @@ -198,6 +263,8 @@ MHTMLGenerationManager::Job::Job(int job_id, |
| params_(params), |
| frame_tree_node_id_of_busy_frame_(FrameTreeNode::kFrameTreeNodeInvalidId), |
| mhtml_boundary_marker_(net::GenerateMimeMultipartBoundary()), |
| + title_(web_contents->GetTitle()), |
| + mime_type_(web_contents->GetContentsMimeType()), |
| salt_(base::GenerateGUID()), |
| callback_(callback), |
| is_finished_(false), |
| @@ -505,6 +572,24 @@ bool MHTMLGenerationManager::Job::WriteExtraDataParts( |
| serialized_extra_data_parts.size()) >= 0); |
| } |
| +std::string MHTMLGenerationManager::Job::CreateHeader() { |
| + DCHECK(!mhtml_boundary_marker_.empty()); |
| + DCHECK(!mime_type_.empty()); |
| + |
| + std::vector<std::string> headers = { |
| + "From: <Saved by Blink>", |
| + base::StringPrintf("Subject: %s", |
| + ReplaceNonPrintableCharacters(title_).c_str()), |
| + MakeRFC2822DateString(base::Time::Now()), |
| + "Mime-Version: 1.0", |
| + "Content-Type: multipart/related;", |
| + base::StringPrintf("\ttype=\"%s\";", mime_type_.c_str()), |
| + base::StringPrintf("\tboundary=\"%s\";", mhtml_boundary_marker_.c_str()), |
| + "\r\n"}; |
| + |
| + return base::JoinString(headers, "\r\n"); |
| +} |
| + |
| // static |
| bool MHTMLGenerationManager::Job::WriteFooter(const std::string& boundary, |
| base::File& file) { |
| @@ -550,7 +635,8 @@ void MHTMLGenerationManager::SaveMHTML(WebContents* web_contents, |
| BrowserThread::PostTaskAndReplyWithResult( |
| BrowserThread::FILE, FROM_HERE, |
| - base::Bind(&MHTMLGenerationManager::CreateFile, params.file_path), |
| + base::Bind(&MHTMLGenerationManager::CreateFileAndWriteHeader, |
| + params.file_path, job->CreateHeader()), |
| base::Bind(&MHTMLGenerationManager::OnFileAvailable, |
| base::Unretained(this), // Safe b/c |this| is a singleton. |
| job->id())); |
| @@ -595,7 +681,10 @@ void MHTMLGenerationManager::OnSerializeAsMHTMLResponse( |
| } |
| // static |
| -base::File MHTMLGenerationManager::CreateFile(const base::FilePath& file_path) { |
| +MHTMLGenerationManager::FileCreationResult |
| +MHTMLGenerationManager::CreateFileAndWriteHeader( |
| + const base::FilePath& file_path, |
| + const std::string& header) { |
| DCHECK_CURRENTLY_ON(BrowserThread::FILE); |
| // SECURITY NOTE: A file descriptor to the file created below will be passed |
| @@ -605,29 +694,34 @@ base::File MHTMLGenerationManager::CreateFile(const base::FilePath& file_path) { |
| // would allow reading content generated by other renderers / other web |
| // principals). |
| uint32_t file_flags = base::File::FLAG_CREATE_ALWAYS | base::File::FLAG_WRITE; |
| - |
| - base::File browser_file(file_path, file_flags); |
| - if (!browser_file.IsValid()) { |
| - LOG(ERROR) << "Failed to create file to save MHTML at: " << |
| - file_path.value(); |
| + FileCreationResult result; |
| + result.file = base::File(file_path, file_flags); |
| + result.header_size = -1; |
|
carlosk
2017/04/25 01:28:08
Set default to -1 so not to have to re-set it here
|
| + if (!result.file.IsValid()) { |
| + LOG(ERROR) << "Failed to create file to save MHTML at: " |
| + << file_path.value(); |
| + return result; |
| } |
| - return browser_file; |
| + |
| + result.header_size = |
| + result.file.WriteAtCurrentPos(header.data(), header.size()); |
| + return result; |
|
carlosk
2017/04/25 01:28:07
Could you differentiate here between an error crea
|
| } |
| void MHTMLGenerationManager::OnFileAvailable(int job_id, |
| - base::File browser_file) { |
| + FileCreationResult result) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| Job* job = FindJob(job_id); |
| DCHECK(job); |
| - if (!browser_file.IsValid()) { |
| + if (!result.file.IsValid() || result.header_size < 0) { |
| LOG(ERROR) << "Failed to create file"; |
| JobFinished(job, MhtmlSaveStatus::FILE_CREATION_ERROR); |
| return; |
| } |
| - job->set_browser_file(std::move(browser_file)); |
| + job->set_browser_file(std::move(result.file)); |
| MhtmlSaveStatus save_status = job->SendToNextRenderFrame(); |
| if (save_status != MhtmlSaveStatus::SUCCESS) { |