1 /*
2  * Copyright (C) 2023 Huawei Device Co., Ltd.
3  * Licensed under the Apache License, Version 2.0 (the "License");
4  * you may not use this file except in compliance with the License.
5  * You may obtain a copy of the License at
6  *
7  *     http://www.apache.org/licenses/LICENSE-2.0
8  *
9  * Unless required by applicable law or agreed to in writing, software
10  * distributed under the License is distributed on an "AS IS" BASIS,
11  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12  * See the License for the specific language governing permissions and
13  * limitations under the License.
14  */
15 
16 #include <regex>
17 #include "file_uri.h"
18 #include "pasteboard_web_controller.h"
19 
20 namespace {
21 const std::string IMG_TAG_PATTERN = "<img.*?>";
22 const std::string IMG_TAG_SRC_PATTERN = "src=(['\"])(.*?)\\1";
23 const std::string IMG_TAG_SRC_HEAD = "src=\"";
24 const std::string IMG_LOCAL_URI = "file:///";
25 const std::string IMG_LOCAL_PATH = "://";
26 constexpr uint32_t FOUR_BYTES = 4;
27 constexpr uint32_t EIGHT_BIT = 8;
28 
29 struct Cmp {
operator ()__anon5f8adb9d0110::Cmp30     bool operator()(const uint32_t& lhs, const uint32_t& rhs) const
31     {
32         return lhs > rhs;
33     }
34 };
35 } // namespace
36 
37 namespace OHOS {
38 namespace MiscServices {
39 
40 // static
GetInstance()41 PasteboardWebController& PasteboardWebController::GetInstance()
42 {
43     static PasteboardWebController instance;
44     return instance;
45 }
46 
SplitHtml2Records(const std::shared_ptr<std::string> & html,uint32_t recordId)47 std::vector<std::shared_ptr<PasteDataRecord>> PasteboardWebController::SplitHtml2Records(
48     const std::shared_ptr<std::string> &html, uint32_t recordId) noexcept
49 {
50     std::vector<std::pair<std::string, uint32_t>> matchVec = SplitHtmlWithImgLabel(html);
51     PASTEBOARD_HILOGD(PASTEBOARD_MODULE_CLIENT, "matchVec size: %{public}zu", matchVec.size());
52     if (matchVec.empty()) {
53         return {};
54     }
55     std::map<std::string, std::vector<uint8_t>> imgSrcMap = SplitHtmlWithImgSrcLabel(matchVec);
56     PASTEBOARD_HILOGD(PASTEBOARD_MODULE_CLIENT, "imgSrcMap size: %{public}zu", imgSrcMap.size());
57     return BuildPasteDataRecords(imgSrcMap, recordId);
58 }
59 
MergeExtraUris2Html(PasteData & data)60 void PasteboardWebController::MergeExtraUris2Html(PasteData &data)
61 {
62     auto recordGroups = GroupRecordWithFrom(data);
63     PASTEBOARD_HILOGD(PASTEBOARD_MODULE_CLIENT, "recordGroups size: %{public}zu", recordGroups.size());
64     for (auto &recordGroup : recordGroups) {
65         ReplaceHtmlRecordContentByExtraUris(recordGroup.second);
66     }
67     RemoveExtraUris(data);
68 }
69 
RebuildHtml(std::shared_ptr<PasteData> pasteData)70 std::shared_ptr<std::string> PasteboardWebController::RebuildHtml(
71     std::shared_ptr<PasteData> pasteData) noexcept
72 {
73     std::vector<std::shared_ptr<PasteDataRecord>> pasteDataRecords = pasteData->AllRecords();
74     std::shared_ptr<std::string> htmlData;
75     std::map<uint32_t, std::pair<std::string, std::string>, Cmp> replaceUris;
76 
77     for (auto& item : pasteDataRecords) {
78         std::shared_ptr<std::string> html = item->GetHtmlText();
79         if (html) {
80             htmlData = html;
81         }
82         std::shared_ptr<OHOS::Uri> uri = item->GetUri();
83         std::shared_ptr<MiscServices::MineCustomData> customData = item->GetCustomData();
84         if (!uri || !customData) {
85             continue;
86         }
87         std::map<std::string, std::vector<uint8_t>> customItemData = customData->GetItemData();
88         for (auto& itemData : customItemData) {
89             for (uint32_t i = 0; i < itemData.second.size(); i += FOUR_BYTES) {
90                 uint32_t offset = static_cast<uint32_t>(itemData.second[i]) |
91                                   static_cast<uint32_t>(itemData.second[i + 1] << 8) |
92                                   static_cast<uint32_t>(itemData.second[i + 2] << 16) |
93                                   static_cast<uint32_t>(itemData.second[i + 3] << 24);
94                 replaceUris[offset] = std::make_pair(uri->ToString(), itemData.first);
95             }
96         }
97     }
98 
99     RemoveAllRecord(pasteData);
100     for (auto& replaceUri : replaceUris) {
101         htmlData->replace(replaceUri.first, replaceUri.second.second.size(), replaceUri.second.first);
102     }
103     pasteData->AddHtmlRecord(*htmlData);
104     return htmlData;
105 }
106 
SplitHtmlWithImgLabel(const std::shared_ptr<std::string> html)107 std::vector<std::pair<std::string, uint32_t>> PasteboardWebController::SplitHtmlWithImgLabel(
108     const std::shared_ptr<std::string> html) noexcept
109 {
110     std::smatch results;
111     std::string pattern(IMG_TAG_PATTERN);
112     std::regex r(pattern);
113     std::string::const_iterator iterStart = html->begin();
114     std::string::const_iterator iterEnd = html->end();
115     std::vector<std::pair<std::string, uint32_t>> matchVec;
116 
117     while (std::regex_search(iterStart, iterEnd, results, r)) {
118         std::string tmp = results[0];
119         iterStart = results[0].second;
120         uint32_t offset = static_cast<uint32_t>(results[0].first - html->begin());
121 
122         matchVec.emplace_back(tmp, offset);
123     }
124 
125     return matchVec;
126 }
127 
SplitHtmlWithImgSrcLabel(const std::vector<std::pair<std::string,uint32_t>> & matchVec)128 std::map<std::string, std::vector<uint8_t>> PasteboardWebController::SplitHtmlWithImgSrcLabel(
129     const std::vector<std::pair<std::string, uint32_t>>& matchVec) noexcept
130 {
131     std::map<std::string, std::vector<uint8_t>> res;
132     std::smatch match;
133     std::regex re(IMG_TAG_SRC_PATTERN);
134     for (auto& node : matchVec) {
135         std::string::const_iterator iterStart = node.first.begin();
136         std::string::const_iterator iterEnd = node.first.end();
137 
138         while (std::regex_search(iterStart, iterEnd, match, re)) {
139             std::string tmp = match[0];
140             iterStart = match[0].second;
141             uint32_t offset = static_cast<uint32_t>(match[0].first - node.first.begin());
142             tmp = tmp.substr(IMG_TAG_SRC_HEAD.size());
143             tmp.pop_back();
144             if (!IsLocalURI(tmp)) {
145                 continue;
146             }
147             offset += IMG_TAG_SRC_HEAD.size() + node.second;
148             for (uint32_t i = 0; i < FOUR_BYTES; i++) {
149                 res[tmp].emplace_back((offset >> (EIGHT_BIT * i)) & 0xff);
150             }
151         }
152     }
153     return res;
154 }
155 
BuildPasteDataRecords(const std::map<std::string,std::vector<uint8_t>> & imgSrcMap,uint32_t recordId)156 std::vector<std::shared_ptr<PasteDataRecord>> PasteboardWebController::BuildPasteDataRecords(
157     const std::map<std::string, std::vector<uint8_t>> &imgSrcMap, uint32_t recordId) noexcept
158 {
159     std::vector<std::shared_ptr<PasteDataRecord>> records;
160     for (auto &item : imgSrcMap) {
161         PasteDataRecord::Builder builder(MiscServices::MIMETYPE_TEXT_URI);
162         auto uri = std::make_shared<OHOS::Uri>(item.first);
163         builder.SetUri(uri);
164         auto customData = std::make_shared<MiscServices::MineCustomData>();
165 
166         customData->AddItemData(item.first, item.second);
167         builder.SetCustomData(customData);
168         auto record = builder.Build();
169         record->SetFrom(recordId);
170         records.push_back(record);
171     }
172     PASTEBOARD_HILOGD(PASTEBOARD_MODULE_CLIENT, "Build extra records size: %{public}zu", records.size());
173     return records;
174 }
175 
RemoveRecordById(PasteData & pasteData,uint32_t recordId)176 void PasteboardWebController::RemoveRecordById(PasteData &pasteData, uint32_t recordId) noexcept
177 {
178     for (uint32_t i = 0; i < pasteData.GetRecordCount(); i++) {
179         if (pasteData.GetRecordAt(i)->GetRecordId() == recordId) {
180             if (pasteData.RemoveRecordAt(i)) {
181                 PASTEBOARD_HILOGD(PASTEBOARD_MODULE_CLIENT,
182                     "WebClipboardController RemoveRecord success, i=%{public}u", i);
183                 return;
184             }
185             PASTEBOARD_HILOGW(PASTEBOARD_MODULE_CLIENT,
186                               "WebClipboardController RemoveRecord failed, i=%{public}u", i);
187         }
188     }
189 }
190 
RemoveAllRecord(std::shared_ptr<PasteData> pasteData)191 void PasteboardWebController::RemoveAllRecord(std::shared_ptr<PasteData> pasteData) noexcept
192 {
193     std::size_t recordCount = pasteData->GetRecordCount();
194     for (uint32_t i = 0; i < recordCount; i++) {
195         if (!pasteData->RemoveRecordAt(0)) {
196             PASTEBOARD_HILOGD(PASTEBOARD_MODULE_CLIENT, "WebClipboardController RemoveRecord failed, i=%{public}u", i);
197         }
198     }
199 }
200 
IsLocalURI(std::string & uri)201 bool PasteboardWebController::IsLocalURI(std::string& uri) noexcept
202 {
203     return uri.substr(0, IMG_LOCAL_URI.size()) == IMG_LOCAL_URI || uri.find(IMG_LOCAL_PATH) == std::string::npos;
204 }
205 
ReplaceHtmlRecordContentByExtraUris(std::vector<std::shared_ptr<PasteDataRecord>> & records)206 void PasteboardWebController::ReplaceHtmlRecordContentByExtraUris(
207     std::vector<std::shared_ptr<PasteDataRecord>> &records)
208 {
209     std::shared_ptr<PasteDataRecord> htmlRecord = nullptr;
210     std::shared_ptr<std::string> htmlData;
211     std::map<uint32_t, std::pair<std::string, std::string>, Cmp> replaceUris;
212     for (const auto &item : records) {
213         auto htmlEntry = item->GetEntryByMimeType(MIMETYPE_TEXT_HTML);
214         if (htmlEntry != nullptr) {
215             auto html = htmlEntry->ConvertToHtml();
216             if (html != nullptr && !html->empty()) {
217                 htmlData = html;
218                 htmlRecord = item;
219                 continue;
220             }
221         }
222         std::shared_ptr<OHOS::Uri> uri = item->GetUri();
223         std::shared_ptr<MiscServices::MineCustomData> customData = item->GetCustomData();
224         if (!uri || !customData) {
225             continue;
226         }
227         std::map<std::string, std::vector<uint8_t>> customItemData = customData->GetItemData();
228         for (auto &itemData : customItemData) {
229             for (uint32_t i = 0; i < itemData.second.size(); i += FOUR_BYTES) {
230                 uint32_t offset = static_cast<uint32_t>(itemData.second[i]) |
231                                   static_cast<uint32_t>(itemData.second[i + 1] << 8) |
232                                   static_cast<uint32_t>(itemData.second[i + 2] << 16) |
233                                   static_cast<uint32_t>(itemData.second[i + 3] << 24);
234                 replaceUris[offset] = std::make_pair(uri->ToString(), itemData.first);
235             }
236         }
237     }
238     if (htmlData == nullptr) {
239         PASTEBOARD_HILOGW(PASTEBOARD_MODULE_CLIENT, "htmlData is nullptr");
240         return;
241     }
242 
243     for (const auto &replaceUri : replaceUris) {
244         htmlData->replace(replaceUri.first, replaceUri.second.second.size(), replaceUri.second.first);
245     }
246     PASTEBOARD_HILOGD(PASTEBOARD_MODULE_CLIENT, "replace uri count: %{public}zu", replaceUris.size());
247     if (htmlRecord != nullptr) {
248         auto htmlUtdId = CommonUtils::Convert2UtdId(UDMF::UDType::UD_BUTT, MIMETYPE_TEXT_HTML);
249         auto newHtmlEntry = std::make_shared<PasteDataEntry>(htmlUtdId, *htmlData);
250         htmlRecord->AddEntryByMimeType(MIMETYPE_TEXT_HTML, newHtmlEntry);
251         htmlRecord->SetFrom(0);
252     }
253 }
254 
GroupRecordWithFrom(PasteData & data)255 std::map<std::uint32_t, std::vector<std::shared_ptr<PasteDataRecord>>> PasteboardWebController::GroupRecordWithFrom(
256     PasteData &data)
257 {
258     std::map<std::uint32_t, std::vector<std::shared_ptr<PasteDataRecord>>> groupMap;
259     for (const auto &record : data.AllRecords()) {
260         if (record->GetFrom() == 0) {
261             continue;
262         }
263         auto item = groupMap.find(record->GetFrom());
264         auto value = item != groupMap.end() ? item->second : std::vector<std::shared_ptr<PasteDataRecord>>();
265         value.emplace_back(record);
266         groupMap.insert_or_assign(record->GetFrom(), value);
267     }
268     return groupMap;
269 }
270 
RemoveExtraUris(PasteData & data)271 void PasteboardWebController::RemoveExtraUris(PasteData &data)
272 {
273     PASTEBOARD_HILOGD(PASTEBOARD_MODULE_CLIENT, "Before remove record count: %{public}zu", data.AllRecords().size());
274     for (const auto &record : data.AllRecords()) {
275         if (record->GetFrom() > 0 && record->GetMimeType() == MIMETYPE_TEXT_URI) {
276             RemoveRecordById(data, record->GetRecordId());
277         }
278     }
279     PASTEBOARD_HILOGD(PASTEBOARD_MODULE_CLIENT, "After remove record count: %{public}zu", data.AllRecords().size());
280 }
281 } // namespace MiscServices
282 } // namespace OHOS
283