1 /**************************************************************
2  *
3  * Licensed to the Apache Software Foundation (ASF) under one
4  * or more contributor license agreements.  See the NOTICE file
5  * distributed with this work for additional information
6  * regarding copyright ownership.  The ASF licenses this file
7  * to you under the Apache License, Version 2.0 (the
8  * "License"); you may not use this file except in compliance
9  * with the License.  You may obtain a copy of the License at
10  *
11  *   http://www.apache.org/licenses/LICENSE-2.0
12  *
13  * Unless required by applicable law or agreed to in writing,
14  * software distributed under the License is distributed on an
15  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16  * KIND, either express or implied.  See the License for the
17  * specific language governing permissions and limitations
18  * under the License.
19  *
20  *************************************************************/
21 
22 
23 
24 // MARKER(update_precomp.py): autogen include statement, do not remove
25 #include "precompiled_shell.hxx"
26 
27 #include "internal/global.hxx"
28 
29 #ifndef INFOTIPS_HXX_INCLUDED
30 #include "internal/thumbviewer.hxx"
31 #endif
32 #include "internal/shlxthdl.hxx"
33 #include "internal/registry.hxx"
34 #include "internal/fileextensions.hxx"
35 #include "internal/config.hxx"
36 #include "internal/zipfile.hxx"
37 #include "internal/utilities.hxx"
38 
39 #include "internal/resource.h"
40 
41 #include <stdio.h>
42 #include <utility>
43 #include <stdlib.h>
44 
45 #if defined _MSC_VER
46 #pragma warning(push, 1)
47 #endif
48 #include <shellapi.h>
49 #if defined _MSC_VER
50 #pragma warning(pop)
51 #endif
52 #include <memory>
53 
54 extern HINSTANCE g_hModule;
55 
56 namespace internal
57 {
58     /* The signet.png used for thumbnails of signed documents
59        is contained as resource in this module, the resource
60        id is 2000 */
61     void LoadSignetImageFromResource(ZipFile::ZipContentBuffer_t& buffer)
62     {
63         HRSRC hrc = FindResource(g_hModule, TEXT("#2000"), RT_RCDATA);
64         DWORD size = SizeofResource(g_hModule, hrc);
65         HGLOBAL hglob = LoadResource(g_hModule, hrc);
66         char* data = reinterpret_cast<char*>(LockResource(hglob));
67         buffer = ZipFile::ZipContentBuffer_t(data, data + size);
68     }
69 
70     bool IsSignedDocument(const ZipFile* zipfile)
71     {
72         return zipfile->HasContent("META-INF/documentsignatures.xml");
73     }
74 
75     bool IsWindowsXP()
76     {
77         OSVERSIONINFO osvi;
78         ZeroMemory(&osvi, sizeof(osvi));
79         osvi.dwOSVersionInfoSize = sizeof(osvi);
80         GetVersionEx(&osvi);
81 
82         return ((osvi.dwPlatformId == VER_PLATFORM_WIN32_NT) &&
83                 ((osvi.dwMajorVersion >= 5) && (osvi.dwMinorVersion >= 1)));
84     }
85 
86     /* Calculate where to position the signet image.
87        On Windows ME we need to shift the signet a
88        little bit to the left because Windows ME
89        puts an overlay icon to the lower right
90        corner of a thumbnail image so that our signet
91        we be hidden. */
92     Gdiplus::Point CalcSignetPosition(
93         const Gdiplus::Rect& canvas, const Gdiplus::Rect& thumbnail_border, const Gdiplus::Rect& signet)
94     {
95         int x = 0;
96         int y = 0;
97         int hoffset = canvas.GetRight() - thumbnail_border.GetRight();
98         int voffset = canvas.GetBottom() - thumbnail_border.GetBottom();
99 
100         if (hoffset > voffset)
101         {
102             x = thumbnail_border.GetRight() - signet.GetRight() + min(signet.GetRight() / 2, hoffset);
103             y = thumbnail_border.GetBottom() - signet.GetBottom();
104         }
105         else
106         {
107             x = thumbnail_border.GetRight() - signet.GetRight();
108             y = thumbnail_border.GetBottom() - signet.GetBottom() + min(signet.GetBottom() / 2, voffset);
109         }
110 
111         if (!IsWindowsXP())
112             x -= 15;
113 
114         return Gdiplus::Point(x,y);
115     }
116 }
117 
118 class StreamOnZipBuffer : public IStream
119 {
120 public:
121     StreamOnZipBuffer(const ZipFile::ZipContentBuffer_t& zip_buffer);
122 
123     // IUnknown
124     virtual ULONG STDMETHODCALLTYPE AddRef();
125     virtual ULONG STDMETHODCALLTYPE Release( void);
126     virtual HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, void __RPC_FAR *__RPC_FAR *ppvObject);
127 
128     // IStream
129     virtual HRESULT STDMETHODCALLTYPE Read(void *pv, ULONG cb, ULONG *pcbRead);
130     virtual HRESULT STDMETHODCALLTYPE Write(void const *pv, ULONG cb, ULONG *pcbWritten);
131     virtual HRESULT STDMETHODCALLTYPE Seek(LARGE_INTEGER dlibMove, DWORD dwOrigin, ULARGE_INTEGER *plibNewPosition);
132     virtual HRESULT STDMETHODCALLTYPE SetSize(ULARGE_INTEGER libNewSize);
133     virtual HRESULT STDMETHODCALLTYPE CopyTo(IStream *pstm, ULARGE_INTEGER cb, ULARGE_INTEGER *pcbRead, ULARGE_INTEGER *pcbWritten);
134     virtual HRESULT STDMETHODCALLTYPE Commit(DWORD grfCommitFlags);
135     virtual HRESULT STDMETHODCALLTYPE Revert(void);
136     virtual HRESULT STDMETHODCALLTYPE LockRegion(ULARGE_INTEGER libOffset, ULARGE_INTEGER cb, DWORD dwLockType);
137     virtual HRESULT STDMETHODCALLTYPE UnlockRegion(ULARGE_INTEGER libOffset, ULARGE_INTEGER cb, DWORD dwLockType);
138     virtual HRESULT STDMETHODCALLTYPE Stat(STATSTG *pstatstg, DWORD grfStatFlag);
139     virtual HRESULT STDMETHODCALLTYPE Clone(IStream **ppstm);
140 
141 private:
142     LONG ref_count_;
143     const ZipFile::ZipContentBuffer_t& ref_zip_buffer_;
144     size_t pos_;
145 };
146 
147 StreamOnZipBuffer::StreamOnZipBuffer(const ZipFile::ZipContentBuffer_t& zip_buffer) :
148     ref_count_(1),
149     ref_zip_buffer_(zip_buffer),
150     pos_(0)
151 {
152 }
153 
154 // IUnknown methods
155 
156 ULONG STDMETHODCALLTYPE StreamOnZipBuffer::AddRef(void)
157 {
158     return InterlockedIncrement(&ref_count_);
159 }
160 
161 ULONG STDMETHODCALLTYPE StreamOnZipBuffer::Release( void)
162 {
163     long refcnt = InterlockedDecrement(&ref_count_);
164 
165     if (0 == ref_count_)
166         delete this;
167 
168     return refcnt;
169 }
170 
171 HRESULT STDMETHODCALLTYPE StreamOnZipBuffer::QueryInterface(REFIID riid, void __RPC_FAR *__RPC_FAR *ppvObject)
172 {
173     *ppvObject = 0;
174     IUnknown* pUnk = 0;
175 
176     if ((IID_IUnknown == riid) || (IID_IStream == riid))
177     {
178         pUnk = static_cast<IStream*>(this);
179         pUnk->AddRef();
180         *ppvObject = pUnk;
181         return S_OK;
182     }
183     return E_NOINTERFACE;
184 }
185 
186 HRESULT STDMETHODCALLTYPE StreamOnZipBuffer::Read(void *pv, ULONG cb, ULONG *pcbRead)
187 {
188     if (pv == NULL)
189         return STG_E_INVALIDPOINTER;
190 
191     size_t size = ref_zip_buffer_.size();
192 
193     if (pos_ > size)
194         return S_FALSE;
195 
196     char* p = reinterpret_cast<char*>(pv);
197     ULONG read = 0;
198 
199     for ( ;(pos_ < size) && (cb > 0); pos_++, cb--, read++)
200         *p++ = ref_zip_buffer_[pos_];
201 
202     if (pcbRead)
203         *pcbRead = read;
204 
205     return S_OK;
206 }
207 
208 HRESULT STDMETHODCALLTYPE StreamOnZipBuffer::Seek(LARGE_INTEGER dlibMove, DWORD dwOrigin, ULARGE_INTEGER *)
209 {
210     __int64 size = (__int64) ref_zip_buffer_.size();
211     __int64 p = 0;
212 
213     switch (dwOrigin)
214     {
215         case STREAM_SEEK_SET:
216             break;
217         case STREAM_SEEK_CUR:
218             p = (__int64) pos_;
219             break;
220         case STREAM_SEEK_END:
221             p = size - 1;
222             break;
223    }
224 
225    HRESULT hr = STG_E_INVALIDFUNCTION;
226 
227    p += dlibMove.QuadPart;
228 
229    if ( ( p >= 0 ) && (p < size) )
230    {
231         pos_ = (size_t) p;
232         hr = S_OK;
233    }
234    return hr;
235 }
236 
237 HRESULT STDMETHODCALLTYPE StreamOnZipBuffer::Stat(STATSTG *pstatstg, DWORD grfStatFlag)
238 {
239     if (pstatstg == NULL)
240         return STG_E_INVALIDPOINTER;
241 
242     ZeroMemory(pstatstg, sizeof(STATSTG));
243 
244     if (grfStatFlag == STATFLAG_DEFAULT)
245     {
246         size_t sz = 4 * sizeof(wchar_t);
247         wchar_t* name = reinterpret_cast<wchar_t*>(CoTaskMemAlloc(sz));
248         ZeroMemory(name, sz);
249         memcpy(name, L"png", 3 * sizeof(wchar_t));
250         pstatstg->pwcsName = name;
251     }
252 
253     pstatstg->type = STGTY_LOCKBYTES;
254 
255     ULARGE_INTEGER uli;
256     uli.LowPart = ref_zip_buffer_.size();
257     uli.HighPart = 0;
258 
259     pstatstg->cbSize = uli;
260 
261     return S_OK;
262 }
263 
264 HRESULT STDMETHODCALLTYPE StreamOnZipBuffer::Write(void const *, ULONG, ULONG *)
265 { return E_NOTIMPL; }
266 
267 HRESULT STDMETHODCALLTYPE StreamOnZipBuffer::SetSize(ULARGE_INTEGER)
268 { return E_NOTIMPL; }
269 
270 HRESULT STDMETHODCALLTYPE StreamOnZipBuffer::CopyTo(IStream *, ULARGE_INTEGER, ULARGE_INTEGER *, ULARGE_INTEGER *)
271 { return E_NOTIMPL; }
272 
273 HRESULT STDMETHODCALLTYPE StreamOnZipBuffer::Commit(DWORD)
274 { return E_NOTIMPL; }
275 
276 HRESULT STDMETHODCALLTYPE StreamOnZipBuffer::Revert(void)
277 { return E_NOTIMPL; }
278 
279 HRESULT STDMETHODCALLTYPE StreamOnZipBuffer::LockRegion(ULARGE_INTEGER, ULARGE_INTEGER, DWORD)
280 { return E_NOTIMPL; }
281 
282 HRESULT STDMETHODCALLTYPE StreamOnZipBuffer::UnlockRegion(ULARGE_INTEGER, ULARGE_INTEGER, DWORD)
283 { return E_NOTIMPL; }
284 
285 HRESULT STDMETHODCALLTYPE StreamOnZipBuffer::Clone(IStream **)
286 {  return E_NOTIMPL; }
287 
288 
289 //#########################################
290 
291 
292 CThumbviewer::CThumbviewer(long RefCnt) :
293     ref_count_(RefCnt)
294 {
295     InterlockedIncrement(&g_DllRefCnt);
296 
297     thumbnail_size_.cx = 0;
298     thumbnail_size_.cy = 0;
299 
300     Gdiplus::GdiplusStartupInput gdiplusStartupInput;
301     Gdiplus::GdiplusStartup(&gdiplus_token_, &gdiplusStartupInput, NULL);
302 
303     ZipFile::ZipContentBuffer_t img_data;
304     internal::LoadSignetImageFromResource(img_data);
305     IStream* stream = new StreamOnZipBuffer(img_data);
306     signet_ = new Gdiplus::Bitmap(stream, TRUE);
307     stream->Release();
308 }
309 
310 CThumbviewer::~CThumbviewer()
311 {
312     delete signet_;
313     Gdiplus::GdiplusShutdown(gdiplus_token_);
314     InterlockedDecrement(&g_DllRefCnt);
315 }
316 
317 // IUnknown methods
318 
319 HRESULT STDMETHODCALLTYPE CThumbviewer::QueryInterface(REFIID riid, void __RPC_FAR *__RPC_FAR *ppvObject)
320 {
321     *ppvObject = 0;
322     IUnknown* pUnk = 0;
323 
324     if ((IID_IUnknown == riid) || (IID_IPersistFile == riid))
325     {
326         pUnk = static_cast<IPersistFile*>(this);
327         pUnk->AddRef();
328         *ppvObject = pUnk;
329         return S_OK;
330     }
331     else if (IID_IExtractImage == riid)
332     {
333         pUnk = static_cast<IExtractImage*>(this);
334         pUnk->AddRef();
335         *ppvObject = pUnk;
336         return S_OK;
337     }
338     return E_NOINTERFACE;
339 }
340 
341 ULONG STDMETHODCALLTYPE CThumbviewer::AddRef(void)
342 {
343     return InterlockedIncrement(&ref_count_);
344 }
345 
346 ULONG STDMETHODCALLTYPE CThumbviewer::Release( void)
347 {
348     long refcnt = InterlockedDecrement(&ref_count_);
349 
350     if (0 == ref_count_)
351         delete this;
352 
353     return refcnt;
354 }
355 
356 // IExtractImage2 methods
357 
358 const std::string THUMBNAIL_CONTENT = "Thumbnails/thumbnail.png";
359 
360 HRESULT STDMETHODCALLTYPE CThumbviewer::Extract(HBITMAP *phBmpImage)
361 {
362     HRESULT hr = E_FAIL;
363 
364     try
365     {
366         std::wstring fname = getShortPathName( filename_ );
367         std::auto_ptr<ZipFile> zipfile( new ZipFile( WStringToString( fname ) ) );
368 
369         if (zipfile->HasContent(THUMBNAIL_CONTENT))
370         {
371             ZipFile::ZipContentBuffer_t thumbnail;
372             zipfile->GetUncompressedContent(THUMBNAIL_CONTENT, thumbnail);
373             IStream* stream = new StreamOnZipBuffer(thumbnail);
374 
375             Gdiplus::Bitmap thumbnail_png(stream, TRUE);
376 
377             if ((thumbnail_png.GetHeight() == 0) || (thumbnail_png.GetWidth() == 0))
378             {
379                 stream->Release();
380                 return E_FAIL;
381             }
382 
383     		HWND hwnd = GetDesktopWindow();
384     		HDC hdc = GetDC(hwnd);
385             HDC memDC = CreateCompatibleDC(hdc);
386 
387             if (memDC)
388             {
389                 UINT offset = 3; // reserve a little border space
390 
391                 Gdiplus::Rect canvas(0, 0, thumbnail_size_.cx, thumbnail_size_.cy);
392                 Gdiplus::Rect canvas_thumbnail(offset, offset, thumbnail_size_.cx - 2 * offset, thumbnail_size_.cy - 2 * offset);
393 
394                 Gdiplus::Rect scaledRect = CalcScaledAspectRatio(
395                     Gdiplus::Rect(0, 0, thumbnail_png.GetWidth(), thumbnail_png.GetHeight()), canvas_thumbnail);
396 
397                 struct {
398                     BITMAPINFOHEADER bi;
399                     DWORD ct[256];
400                 } dib;
401 
402                 ZeroMemory(&dib, sizeof(dib));
403 
404                 dib.bi.biSize = sizeof(BITMAPINFOHEADER);
405                 dib.bi.biWidth = thumbnail_size_.cx;
406                 dib.bi.biHeight = thumbnail_size_.cy;
407                 dib.bi.biPlanes = 1;
408                 dib.bi.biBitCount = static_cast<WORD>(color_depth_);
409                 dib.bi.biCompression = BI_RGB;
410 
411                 LPVOID lpBits;
412                 HBITMAP hMemBmp = CreateDIBSection(memDC, (LPBITMAPINFO)&dib, DIB_RGB_COLORS, &lpBits, NULL, 0);
413                 HGDIOBJ hOldObj = SelectObject(memDC, hMemBmp);
414 
415                 Gdiplus::Graphics graphics(memDC);
416                 Gdiplus::Pen blackPen(Gdiplus::Color(255, 0, 0, 0), 1);
417 
418                 Gdiplus::SolidBrush whiteBrush(Gdiplus::Color(255, 255, 255, 255));
419                 graphics.FillRectangle(&whiteBrush, canvas);
420 
421                 scaledRect.X = (canvas.Width - scaledRect.Width) / 2;
422                 scaledRect.Y = (canvas.Height - scaledRect.Height) / 2;
423 
424                 Gdiplus::Rect border_rect(scaledRect.X, scaledRect.Y, scaledRect.Width, scaledRect.Height);
425                 graphics.DrawRectangle(&blackPen, border_rect);
426 
427                 scaledRect.X += 1;
428                 scaledRect.Y += 1;
429                 scaledRect.Width -= 1;
430                 scaledRect.Height -= 1;
431 
432                 graphics.SetInterpolationMode(Gdiplus::InterpolationModeHighQualityBicubic);
433                 Gdiplus::Status stat = graphics.DrawImage(
434                     &thumbnail_png, scaledRect, 0 , 0,
435                     thumbnail_png.GetWidth(), thumbnail_png.GetHeight(), Gdiplus::UnitPixel);
436 
437                 /* Add a signet sign to the thumbnail of signed documents */
438                 if (internal::IsSignedDocument(zipfile.get()))
439                 {
440                     double SCALING_FACTOR = 0.6;
441                     Gdiplus::Rect signet_scaled(
442                         0, 0, static_cast<INT>(signet_->GetWidth() * SCALING_FACTOR), static_cast<INT>(signet_->GetHeight() * SCALING_FACTOR));
443                     Gdiplus::Point pos_signet = internal::CalcSignetPosition(canvas_thumbnail, border_rect, signet_scaled);
444                     Gdiplus::Rect dest(pos_signet.X, pos_signet.Y, signet_scaled.GetRight(), signet_scaled.GetBottom());
445 
446                     stat = graphics.DrawImage(
447                         signet_, dest,
448                         0, 0, signet_->GetWidth(), signet_->GetHeight(),
449                         Gdiplus::UnitPixel);
450                 }
451 
452                 if (stat == Gdiplus::Ok)
453                 {
454                     *phBmpImage = hMemBmp;
455                     hr = NOERROR;
456                 }
457 
458                 SelectObject(memDC, hOldObj);
459                 DeleteDC(memDC);
460 			}
461 
462 			ReleaseDC(hwnd, hdc);
463 			stream->Release();
464 		}
465 	}
466 	catch(std::exception&)
467 	{
468 		OutputDebugStringFormat( "CThumbviewer Extract ERROR!\n" );
469 		hr = E_FAIL;
470 	}
471 	return hr;
472 }
473 
474 HRESULT STDMETHODCALLTYPE CThumbviewer::GetLocation(
475 	LPWSTR pszPathBuffer, DWORD cchMax, DWORD *pdwPriority, const SIZE *prgSize, DWORD dwRecClrDepth, DWORD *pdwFlags)
476 {
477 	if ((prgSize == NULL) || (pdwFlags == NULL) || ((*pdwFlags & IEIFLAG_ASYNC) && (pdwPriority == NULL)))
478 		return E_INVALIDARG;
479 
480 	thumbnail_size_ = *prgSize;
481 	color_depth_ = dwRecClrDepth;
482 
483 	*pdwFlags = IEIFLAG_CACHE; // we don't cache the image
484 
485 	wcsncpy(pszPathBuffer, filename_.c_str(), cchMax);
486 
487 	return NOERROR;
488 }
489 
490 // IPersist methods
491 
492 HRESULT STDMETHODCALLTYPE CThumbviewer::GetClassID(CLSID* pClassID)
493 {
494 	pClassID = const_cast<CLSID*>(&CLSID_THUMBVIEWER_HANDLER);
495 	return S_OK;
496 }
497 
498 // IPersistFile methods
499 
500 HRESULT STDMETHODCALLTYPE CThumbviewer::Load(LPCOLESTR pszFileName, DWORD)
501 {
502 	filename_ = pszFileName;
503 	return S_OK;
504 }
505 
506 HRESULT STDMETHODCALLTYPE CThumbviewer::IsDirty()
507 { return E_NOTIMPL; }
508 
509 HRESULT STDMETHODCALLTYPE CThumbviewer::Save(LPCOLESTR, BOOL)
510 { return E_NOTIMPL; }
511 
512 HRESULT STDMETHODCALLTYPE CThumbviewer::SaveCompleted(LPCOLESTR)
513 { return E_NOTIMPL; }
514 
515 HRESULT STDMETHODCALLTYPE CThumbviewer::GetCurFile(LPOLESTR __RPC_FAR*)
516 { return E_NOTIMPL; }
517 
518 
519 Gdiplus::Rect CThumbviewer::CalcScaledAspectRatio(Gdiplus::Rect src, Gdiplus::Rect dest)
520 {
521 	Gdiplus::Rect result;
522 	if (src.Width >= src.Height)
523 		result = Gdiplus::Rect(0, 0, dest.Width, src.Height * dest.Width / src.Width);
524 	else
525 		result = Gdiplus::Rect(0, 0, src.Width * dest.Height / src.Height, dest.Height);
526 
527 	return result;
528 }
529 
530 /* vim: set noet sw=4 ts=4: */
531