OLD | NEW |
| (Empty) |
1 // Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. | |
2 // Use of this source code is governed by a BSD-style license that can be | |
3 // found in the LICENSE file. | |
4 | |
5 #include "base/clipboard_util.h" | |
6 | |
7 #include <shellapi.h> | |
8 #include <shlwapi.h> | |
9 #include <wininet.h> | |
10 | |
11 #include "base/basictypes.h" | |
12 #include "base/logging.h" | |
13 #include "base/scoped_handle.h" | |
14 #include "base/string_util.h" | |
15 | |
16 namespace { | |
17 | |
18 bool GetUrlFromHDrop(IDataObject* data_object, std::wstring* url, | |
19 std::wstring* title) { | |
20 DCHECK(data_object && url && title); | |
21 | |
22 STGMEDIUM medium; | |
23 if (FAILED(data_object->GetData(ClipboardUtil::GetCFHDropFormat(), &medium))) | |
24 return false; | |
25 | |
26 HDROP hdrop = static_cast<HDROP>(GlobalLock(medium.hGlobal)); | |
27 | |
28 if (!hdrop) | |
29 return false; | |
30 | |
31 bool success = false; | |
32 wchar_t filename[MAX_PATH]; | |
33 if (DragQueryFileW(hdrop, 0, filename, arraysize(filename))) { | |
34 wchar_t url_buffer[INTERNET_MAX_URL_LENGTH]; | |
35 if (0 == _wcsicmp(PathFindExtensionW(filename), L".url") && | |
36 GetPrivateProfileStringW(L"InternetShortcut", L"url", 0, url_buffer, | |
37 arraysize(url_buffer), filename)) { | |
38 *url = url_buffer; | |
39 PathRemoveExtension(filename); | |
40 title->assign(PathFindFileName(filename)); | |
41 success = true; | |
42 } | |
43 } | |
44 | |
45 DragFinish(hdrop); | |
46 GlobalUnlock(medium.hGlobal); | |
47 // We don't need to call ReleaseStgMedium here because as far as I can tell, | |
48 // DragFinish frees the hGlobal for us. | |
49 return success; | |
50 } | |
51 | |
52 bool SplitUrlAndTitle(const std::wstring& str, std::wstring* url, | |
53 std::wstring* title) { | |
54 DCHECK(url && title); | |
55 size_t newline_pos = str.find('\n'); | |
56 bool success = false; | |
57 if (newline_pos != std::string::npos) { | |
58 *url = str.substr(0, newline_pos); | |
59 title->assign(str.substr(newline_pos + 1)); | |
60 success = true; | |
61 } else { | |
62 *url = str; | |
63 title->assign(str); | |
64 success = true; | |
65 } | |
66 return success; | |
67 } | |
68 | |
69 } // namespace | |
70 | |
71 | |
72 FORMATETC* ClipboardUtil::GetUrlFormat() { | |
73 static UINT cf = RegisterClipboardFormat(CFSTR_INETURLA); | |
74 static FORMATETC format = {cf, 0, DVASPECT_CONTENT, -1, TYMED_HGLOBAL}; | |
75 return &format; | |
76 } | |
77 | |
78 FORMATETC* ClipboardUtil::GetUrlWFormat() { | |
79 static UINT cf = RegisterClipboardFormat(CFSTR_INETURLW); | |
80 static FORMATETC format = {cf, 0, DVASPECT_CONTENT, -1, TYMED_HGLOBAL}; | |
81 return &format; | |
82 } | |
83 | |
84 FORMATETC* ClipboardUtil::GetMozUrlFormat() { | |
85 // The format is "URL\nTitle" | |
86 static UINT cf = RegisterClipboardFormat(L"text/x-moz-url"); | |
87 static FORMATETC format = {cf, 0, DVASPECT_CONTENT, -1, TYMED_HGLOBAL}; | |
88 return &format; | |
89 } | |
90 | |
91 FORMATETC* ClipboardUtil::GetPlainTextFormat() { | |
92 // We don't need to register this format since it's a built in format. | |
93 static FORMATETC format = {CF_TEXT, 0, DVASPECT_CONTENT, -1, TYMED_HGLOBAL}; | |
94 return &format; | |
95 } | |
96 | |
97 FORMATETC* ClipboardUtil::GetPlainTextWFormat() { | |
98 // We don't need to register this format since it's a built in format. | |
99 static FORMATETC format = {CF_UNICODETEXT, 0, DVASPECT_CONTENT, -1, | |
100 TYMED_HGLOBAL}; | |
101 return &format; | |
102 } | |
103 | |
104 FORMATETC* ClipboardUtil::GetFilenameWFormat() { | |
105 static UINT cf = RegisterClipboardFormat(CFSTR_FILENAMEW); | |
106 static FORMATETC format = {cf, 0, DVASPECT_CONTENT, -1, TYMED_HGLOBAL}; | |
107 return &format; | |
108 } | |
109 | |
110 FORMATETC* ClipboardUtil::GetFilenameFormat() { | |
111 static UINT cf = RegisterClipboardFormat(CFSTR_FILENAMEA); | |
112 static FORMATETC format = {cf, 0, DVASPECT_CONTENT, -1, TYMED_HGLOBAL}; | |
113 return &format; | |
114 } | |
115 | |
116 FORMATETC* ClipboardUtil::GetHtmlFormat() { | |
117 static UINT cf = RegisterClipboardFormat(L"HTML Format"); | |
118 static FORMATETC format = {cf, 0, DVASPECT_CONTENT, -1, TYMED_HGLOBAL}; | |
119 return &format; | |
120 } | |
121 | |
122 FORMATETC* ClipboardUtil::GetTextHtmlFormat() { | |
123 static UINT cf = RegisterClipboardFormat(L"text/html"); | |
124 static FORMATETC format = {cf, 0, DVASPECT_CONTENT, -1, TYMED_HGLOBAL}; | |
125 return &format; | |
126 } | |
127 | |
128 FORMATETC* ClipboardUtil::GetCFHDropFormat() { | |
129 // We don't need to register this format since it's a built in format. | |
130 static FORMATETC format = {CF_HDROP, 0, DVASPECT_CONTENT, -1, | |
131 TYMED_HGLOBAL}; | |
132 return &format; | |
133 } | |
134 | |
135 FORMATETC* ClipboardUtil::GetFileDescriptorFormat() { | |
136 static UINT cf = RegisterClipboardFormat(CFSTR_FILEDESCRIPTOR); | |
137 static FORMATETC format = {cf, 0, DVASPECT_CONTENT, -1, TYMED_HGLOBAL}; | |
138 return &format; | |
139 } | |
140 | |
141 FORMATETC* ClipboardUtil::GetFileContentFormatZero() { | |
142 static UINT cf = RegisterClipboardFormat(CFSTR_FILECONTENTS); | |
143 static FORMATETC format = {cf, 0, DVASPECT_CONTENT, 0, TYMED_HGLOBAL}; | |
144 return &format; | |
145 } | |
146 | |
147 FORMATETC* ClipboardUtil::GetWebKitSmartPasteFormat() { | |
148 static UINT cf = RegisterClipboardFormat(L"WebKit Smart Paste Format"); | |
149 static FORMATETC format = {cf, 0, DVASPECT_CONTENT, 0, TYMED_HGLOBAL}; | |
150 return &format; | |
151 } | |
152 | |
153 | |
154 bool ClipboardUtil::HasUrl(IDataObject* data_object) { | |
155 DCHECK(data_object); | |
156 return SUCCEEDED(data_object->QueryGetData(GetMozUrlFormat())) || | |
157 SUCCEEDED(data_object->QueryGetData(GetUrlWFormat())) || | |
158 SUCCEEDED(data_object->QueryGetData(GetUrlFormat())) || | |
159 SUCCEEDED(data_object->QueryGetData(GetFilenameWFormat())) || | |
160 SUCCEEDED(data_object->QueryGetData(GetFilenameFormat())); | |
161 } | |
162 | |
163 bool ClipboardUtil::HasFilenames(IDataObject* data_object) { | |
164 DCHECK(data_object); | |
165 return SUCCEEDED(data_object->QueryGetData(GetCFHDropFormat())); | |
166 } | |
167 | |
168 bool ClipboardUtil::HasFileContents(IDataObject* data_object) { | |
169 DCHECK(data_object); | |
170 return SUCCEEDED(data_object->QueryGetData(GetFileContentFormatZero())); | |
171 } | |
172 | |
173 bool ClipboardUtil::HasHtml(IDataObject* data_object) { | |
174 DCHECK(data_object); | |
175 return SUCCEEDED(data_object->QueryGetData(GetHtmlFormat())) || | |
176 SUCCEEDED(data_object->QueryGetData(GetTextHtmlFormat())); | |
177 } | |
178 | |
179 bool ClipboardUtil::HasPlainText(IDataObject* data_object) { | |
180 DCHECK(data_object); | |
181 return SUCCEEDED(data_object->QueryGetData(GetPlainTextWFormat())) || | |
182 SUCCEEDED(data_object->QueryGetData(GetPlainTextFormat())); | |
183 } | |
184 | |
185 | |
186 bool ClipboardUtil::GetUrl(IDataObject* data_object, | |
187 std::wstring* url, std::wstring* title) { | |
188 DCHECK(data_object && url && title); | |
189 if (!HasUrl(data_object)) | |
190 return false; | |
191 | |
192 // Try to extract a URL from |data_object| in a variety of formats. | |
193 STGMEDIUM store; | |
194 if (GetUrlFromHDrop(data_object, url, title)) { | |
195 return true; | |
196 } | |
197 | |
198 if (SUCCEEDED(data_object->GetData(GetMozUrlFormat(), &store)) || | |
199 SUCCEEDED(data_object->GetData(GetUrlWFormat(), &store))) { | |
200 // Mozilla URL format or unicode URL | |
201 ScopedHGlobal<wchar_t> data(store.hGlobal); | |
202 bool success = SplitUrlAndTitle(data.get(), url, title); | |
203 ReleaseStgMedium(&store); | |
204 if (success) | |
205 return true; | |
206 } | |
207 | |
208 if (SUCCEEDED(data_object->GetData(GetUrlFormat(), &store))) { | |
209 // URL using ascii | |
210 ScopedHGlobal<char> data(store.hGlobal); | |
211 bool success = SplitUrlAndTitle(UTF8ToWide(data.get()), url, title); | |
212 ReleaseStgMedium(&store); | |
213 if (success) | |
214 return true; | |
215 } | |
216 | |
217 if (SUCCEEDED(data_object->GetData(GetFilenameWFormat(), &store))) { | |
218 // filename using unicode | |
219 ScopedHGlobal<wchar_t> data(store.hGlobal); | |
220 bool success = false; | |
221 if (data.get() && data.get()[0] && (PathFileExists(data.get()) || | |
222 PathIsUNC(data.get()))) { | |
223 wchar_t file_url[INTERNET_MAX_URL_LENGTH]; | |
224 DWORD file_url_len = sizeof(file_url) / sizeof(file_url[0]); | |
225 if (SUCCEEDED(::UrlCreateFromPathW(data.get(), file_url, &file_url_len, | |
226 0))) { | |
227 *url = file_url; | |
228 title->assign(file_url); | |
229 success = true; | |
230 } | |
231 } | |
232 ReleaseStgMedium(&store); | |
233 if (success) | |
234 return true; | |
235 } | |
236 | |
237 if (SUCCEEDED(data_object->GetData(GetFilenameFormat(), &store))) { | |
238 // filename using ascii | |
239 ScopedHGlobal<char> data(store.hGlobal); | |
240 bool success = false; | |
241 if (data.get() && data.get()[0] && (PathFileExistsA(data.get()) || | |
242 PathIsUNCA(data.get()))) { | |
243 char file_url[INTERNET_MAX_URL_LENGTH]; | |
244 DWORD file_url_len = sizeof(file_url) / sizeof(file_url[0]); | |
245 if (SUCCEEDED(::UrlCreateFromPathA(data.get(), | |
246 file_url, | |
247 &file_url_len, | |
248 0))) { | |
249 *url = UTF8ToWide(file_url); | |
250 title->assign(*url); | |
251 success = true; | |
252 } | |
253 } | |
254 ReleaseStgMedium(&store); | |
255 if (success) | |
256 return true; | |
257 } | |
258 | |
259 return false; | |
260 } | |
261 | |
262 bool ClipboardUtil::GetFilenames(IDataObject* data_object, | |
263 std::vector<std::wstring>* filenames) { | |
264 DCHECK(data_object && filenames); | |
265 if (!HasFilenames(data_object)) | |
266 return false; | |
267 | |
268 STGMEDIUM medium; | |
269 if (FAILED(data_object->GetData(GetCFHDropFormat(), &medium))) | |
270 return false; | |
271 | |
272 HDROP hdrop = static_cast<HDROP>(GlobalLock(medium.hGlobal)); | |
273 if (!hdrop) | |
274 return false; | |
275 | |
276 const int kMaxFilenameLen = 4096; | |
277 const unsigned num_files = DragQueryFileW(hdrop, 0xffffffff, 0, 0); | |
278 for (unsigned int i = 0; i < num_files; ++i) { | |
279 wchar_t filename[kMaxFilenameLen]; | |
280 if (!DragQueryFileW(hdrop, i, filename, kMaxFilenameLen)) | |
281 continue; | |
282 filenames->push_back(filename); | |
283 } | |
284 | |
285 DragFinish(hdrop); | |
286 GlobalUnlock(medium.hGlobal); | |
287 // We don't need to call ReleaseStgMedium here because as far as I can tell, | |
288 // DragFinish frees the hGlobal for us. | |
289 return true; | |
290 } | |
291 | |
292 bool ClipboardUtil::GetPlainText(IDataObject* data_object, | |
293 std::wstring* plain_text) { | |
294 DCHECK(data_object && plain_text); | |
295 if (!HasPlainText(data_object)) | |
296 return false; | |
297 | |
298 STGMEDIUM store; | |
299 bool success = false; | |
300 if (SUCCEEDED(data_object->GetData(GetPlainTextWFormat(), &store))) { | |
301 // Unicode text | |
302 ScopedHGlobal<wchar_t> data(store.hGlobal); | |
303 plain_text->assign(data.get()); | |
304 ReleaseStgMedium(&store); | |
305 success = true; | |
306 } else if (SUCCEEDED(data_object->GetData(GetPlainTextFormat(), &store))) { | |
307 // ascii text | |
308 ScopedHGlobal<char> data(store.hGlobal); | |
309 plain_text->assign(UTF8ToWide(data.get())); | |
310 ReleaseStgMedium(&store); | |
311 success = true; | |
312 } else { | |
313 // If a file is dropped on the window, it does not provide either of the | |
314 // plain text formats, so here we try to forcibly get a url. | |
315 std::wstring title; | |
316 success = GetUrl(data_object, plain_text, &title); | |
317 } | |
318 | |
319 return success; | |
320 } | |
321 | |
322 bool ClipboardUtil::GetHtml(IDataObject* data_object, | |
323 std::wstring* html, std::string* base_url) { | |
324 DCHECK(data_object && html && base_url); | |
325 | |
326 if (SUCCEEDED(data_object->QueryGetData(GetHtmlFormat()))) { | |
327 STGMEDIUM store; | |
328 if (SUCCEEDED(data_object->GetData(GetHtmlFormat(), &store))) { | |
329 // MS CF html | |
330 ScopedHGlobal<char> data(store.hGlobal); | |
331 | |
332 std::string html_utf8; | |
333 CFHtmlToHtml(std::string(data.get(), data.Size()), &html_utf8, base_url); | |
334 html->assign(UTF8ToWide(html_utf8)); | |
335 | |
336 ReleaseStgMedium(&store); | |
337 return true; | |
338 } | |
339 } | |
340 | |
341 if (FAILED(data_object->QueryGetData(GetTextHtmlFormat()))) | |
342 return false; | |
343 | |
344 STGMEDIUM store; | |
345 if (FAILED(data_object->GetData(GetTextHtmlFormat(), &store))) | |
346 return false; | |
347 | |
348 // text/html | |
349 ScopedHGlobal<wchar_t> data(store.hGlobal); | |
350 html->assign(data.get()); | |
351 ReleaseStgMedium(&store); | |
352 return true; | |
353 } | |
354 | |
355 bool ClipboardUtil::GetFileContents(IDataObject* data_object, | |
356 std::wstring* filename, std::string* file_contents) { | |
357 DCHECK(data_object && filename && file_contents); | |
358 bool has_data = | |
359 SUCCEEDED(data_object->QueryGetData(GetFileContentFormatZero())) || | |
360 SUCCEEDED(data_object->QueryGetData(GetFileDescriptorFormat())); | |
361 | |
362 if (!has_data) | |
363 return false; | |
364 | |
365 STGMEDIUM content; | |
366 // The call to GetData can be very slow depending on what is in | |
367 // |data_object|. | |
368 if (SUCCEEDED(data_object->GetData(GetFileContentFormatZero(), &content))) { | |
369 if (TYMED_HGLOBAL == content.tymed) { | |
370 ScopedHGlobal<char> data(content.hGlobal); | |
371 file_contents->assign(data.get(), data.Size()); | |
372 } | |
373 ReleaseStgMedium(&content); | |
374 } | |
375 | |
376 STGMEDIUM description; | |
377 if (SUCCEEDED(data_object->GetData(GetFileDescriptorFormat(), | |
378 &description))) { | |
379 ScopedHGlobal<FILEGROUPDESCRIPTOR> fgd(description.hGlobal); | |
380 // We expect there to be at least one file in here. | |
381 DCHECK_GE(fgd->cItems, 1); | |
382 filename->assign(fgd->fgd[0].cFileName); | |
383 ReleaseStgMedium(&description); | |
384 } | |
385 return true; | |
386 } | |
387 | |
388 // HtmlToCFHtml and CFHtmlToHtml are based on similar methods in | |
389 // WebCore/platform/win/ClipboardUtilitiesWin.cpp. | |
390 /* | |
391 * Copyright (C) 2007, 2008 Apple Inc. All rights reserved. | |
392 * | |
393 * Redistribution and use in source and binary forms, with or without | |
394 * modification, are permitted provided that the following conditions | |
395 * are met: | |
396 * 1. Redistributions of source code must retain the above copyright | |
397 * notice, this list of conditions and the following disclaimer. | |
398 * 2. Redistributions in binary form must reproduce the above copyright | |
399 * notice, this list of conditions and the following disclaimer in the | |
400 * documentation and/or other materials provided with the distribution. | |
401 * | |
402 * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY | |
403 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | |
404 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR | |
405 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR | |
406 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, | |
407 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, | |
408 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR | |
409 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY | |
410 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
411 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
412 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
413 */ | |
414 | |
415 // Helper method for converting from text/html to MS CF_HTML. | |
416 // Documentation for the CF_HTML format is available at | |
417 // http://msdn.microsoft.com/en-us/library/aa767917(VS.85).aspx | |
418 std::string ClipboardUtil::HtmlToCFHtml(const std::string& html, | |
419 const std::string& base_url) { | |
420 if (html.empty()) | |
421 return std::string(); | |
422 | |
423 #define MAX_DIGITS 10 | |
424 #define MAKE_NUMBER_FORMAT_1(digits) MAKE_NUMBER_FORMAT_2(digits) | |
425 #define MAKE_NUMBER_FORMAT_2(digits) "%0" #digits "u" | |
426 #define NUMBER_FORMAT MAKE_NUMBER_FORMAT_1(MAX_DIGITS) | |
427 | |
428 static const char* header = "Version:0.9\r\n" | |
429 "StartHTML:" NUMBER_FORMAT "\r\n" | |
430 "EndHTML:" NUMBER_FORMAT "\r\n" | |
431 "StartFragment:" NUMBER_FORMAT "\r\n" | |
432 "EndFragment:" NUMBER_FORMAT "\r\n"; | |
433 static const char* source_url_prefix = "SourceURL:"; | |
434 | |
435 static const char* start_markup = | |
436 "<html>\r\n<body>\r\n<!--StartFragment-->\r\n"; | |
437 static const char* end_markup = | |
438 "\r\n<!--EndFragment-->\r\n</body>\r\n</html>"; | |
439 | |
440 // Calculate offsets | |
441 size_t start_html_offset = strlen(header) - strlen(NUMBER_FORMAT) * 4 + | |
442 MAX_DIGITS * 4; | |
443 if (!base_url.empty()) { | |
444 start_html_offset += strlen(source_url_prefix) + | |
445 base_url.length() + 2; // Add 2 for \r\n. | |
446 } | |
447 size_t start_fragment_offset = start_html_offset + strlen(start_markup); | |
448 size_t end_fragment_offset = start_fragment_offset + html.length(); | |
449 size_t end_html_offset = end_fragment_offset + strlen(end_markup); | |
450 | |
451 std::string result = StringPrintf(header, start_html_offset, | |
452 end_html_offset, start_fragment_offset, end_fragment_offset); | |
453 if (!base_url.empty()) { | |
454 result.append(source_url_prefix); | |
455 result.append(base_url); | |
456 result.append("\r\n"); | |
457 } | |
458 result.append(start_markup); | |
459 result.append(html); | |
460 result.append(end_markup); | |
461 | |
462 #undef MAX_DIGITS | |
463 #undef MAKE_NUMBER_FORMAT_1 | |
464 #undef MAKE_NUMBER_FORMAT_2 | |
465 #undef NUMBER_FORMAT | |
466 | |
467 return result; | |
468 } | |
469 | |
470 // Helper method for converting from MS CF_HTML to text/html. | |
471 void ClipboardUtil::CFHtmlToHtml(const std::string& cf_html, | |
472 std::string* html, | |
473 std::string* base_url) { | |
474 // Obtain base_url if present. | |
475 if (base_url) { | |
476 static std::string src_url_str("SourceURL:"); | |
477 size_t line_start = cf_html.find(src_url_str); | |
478 if (line_start != std::string::npos) { | |
479 size_t src_end = cf_html.find("\n", line_start); | |
480 size_t src_start = line_start + src_url_str.length(); | |
481 if (src_end != std::string::npos && src_start != std::string::npos) { | |
482 *base_url = cf_html.substr(src_start, src_end - src_start); | |
483 TrimWhitespace(*base_url, TRIM_ALL, base_url); | |
484 } | |
485 } | |
486 } | |
487 | |
488 // Find the markup between "<!--StartFragment -->" and "<!--EndFragment-->". | |
489 if (html) { | |
490 std::string cf_html_lower = StringToLowerASCII(cf_html); | |
491 size_t markup_start = cf_html_lower.find("<html", 0); | |
492 size_t tag_start = cf_html.find("StartFragment", markup_start); | |
493 size_t fragment_start = cf_html.find('>', tag_start) + 1; | |
494 size_t tag_end = cf_html.rfind("EndFragment", std::string::npos); | |
495 size_t fragment_end = cf_html.rfind('<', tag_end); | |
496 if (fragment_start != std::string::npos && | |
497 fragment_end != std::string::npos) { | |
498 *html = cf_html.substr(fragment_start, fragment_end - fragment_start); | |
499 TrimWhitespace(*html, TRIM_ALL, html); | |
500 } | |
501 } | |
502 } | |
OLD | NEW |