1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim:expandtab:shiftwidth=4:tabstop=4:
3  */
4 /* This Source Code Form is subject to the terms of the Mozilla Public
5  * License, v. 2.0. If a copy of the MPL was not distributed with this
6  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 
8 #include "mozilla/ArrayUtils.h"
9 
10 #include "nsArrayUtils.h"
11 #include "nsClipboard.h"
12 #include "nsClipboardX11.h"
13 #if defined(MOZ_WAYLAND)
14 #  include "nsClipboardWayland.h"
15 #endif
16 #include "nsContentUtils.h"
17 #include "HeadlessClipboard.h"
18 #include "nsSupportsPrimitives.h"
19 #include "nsString.h"
20 #include "nsReadableUtils.h"
21 #include "nsPrimitiveHelpers.h"
22 #include "nsImageToPixbuf.h"
23 #include "nsStringStream.h"
24 #include "nsIFileURL.h"
25 #include "nsIObserverService.h"
26 #include "mozilla/Services.h"
27 #include "mozilla/RefPtr.h"
28 #include "mozilla/SchedulerGroup.h"
29 #include "mozilla/TimeStamp.h"
30 #include "WidgetUtilsGtk.h"
31 
32 #include "imgIContainer.h"
33 
34 #include <gtk/gtk.h>
35 #include <gtk/gtkx.h>
36 
37 #include "mozilla/Encoding.h"
38 
39 using namespace mozilla;
40 
41 // Idle timeout for receiving selection and property notify events (microsec)
42 const int kClipboardTimeout = 500000;
43 
44 // We add this prefix to HTML markup, so that GetHTMLCharset can correctly
45 // detect the HTML as UTF-8 encoded.
46 static const char kHTMLMarkupPrefix[] =
47     R"(<meta http-equiv="content-type" content="text/html; charset=utf-8">)";
48 
49 static const char kURIListMime[] = "text/uri-list";
50 
51 // Callback when someone asks us for the data
52 void clipboard_get_cb(GtkClipboard* aGtkClipboard,
53                       GtkSelectionData* aSelectionData, guint info,
54                       gpointer user_data);
55 
56 // Callback when someone asks us to clear a clipboard
57 void clipboard_clear_cb(GtkClipboard* aGtkClipboard, gpointer user_data);
58 
59 static bool ConvertHTMLtoUCS2(const char* data, int32_t dataLength,
60                               nsCString& charset, char16_t** unicodeData,
61                               int32_t& outUnicodeLen);
62 
63 static bool GetHTMLCharset(const char* data, int32_t dataLength,
64                            nsCString& str);
65 
GetSelectionAtom(int32_t aWhichClipboard)66 GdkAtom GetSelectionAtom(int32_t aWhichClipboard) {
67   if (aWhichClipboard == nsIClipboard::kGlobalClipboard)
68     return GDK_SELECTION_CLIPBOARD;
69 
70   return GDK_SELECTION_PRIMARY;
71 }
72 
GetGeckoClipboardType(GtkClipboard * aGtkClipboard)73 int GetGeckoClipboardType(GtkClipboard* aGtkClipboard) {
74   if (aGtkClipboard == gtk_clipboard_get(GDK_SELECTION_PRIMARY))
75     return nsClipboard::kSelectionClipboard;
76   else if (aGtkClipboard == gtk_clipboard_get(GDK_SELECTION_CLIPBOARD))
77     return nsClipboard::kGlobalClipboard;
78 
79   return -1;  // THAT AIN'T NO CLIPBOARD I EVER HEARD OF
80 }
81 
82 nsClipboard::nsClipboard() = default;
83 
~nsClipboard()84 nsClipboard::~nsClipboard() {
85   // We have to clear clipboard before gdk_display_close() call.
86   // See bug 531580 for details.
87   if (mGlobalTransferable) {
88     gtk_clipboard_clear(gtk_clipboard_get(GDK_SELECTION_CLIPBOARD));
89   }
90   if (mSelectionTransferable) {
91     gtk_clipboard_clear(gtk_clipboard_get(GDK_SELECTION_PRIMARY));
92   }
93 }
94 
NS_IMPL_ISUPPORTS(nsClipboard,nsIClipboard,nsIObserver)95 NS_IMPL_ISUPPORTS(nsClipboard, nsIClipboard, nsIObserver)
96 
97 nsresult nsClipboard::Init(void) {
98   if (widget::GdkIsX11Display()) {
99     mContext = MakeUnique<nsRetrievalContextX11>();
100 #if defined(MOZ_WAYLAND)
101   } else if (widget::GdkIsWaylandDisplay()) {
102     mContext = MakeUnique<nsRetrievalContextWayland>();
103 #endif
104   } else {
105     NS_WARNING("Missing nsRetrievalContext for nsClipboard!");
106     return NS_OK;
107   }
108 
109   nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
110   if (os) {
111     os->AddObserver(this, "xpcom-shutdown", false);
112   }
113 
114   return NS_OK;
115 }
116 
117 NS_IMETHODIMP
Observe(nsISupports * aSubject,const char * aTopic,const char16_t * aData)118 nsClipboard::Observe(nsISupports* aSubject, const char* aTopic,
119                      const char16_t* aData) {
120   // Save global clipboard content to CLIPBOARD_MANAGER.
121   // gtk_clipboard_store() can run an event loop, so call from a dedicated
122   // runnable.
123   return SchedulerGroup::Dispatch(
124       TaskCategory::Other,
125       NS_NewRunnableFunction("gtk_clipboard_store()", []() {
126         LOGCLIP(("nsClipboard storing clipboard content\n"));
127         gtk_clipboard_store(gtk_clipboard_get(GDK_SELECTION_CLIPBOARD));
128       }));
129 }
130 
131 NS_IMETHODIMP
SetData(nsITransferable * aTransferable,nsIClipboardOwner * aOwner,int32_t aWhichClipboard)132 nsClipboard::SetData(nsITransferable* aTransferable, nsIClipboardOwner* aOwner,
133                      int32_t aWhichClipboard) {
134   // See if we can short cut
135   if ((aWhichClipboard == kGlobalClipboard &&
136        aTransferable == mGlobalTransferable.get() &&
137        aOwner == mGlobalOwner.get()) ||
138       (aWhichClipboard == kSelectionClipboard &&
139        aTransferable == mSelectionTransferable.get() &&
140        aOwner == mSelectionOwner.get())) {
141     return NS_OK;
142   }
143 
144   LOGCLIP(("nsClipboard::SetData (%s)\n",
145            aWhichClipboard == kSelectionClipboard ? "primary" : "clipboard"));
146 
147   // List of suported targets
148   GtkTargetList* list = gtk_target_list_new(nullptr, 0);
149 
150   // Get the types of supported flavors
151   nsTArray<nsCString> flavors;
152   nsresult rv = aTransferable->FlavorsTransferableCanExport(flavors);
153   if (NS_FAILED(rv)) {
154     LOGCLIP(("    FlavorsTransferableCanExport failed!\n"));
155     // Fall through.  |gtkTargets| will be null below.
156   }
157 
158   // Add all the flavors to this widget's supported type.
159   bool imagesAdded = false;
160   for (uint32_t i = 0; i < flavors.Length(); i++) {
161     nsCString& flavorStr = flavors[i];
162     LOGCLIP(("    processing target %s\n", flavorStr.get()));
163 
164     // Special case text/unicode since we can handle all of the string types.
165     if (flavorStr.EqualsLiteral(kUnicodeMime)) {
166       LOGCLIP(("    adding TEXT targets\n"));
167       gtk_target_list_add_text_targets(list, 0);
168       continue;
169     }
170 
171     if (nsContentUtils::IsFlavorImage(flavorStr)) {
172       // Don't bother adding image targets twice
173       if (!imagesAdded) {
174         // accept any writable image type
175         LOGCLIP(("    adding IMAGE targets\n"));
176         gtk_target_list_add_image_targets(list, 0, TRUE);
177         imagesAdded = true;
178       }
179       continue;
180     }
181 
182     // Add this to our list of valid targets
183     LOGCLIP(("    adding OTHER target %s\n", flavorStr.get()));
184     GdkAtom atom = gdk_atom_intern(flavorStr.get(), FALSE);
185     gtk_target_list_add(list, atom, 0, 0);
186   }
187 
188   // Get GTK clipboard (CLIPBOARD or PRIMARY)
189   GtkClipboard* gtkClipboard =
190       gtk_clipboard_get(GetSelectionAtom(aWhichClipboard));
191 
192   gint numTargets;
193   GtkTargetEntry* gtkTargets =
194       gtk_target_table_new_from_list(list, &numTargets);
195   if (!gtkTargets) {
196     LOGCLIP(("    gtk_clipboard_set_with_data() failed!\n"));
197     // Clear references to the any old data and let GTK know that it is no
198     // longer available.
199     EmptyClipboard(aWhichClipboard);
200     return NS_ERROR_FAILURE;
201   }
202 
203   // Set getcallback and request to store data after an application exit
204   if (gtk_clipboard_set_with_data(gtkClipboard, gtkTargets, numTargets,
205                                   clipboard_get_cb, clipboard_clear_cb, this)) {
206     // We managed to set-up the clipboard so update internal state
207     // We have to set it now because gtk_clipboard_set_with_data() calls
208     // clipboard_clear_cb() which reset our internal state
209     if (aWhichClipboard == kSelectionClipboard) {
210       mSelectionOwner = aOwner;
211       mSelectionTransferable = aTransferable;
212     } else {
213       mGlobalOwner = aOwner;
214       mGlobalTransferable = aTransferable;
215       gtk_clipboard_set_can_store(gtkClipboard, gtkTargets, numTargets);
216     }
217 
218     rv = NS_OK;
219   } else {
220     LOGCLIP(("    gtk_clipboard_set_with_data() failed!\n"));
221     EmptyClipboard(aWhichClipboard);
222     rv = NS_ERROR_FAILURE;
223   }
224 
225   gtk_target_table_free(gtkTargets, numTargets);
226   gtk_target_list_unref(list);
227 
228   return rv;
229 }
230 
SetTransferableData(nsITransferable * aTransferable,nsCString & aFlavor,const char * aClipboardData,uint32_t aClipboardDataLength)231 void nsClipboard::SetTransferableData(nsITransferable* aTransferable,
232                                       nsCString& aFlavor,
233                                       const char* aClipboardData,
234                                       uint32_t aClipboardDataLength) {
235   LOGCLIP(("nsClipboard::SetTransferableData MIME %s\n", aFlavor.get()));
236 
237   nsCOMPtr<nsISupports> wrapper;
238   nsPrimitiveHelpers::CreatePrimitiveForData(
239       aFlavor, aClipboardData, aClipboardDataLength, getter_AddRefs(wrapper));
240   aTransferable->SetTransferData(aFlavor.get(), wrapper);
241 }
242 
243 NS_IMETHODIMP
GetData(nsITransferable * aTransferable,int32_t aWhichClipboard)244 nsClipboard::GetData(nsITransferable* aTransferable, int32_t aWhichClipboard) {
245   LOGCLIP(("nsClipboard::GetData (%s)\n",
246            aWhichClipboard == kSelectionClipboard ? "primary" : "clipboard"));
247 
248   if (!aTransferable || !mContext) {
249     return NS_ERROR_FAILURE;
250   }
251 
252   // Get a list of flavors this transferable can import
253   nsTArray<nsCString> flavors;
254   nsresult rv = aTransferable->FlavorsTransferableCanImport(flavors);
255   if (NS_FAILED(rv)) {
256     LOGCLIP(("    FlavorsTransferableCanImport falied!\n"));
257     return rv;
258   }
259 
260 #ifdef MOZ_LOGGING
261   LOGCLIP(("Flavors which can be imported:\n"));
262   for (uint32_t i = 0; i < flavors.Length(); i++) {
263     LOGCLIP(("    %s\n", flavors[i].get()));
264   }
265 #endif
266 
267   for (uint32_t i = 0; i < flavors.Length(); i++) {
268     nsCString& flavorStr = flavors[i];
269 
270     if (flavorStr.EqualsLiteral(kJPEGImageMime) ||
271         flavorStr.EqualsLiteral(kJPGImageMime) ||
272         flavorStr.EqualsLiteral(kPNGImageMime) ||
273         flavorStr.EqualsLiteral(kGIFImageMime)) {
274       // Emulate support for image/jpg
275       if (flavorStr.EqualsLiteral(kJPGImageMime)) {
276         flavorStr.Assign(kJPEGImageMime);
277       }
278 
279       LOGCLIP(("    Getting image %s MIME clipboard data\n", flavorStr.get()));
280 
281       uint32_t clipboardDataLength;
282       const char* clipboardData = mContext->GetClipboardData(
283           flavorStr.get(), aWhichClipboard, &clipboardDataLength);
284       if (!clipboardData) {
285         LOGCLIP(("    %s type is missing\n", flavorStr.get()));
286         continue;
287       }
288 
289       nsCOMPtr<nsIInputStream> byteStream;
290       NS_NewByteInputStream(getter_AddRefs(byteStream),
291                             Span(clipboardData, clipboardDataLength),
292                             NS_ASSIGNMENT_COPY);
293       aTransferable->SetTransferData(flavorStr.get(), byteStream);
294       LOGCLIP(("    got %s MIME data\n", flavorStr.get()));
295 
296       mContext->ReleaseClipboardData(clipboardData);
297       return NS_OK;
298     }
299 
300     // Special case text/unicode since we can convert any
301     // string into text/unicode
302     if (flavorStr.EqualsLiteral(kUnicodeMime)) {
303       LOGCLIP(
304           ("    Getting unicode %s MIME clipboard data\n", flavorStr.get()));
305 
306       const char* clipboardData = mContext->GetClipboardText(aWhichClipboard);
307       if (!clipboardData) {
308         LOGCLIP(("    failed to get unicode data\n"));
309         // If the type was text/unicode and we couldn't get
310         // text off the clipboard, run the next loop
311         // iteration.
312         continue;
313       }
314 
315       // Convert utf-8 into our unicode format.
316       NS_ConvertUTF8toUTF16 ucs2string(clipboardData);
317       const char* unicodeData = (const char*)ToNewUnicode(ucs2string);
318       uint32_t unicodeDataLength = ucs2string.Length() * 2;
319       SetTransferableData(aTransferable, flavorStr, unicodeData,
320                           unicodeDataLength);
321       free((void*)unicodeData);
322 
323       LOGCLIP(("    got unicode data, length %zd\n", ucs2string.Length()));
324 
325       mContext->ReleaseClipboardData(clipboardData);
326       return NS_OK;
327     }
328 
329     if (flavorStr.EqualsLiteral(kFileMime)) {
330       LOGCLIP(("    Getting %s file clipboard data\n", flavorStr.get()));
331 
332       uint32_t clipboardDataLength;
333       const char* clipboardData = mContext->GetClipboardData(
334           kURIListMime, aWhichClipboard, &clipboardDataLength);
335       if (!clipboardData) {
336         LOGCLIP(("    text/uri-list type is missing\n"));
337         continue;
338       }
339 
340       nsDependentCSubstring data(clipboardData, clipboardDataLength);
341       nsTArray<nsCString> uris = mozilla::widget::ParseTextURIList(data);
342       if (!uris.IsEmpty()) {
343         nsCOMPtr<nsIURI> fileURI;
344         NS_NewURI(getter_AddRefs(fileURI), uris[0]);
345         if (nsCOMPtr<nsIFileURL> fileURL = do_QueryInterface(fileURI, &rv)) {
346           nsCOMPtr<nsIFile> file;
347           rv = fileURL->GetFile(getter_AddRefs(file));
348           if (NS_SUCCEEDED(rv)) {
349             aTransferable->SetTransferData(flavorStr.get(), file);
350             LOGCLIP(("    successfully set file to clipboard\n"));
351           }
352         }
353       }
354 
355       mContext->ReleaseClipboardData(clipboardData);
356       return NS_OK;
357     }
358 
359     LOGCLIP(("    Getting %s MIME clipboard data\n", flavorStr.get()));
360 
361     uint32_t clipboardDataLength;
362     const char* clipboardData = mContext->GetClipboardData(
363         flavorStr.get(), aWhichClipboard, &clipboardDataLength);
364 
365 #ifdef MOZ_LOGGING
366     if (!clipboardData) {
367       LOGCLIP(("    %s type is missing\n", flavorStr.get()));
368     }
369 #endif
370 
371     if (clipboardData) {
372       LOGCLIP(("    got %s mime type data.\n", flavorStr.get()));
373 
374       // Special case text/html since we can convert into UCS2
375       if (flavorStr.EqualsLiteral(kHTMLMime)) {
376         char16_t* htmlBody = nullptr;
377         int32_t htmlBodyLen = 0;
378         // Convert text/html into our unicode format
379         nsAutoCString charset;
380         if (!GetHTMLCharset(clipboardData, clipboardDataLength, charset)) {
381           // Fall back to utf-8 in case html/data is missing kHTMLMarkupPrefix.
382           LOGCLIP(("Failed to get html/text encoding, fall back to utf-8.\n"));
383           charset.AssignLiteral("utf-8");
384         }
385         if (!ConvertHTMLtoUCS2(clipboardData, clipboardDataLength, charset,
386                                &htmlBody, htmlBodyLen)) {
387           LOGCLIP(("    failed to convert text/html to UCS2.\n"));
388           mContext->ReleaseClipboardData(clipboardData);
389           continue;
390         }
391 
392         SetTransferableData(aTransferable, flavorStr, (const char*)htmlBody,
393                             htmlBodyLen * 2);
394         free(htmlBody);
395       } else {
396         SetTransferableData(aTransferable, flavorStr, clipboardData,
397                             clipboardDataLength);
398       }
399 
400       mContext->ReleaseClipboardData(clipboardData);
401       return NS_OK;
402     }
403   }
404 
405   LOGCLIP(("    failed to get clipboard content.\n"));
406   return NS_OK;
407 }
408 
409 NS_IMETHODIMP
EmptyClipboard(int32_t aWhichClipboard)410 nsClipboard::EmptyClipboard(int32_t aWhichClipboard) {
411   LOGCLIP(("nsClipboard::EmptyClipboard (%s)\n",
412            aWhichClipboard == kSelectionClipboard ? "primary" : "clipboard"));
413   if (aWhichClipboard == kSelectionClipboard) {
414     if (mSelectionTransferable) {
415       gtk_clipboard_clear(gtk_clipboard_get(GDK_SELECTION_PRIMARY));
416       MOZ_ASSERT(!mSelectionTransferable);
417     }
418   } else {
419     if (mGlobalTransferable) {
420       gtk_clipboard_clear(gtk_clipboard_get(GDK_SELECTION_CLIPBOARD));
421       MOZ_ASSERT(!mGlobalTransferable);
422     }
423   }
424 
425   return NS_OK;
426 }
427 
ClearTransferable(int32_t aWhichClipboard)428 void nsClipboard::ClearTransferable(int32_t aWhichClipboard) {
429   if (aWhichClipboard == kSelectionClipboard) {
430     if (mSelectionOwner) {
431       mSelectionOwner->LosingOwnership(mSelectionTransferable);
432       mSelectionOwner = nullptr;
433     }
434     mSelectionTransferable = nullptr;
435   } else {
436     if (mGlobalOwner) {
437       mGlobalOwner->LosingOwnership(mGlobalTransferable);
438       mGlobalOwner = nullptr;
439     }
440     mGlobalTransferable = nullptr;
441   }
442 }
443 
444 NS_IMETHODIMP
HasDataMatchingFlavors(const nsTArray<nsCString> & aFlavorList,int32_t aWhichClipboard,bool * _retval)445 nsClipboard::HasDataMatchingFlavors(const nsTArray<nsCString>& aFlavorList,
446                                     int32_t aWhichClipboard, bool* _retval) {
447   if (!_retval) {
448     return NS_ERROR_NULL_POINTER;
449   }
450 
451   LOGCLIP(("nsClipboard::HasDataMatchingFlavors (%s)\n",
452            aWhichClipboard == kSelectionClipboard ? "primary" : "clipboard"));
453 
454   *_retval = false;
455 
456   if (!mContext) {
457     return NS_ERROR_FAILURE;
458   }
459 
460   int targetNums;
461   GdkAtom* targets = mContext->GetTargets(aWhichClipboard, &targetNums);
462   if (!targets) {
463     LOGCLIP(("    no targes at clipboard (null)\n"));
464     return NS_OK;
465   }
466 
467 #ifdef MOZ_LOGGING
468   LOGCLIP(("    Clipboard content (target nums %d):\n", targetNums));
469   for (int32_t j = 0; j < targetNums; j++) {
470     gchar* atom_name = gdk_atom_name(targets[j]);
471     if (!atom_name) {
472       LOGCLIP(("        failed to get MIME\n"));
473       continue;
474     }
475     LOGCLIP(("        MIME %s\n", atom_name));
476   }
477   LOGCLIP(("    Asking for content:\n"));
478   for (auto& flavor : aFlavorList) {
479     LOGCLIP(("        MIME %s\n", flavor.get()));
480   }
481 #endif
482 
483   // Walk through the provided types and try to match it to a
484   // provided type.
485   for (auto& flavor : aFlavorList) {
486     // We special case text/unicode here.
487     if (flavor.EqualsLiteral(kUnicodeMime) &&
488         gtk_targets_include_text(targets, targetNums)) {
489       *_retval = true;
490       LOGCLIP(("    has kUnicodeMime\n"));
491       break;
492     }
493 
494     for (int32_t j = 0; j < targetNums; j++) {
495       gchar* atom_name = gdk_atom_name(targets[j]);
496       if (!atom_name) continue;
497 
498       if (flavor.Equals(atom_name)) {
499         *_retval = true;
500         LOGCLIP(("    has %s\n", atom_name));
501       }
502       // X clipboard supports image/jpeg, but we want to emulate support
503       // for image/jpg as well
504       else if (flavor.EqualsLiteral(kJPGImageMime) &&
505                !strcmp(atom_name, kJPEGImageMime)) {
506         *_retval = true;
507         LOGCLIP(("    has image/jpg\n"));
508       }
509       // application/x-moz-file should be treated like text/uri-list
510       else if (flavor.EqualsLiteral(kFileMime) &&
511                !strcmp(atom_name, kURIListMime)) {
512         *_retval = true;
513         LOGCLIP(("    has text/uri-list treating as application/x-moz-file"));
514       }
515 
516       g_free(atom_name);
517 
518       if (*_retval) break;
519     }
520   }
521 
522 #ifdef MOZ_LOGGING
523   if (!(*_retval)) {
524     LOGCLIP(("    no targes at clipboard (bad match)\n"));
525   }
526 #endif
527 
528   g_free(targets);
529   return NS_OK;
530 }
531 
532 NS_IMETHODIMP
SupportsSelectionClipboard(bool * _retval)533 nsClipboard::SupportsSelectionClipboard(bool* _retval) {
534   *_retval = mContext ? mContext->HasSelectionSupport() : false;
535   return NS_OK;
536 }
537 
538 NS_IMETHODIMP
SupportsFindClipboard(bool * _retval)539 nsClipboard::SupportsFindClipboard(bool* _retval) {
540   *_retval = false;
541   return NS_OK;
542 }
543 
GetTransferable(int32_t aWhichClipboard)544 nsITransferable* nsClipboard::GetTransferable(int32_t aWhichClipboard) {
545   nsITransferable* retval;
546 
547   if (aWhichClipboard == kSelectionClipboard)
548     retval = mSelectionTransferable.get();
549   else
550     retval = mGlobalTransferable.get();
551 
552   return retval;
553 }
554 
SelectionGetEvent(GtkClipboard * aClipboard,GtkSelectionData * aSelectionData)555 void nsClipboard::SelectionGetEvent(GtkClipboard* aClipboard,
556                                     GtkSelectionData* aSelectionData) {
557   // Someone has asked us to hand them something.  The first thing
558   // that we want to do is see if that something includes text.  If
559   // it does, try to give it text/unicode after converting it to
560   // utf-8.
561 
562   int32_t whichClipboard;
563 
564   // which clipboard?
565   GdkAtom selection = gtk_selection_data_get_selection(aSelectionData);
566   if (selection == GDK_SELECTION_PRIMARY)
567     whichClipboard = kSelectionClipboard;
568   else if (selection == GDK_SELECTION_CLIPBOARD)
569     whichClipboard = kGlobalClipboard;
570   else
571     return;  // THAT AIN'T NO CLIPBOARD I EVER HEARD OF
572 
573   LOGCLIP(("nsClipboard::SelectionGetEvent (%s)\n",
574            whichClipboard == kSelectionClipboard ? "primary" : "clipboard"));
575 
576   nsCOMPtr<nsITransferable> trans = GetTransferable(whichClipboard);
577   if (!trans) {
578     // We have nothing to serve
579     LOGCLIP(("nsClipboard::SelectionGetEvent() - %s clipboard is empty!\n",
580              whichClipboard == kSelectionClipboard ? "Primary" : "Clipboard"));
581     return;
582   }
583 
584   nsresult rv;
585   nsCOMPtr<nsISupports> item;
586 
587   GdkAtom selectionTarget = gtk_selection_data_get_target(aSelectionData);
588   LOGCLIP(("  selection target %s\n", gdk_atom_name(selectionTarget)));
589 
590   // Check to see if the selection data is some text type.
591   if (gtk_targets_include_text(&selectionTarget, 1)) {
592     LOGCLIP(("  providing text/unicode data\n"));
593     // Try to convert our internal type into a text string.  Get
594     // the transferable for this clipboard and try to get the
595     // text/unicode type for it.
596     rv = trans->GetTransferData("text/unicode", getter_AddRefs(item));
597     if (NS_FAILED(rv) || !item) {
598       LOGCLIP(("  GetTransferData() failed to get text/unicode!\n"));
599       return;
600     }
601 
602     nsCOMPtr<nsISupportsString> wideString;
603     wideString = do_QueryInterface(item);
604     if (!wideString) return;
605 
606     nsAutoString ucs2string;
607     wideString->GetData(ucs2string);
608     NS_ConvertUTF16toUTF8 utf8string(ucs2string);
609 
610     LOGCLIP(("  sent %zd bytes of utf-8 data\n", utf8string.Length()));
611     if (selectionTarget == gdk_atom_intern("text/plain;charset=utf-8", FALSE)) {
612       LOGCLIP(
613           ("  using gtk_selection_data_set for 'text/plain;charset=utf-8'\n"));
614       // Bypass gtk_selection_data_set_text, which will convert \n to \r\n
615       // in some versions of GTK.
616       gtk_selection_data_set(aSelectionData, selectionTarget, 8,
617                              reinterpret_cast<const guchar*>(utf8string.get()),
618                              utf8string.Length());
619     } else {
620       gtk_selection_data_set_text(aSelectionData, utf8string.get(),
621                                   utf8string.Length());
622     }
623     return;
624   }
625 
626   // Check to see if the selection data is an image type
627   if (gtk_targets_include_image(&selectionTarget, 1, TRUE)) {
628     LOGCLIP(("  providing image data\n"));
629     // Look through our transfer data for the image
630     static const char* const imageMimeTypes[] = {kNativeImageMime,
631                                                  kPNGImageMime, kJPEGImageMime,
632                                                  kJPGImageMime, kGIFImageMime};
633     nsCOMPtr<nsISupports> imageItem;
634     nsCOMPtr<imgIContainer> image;
635     for (uint32_t i = 0; i < ArrayLength(imageMimeTypes); i++) {
636       rv = trans->GetTransferData(imageMimeTypes[i], getter_AddRefs(imageItem));
637       if (NS_FAILED(rv)) {
638         LOGCLIP(
639             ("    %s is missing at GetTransferData()\n", imageMimeTypes[i]));
640         continue;
641       }
642 
643       image = do_QueryInterface(imageItem);
644       if (image) {
645         LOGCLIP(
646             ("    %s is available at GetTransferData()\n", imageMimeTypes[i]));
647         break;
648       }
649     }
650 
651     if (!image) {  // Not getting an image for an image mime type!?
652       LOGCLIP(("    Failed to get any image mime from GetTransferData()!\n"));
653       return;
654     }
655 
656     GdkPixbuf* pixbuf = nsImageToPixbuf::ImageToPixbuf(image);
657     if (!pixbuf) {
658       LOGCLIP(("    nsImageToPixbuf::ImageToPixbuf() failed!\n"));
659       return;
660     }
661 
662     LOGCLIP(("    Setting pixbuf image data as %s\n",
663              gdk_atom_name(selectionTarget)));
664     gtk_selection_data_set_pixbuf(aSelectionData, pixbuf);
665     g_object_unref(pixbuf);
666     return;
667   }
668 
669   if (selectionTarget == gdk_atom_intern(kHTMLMime, FALSE)) {
670     LOGCLIP(("  providing %s data\n", kHTMLMime));
671     rv = trans->GetTransferData(kHTMLMime, getter_AddRefs(item));
672     if (NS_FAILED(rv) || !item) {
673       LOGCLIP(("  failed to get %s data by GetTransferData()!\n", kHTMLMime));
674       return;
675     }
676 
677     nsCOMPtr<nsISupportsString> wideString;
678     wideString = do_QueryInterface(item);
679     if (!wideString) {
680       LOGCLIP(("  failed to get wideString interface!"));
681       return;
682     }
683 
684     nsAutoString ucs2string;
685     wideString->GetData(ucs2string);
686 
687     nsAutoCString html;
688     // Add the prefix so the encoding is correctly detected.
689     html.AppendLiteral(kHTMLMarkupPrefix);
690     AppendUTF16toUTF8(ucs2string, html);
691 
692     LOGCLIP(("  Setting %zd bytest of %s data\n", html.Length(),
693              gdk_atom_name(selectionTarget)));
694     gtk_selection_data_set(aSelectionData, selectionTarget, 8,
695                            (const guchar*)html.get(), html.Length());
696     return;
697   }
698 
699   LOGCLIP(("  Try if we have anything at GetTransferData() for %s\n",
700            gdk_atom_name(selectionTarget)));
701 
702   // Try to match up the selection data target to something our
703   // transferable provides.
704   gchar* target_name = gdk_atom_name(selectionTarget);
705   if (!target_name) {
706     LOGCLIP(("  Failed to get target name!\n"));
707     return;
708   }
709 
710   rv = trans->GetTransferData(target_name, getter_AddRefs(item));
711   // nothing found?
712   if (NS_FAILED(rv) || !item) {
713     LOGCLIP(("  Failed to get anything from GetTransferData()!\n"));
714     g_free(target_name);
715     return;
716   }
717 
718   void* primitive_data = nullptr;
719   uint32_t dataLen = 0;
720   nsPrimitiveHelpers::CreateDataFromPrimitive(nsDependentCString(target_name),
721                                               item, &primitive_data, &dataLen);
722 
723   if (primitive_data) {
724     LOGCLIP(("  Setting %s as a primitive data type, %d bytes\n", target_name,
725              dataLen));
726     gtk_selection_data_set(aSelectionData, selectionTarget,
727                            8, /* 8 bits in a unit */
728                            (const guchar*)primitive_data, dataLen);
729     free(primitive_data);
730   } else {
731     LOGCLIP(("  Failed to get primitive data!\n"));
732   }
733 
734   g_free(target_name);
735 }
736 
SelectionClearEvent(GtkClipboard * aGtkClipboard)737 void nsClipboard::SelectionClearEvent(GtkClipboard* aGtkClipboard) {
738   int32_t whichClipboard = GetGeckoClipboardType(aGtkClipboard);
739   if (whichClipboard < 0) {
740     return;
741   }
742 
743   LOGCLIP(("nsClipboard::SelectionClearEvent (%s)\n",
744            whichClipboard == kSelectionClipboard ? "primary" : "clipboard"));
745 
746   ClearTransferable(whichClipboard);
747 }
748 
clipboard_get_cb(GtkClipboard * aGtkClipboard,GtkSelectionData * aSelectionData,guint info,gpointer user_data)749 void clipboard_get_cb(GtkClipboard* aGtkClipboard,
750                       GtkSelectionData* aSelectionData, guint info,
751                       gpointer user_data) {
752   LOGCLIP(("clipboard_get_cb() callback\n"));
753   nsClipboard* aClipboard = static_cast<nsClipboard*>(user_data);
754   aClipboard->SelectionGetEvent(aGtkClipboard, aSelectionData);
755 }
756 
clipboard_clear_cb(GtkClipboard * aGtkClipboard,gpointer user_data)757 void clipboard_clear_cb(GtkClipboard* aGtkClipboard, gpointer user_data) {
758   LOGCLIP(("clipboard_clear_cb() callback\n"));
759   nsClipboard* aClipboard = static_cast<nsClipboard*>(user_data);
760   aClipboard->SelectionClearEvent(aGtkClipboard);
761 }
762 
763 /*
764  * when copy-paste, mozilla wants data encoded using UCS2,
765  * other app such as StarOffice use "text/html"(RFC2854).
766  * This function convert data(got from GTK clipboard)
767  * to data mozilla wanted.
768  *
769  * data from GTK clipboard can be 3 forms:
770  *  1. From current mozilla
771  *     "text/html", charset = utf-16
772  *  2. From old version mozilla or mozilla-based app
773  *     content("body" only), charset = utf-16
774  *  3. From other app who use "text/html" when copy-paste
775  *     "text/html", has "charset" info
776  *
777  * data      : got from GTK clipboard
778  * dataLength: got from GTK clipboard
779  * body      : pass to Mozilla
780  * bodyLength: pass to Mozilla
781  */
ConvertHTMLtoUCS2(const char * data,int32_t dataLength,nsCString & charset,char16_t ** unicodeData,int32_t & outUnicodeLen)782 bool ConvertHTMLtoUCS2(const char* data, int32_t dataLength, nsCString& charset,
783                        char16_t** unicodeData, int32_t& outUnicodeLen) {
784   if (charset.EqualsLiteral("UTF-16")) {  // current mozilla
785     outUnicodeLen = (dataLength / 2) - 1;
786     *unicodeData = reinterpret_cast<char16_t*>(
787         moz_xmalloc((outUnicodeLen + sizeof('\0')) * sizeof(char16_t)));
788     memcpy(*unicodeData, data + sizeof(char16_t),
789            outUnicodeLen * sizeof(char16_t));
790     (*unicodeData)[outUnicodeLen] = '\0';
791     return true;
792   }
793   if (charset.EqualsLiteral("UNKNOWN")) {
794     outUnicodeLen = 0;
795     return false;
796   }
797   // app which use "text/html" to copy&paste
798   // get the decoder
799   auto encoding = Encoding::ForLabelNoReplacement(charset);
800   if (!encoding) {
801     LOGCLIP(("ConvertHTMLtoUCS2: get unicode decoder error\n"));
802     outUnicodeLen = 0;
803     return false;
804   }
805 
806   auto dataSpan = Span(data, dataLength);
807   // Remove kHTMLMarkupPrefix again, it won't necessarily cause any
808   // issues, but might confuse other users.
809   const size_t prefixLen = ArrayLength(kHTMLMarkupPrefix) - 1;
810   if (dataSpan.Length() >= prefixLen &&
811       Substring(data, prefixLen).EqualsLiteral(kHTMLMarkupPrefix)) {
812     dataSpan = dataSpan.From(prefixLen);
813   }
814 
815   auto decoder = encoding->NewDecoder();
816   CheckedInt<size_t> needed = decoder->MaxUTF16BufferLength(dataSpan.Length());
817   if (!needed.isValid() || needed.value() > INT32_MAX) {
818     outUnicodeLen = 0;
819     return false;
820   }
821 
822   outUnicodeLen = 0;
823   if (needed.value()) {
824     *unicodeData = reinterpret_cast<char16_t*>(
825         moz_xmalloc((needed.value() + 1) * sizeof(char16_t)));
826     uint32_t result;
827     size_t read;
828     size_t written;
829     bool hadErrors;
830     Tie(result, read, written, hadErrors) = decoder->DecodeToUTF16(
831         AsBytes(dataSpan), Span(*unicodeData, needed.value()), true);
832     MOZ_ASSERT(result == kInputEmpty);
833     MOZ_ASSERT(read == size_t(dataSpan.Length()));
834     MOZ_ASSERT(written <= needed.value());
835     Unused << hadErrors;
836     outUnicodeLen = written;
837     // null terminate.
838     (*unicodeData)[outUnicodeLen] = '\0';
839     return true;
840   }  // if valid length
841   return false;
842 }
843 
844 /*
845  * get "charset" information from clipboard data
846  * return value can be:
847  *  1. "UTF-16":      mozilla or "text/html" with "charset=utf-16"
848  *  2. "UNKNOWN":     mozilla can't detect what encode it use
849  *  3. other:         "text/html" with other charset than utf-16
850  */
GetHTMLCharset(const char * data,int32_t dataLength,nsCString & str)851 bool GetHTMLCharset(const char* data, int32_t dataLength, nsCString& str) {
852   // if detect "FFFE" or "FEFF", assume UTF-16
853   char16_t* beginChar = (char16_t*)data;
854   if ((beginChar[0] == 0xFFFE) || (beginChar[0] == 0xFEFF)) {
855     str.AssignLiteral("UTF-16");
856     LOGCLIP(("GetHTMLCharset: Charset of HTML is UTF-16\n"));
857     return true;
858   }
859   // no "FFFE" and "FEFF", assume ASCII first to find "charset" info
860   const nsDependentCSubstring htmlStr(data, dataLength);
861   nsACString::const_iterator start, end;
862   htmlStr.BeginReading(start);
863   htmlStr.EndReading(end);
864   nsACString::const_iterator valueStart(start), valueEnd(start);
865 
866   if (CaseInsensitiveFindInReadable("CONTENT=\"text/html;"_ns, start, end)) {
867     start = end;
868     htmlStr.EndReading(end);
869 
870     if (CaseInsensitiveFindInReadable("charset="_ns, start, end)) {
871       valueStart = end;
872       start = end;
873       htmlStr.EndReading(end);
874 
875       if (FindCharInReadable('"', start, end)) valueEnd = start;
876     }
877   }
878   // find "charset" in HTML
879   if (valueStart != valueEnd) {
880     str = Substring(valueStart, valueEnd);
881     ToUpperCase(str);
882     LOGCLIP(("GetHTMLCharset: Charset of HTML = %s\n", str.get()));
883     return true;
884   }
885   str.AssignLiteral("UNKNOWN");
886   LOGCLIP(("GetHTMLCharset: Failed to get HTML Charset!\n"));
887   return false;
888 }
889