1 /*************************************************************************
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
5 * Copyright 2008 by Sun Microsystems, Inc.
7 * OpenOffice.org - a multi-platform office productivity suite
9 * $RCSfile: macmgr.cxx,v $
12 * This file is part of OpenOffice.org.
14 * OpenOffice.org is free software: you can redistribute it and/or modify
15 * it under the terms of the GNU Lesser General Public License version 3
16 * only, as published by the Free Software Foundation.
18 * OpenOffice.org is distributed in the hope that it will be useful,
19 * but WITHOUT ANY WARRANTY; without even the implied warranty of
20 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 * GNU Lesser General Public License version 3 for more details
22 * (a copy is included in the LICENSE file that accompanied this code).
24 * You should have received a copy of the GNU Lesser General Public License
25 * version 3 along with OpenOffice.org. If not, see
26 * <http://www.openoffice.org/license.html>
27 * for a copy of the LGPLv3 License.
29 ************************************************************************/
31 #include "rtl/ustrbuf.hxx"
32 #include "rtl/strbuf.hxx"
34 #include "plugin/impl.hxx"
36 #include "osl/module.hxx"
40 using namespace com::sun::star::uno
;
41 using namespace com::sun::star::plugin
;
43 namespace plugstringhelper
46 rtl::OUString
getString( CFStringRef i_xString
)
48 rtl::OUStringBuffer aBuf
;
51 CFIndex nChars
= CFStringGetLength( i_xString
);
52 CFRange aRange
= { 0, nChars
};
53 aBuf
.setLength( nChars
);
54 CFStringGetCharacters( i_xString
, aRange
, static_cast< UniChar
* >(const_cast<sal_Unicode
*>(aBuf
.getStr())) );
56 return aBuf
.makeStringAndClear();
59 rtl::OUString
getString( CFURLRef i_xURL
)
61 CFStringRef xString
= CFURLGetString( i_xURL
);
62 return getString( xString
);
65 CFMutableStringRef
createString( const rtl::OUString
& i_rString
)
67 CFMutableStringRef xString
= CFStringCreateMutable( NULL
, 0 );
69 CFStringAppendCharacters( xString
, i_rString
.getStr(), i_rString
.getLength() );
73 CFURLRef
createURL( const rtl::OUString
& i_rString
)
76 CFMutableStringRef xMutableString
= createString( i_rString
);
77 CFURLRef xURL
= CFURLCreateWithString( NULL
, xMutableString
, NULL
);
78 CFRelease( xMutableString
);
82 rtl::OUString
getURLFromPath( const rtl::OUString
& i_rPath
)
84 CFMutableStringRef xMutableString
= createString( i_rPath
);
85 CFURLRef xURL
= CFURLCreateWithFileSystemPath( NULL
, xMutableString
, kCFURLPOSIXPathStyle
, true );
86 CFRelease( xMutableString
);
87 CFStringRef xString
= CFURLGetString( xURL
);
88 rtl::OUString aRet
= getString( xString
);
93 CFURLRef
createURLFromPath( const rtl::OUString
& i_rPath
)
95 CFMutableStringRef xMutableString
= createString( i_rPath
);
96 CFURLRef xURL
= CFURLCreateWithFileSystemPath( NULL
, xMutableString
, kCFURLPOSIXPathStyle
, true );
100 rtl::OUString
CFURLtoOSLURL( CFURLRef i_xURL
)
103 CFURLRef xAbsURL
= CFURLCopyAbsoluteURL( i_xURL
);
105 CFStringRef xSysPath
= CFURLCopyFileSystemPath( xAbsURL
? xAbsURL
: i_xURL
, kCFURLPOSIXPathStyle
);
107 CFRelease( xAbsURL
);
108 rtl::OUString
aSysPath( getString( xSysPath
) );
109 CFRelease( xSysPath
);
110 rtl::OUString aFileURL
;
111 osl_getFileURLFromSystemPath( aSysPath
.pData
, &aFileURL
.pData
);
117 using namespace plugstringhelper
;
119 static int parsePlist( CFBundleRef i_xBundle
, const rtl::OUString
& i_rBundleURL
, list
< PluginDescription
* >& io_rDescriptions
)
121 CFTypeRef xMimeDict
= CFBundleGetValueForInfoDictionaryKey( i_xBundle
, CFSTR("WebPluginMIMETypes"));
123 if( xMimeDict
== 0 ||
124 CFGetTypeID(xMimeDict
) != CFDictionaryGetTypeID() ||
125 (nMimetypes
= CFDictionaryGetCount( static_cast<CFDictionaryRef
>(xMimeDict
))) <= 0 )
130 // prepare an array of key and value refs
131 std::vector
< CFTypeRef
> aKeys( nMimetypes
, CFTypeRef(NULL
) );
132 std::vector
< CFTypeRef
> aValues( nMimetypes
, CFTypeRef(NULL
) );
133 CFDictionaryGetKeysAndValues(static_cast<CFDictionaryRef
>(xMimeDict
), &aKeys
[0], &aValues
[0] );
136 for( int i
= 0; i
< nMimetypes
; i
++ )
139 CFTypeRef xKey
= aKeys
[i
];
140 if( ! xKey
|| CFGetTypeID(xKey
) != CFStringGetTypeID() )
142 rtl::OUString aMimetype
= getString( (CFStringRef
)xKey
);
144 // the correspoding value should be a dictionary
145 CFTypeRef xDict
= aValues
[i
];
146 if( ! xDict
|| CFGetTypeID( xDict
) != CFDictionaryGetTypeID() )
149 // get the extension list
150 CFTypeRef xExtArray
= CFDictionaryGetValue( (CFDictionaryRef
)xDict
, CFSTR("WebPluginExtensions" ) );
151 if( !xExtArray
|| CFGetTypeID( xExtArray
) != CFArrayGetTypeID() )
154 OUStringBuffer aExtBuf
;
155 int nExtensions
= CFArrayGetCount( (CFArrayRef
)xExtArray
);
156 for( int n
= 0; n
< nExtensions
; n
++ )
158 CFTypeRef xExt
= CFArrayGetValueAtIndex( (CFArrayRef
)xExtArray
, n
);
159 if( xExt
&& CFGetTypeID( xExt
) == CFStringGetTypeID() )
161 if( aExtBuf
.getLength() > 0 )
162 aExtBuf
.append( sal_Unicode(';') );
163 OUString
aExt( getString( (CFStringRef
)xExt
) );
164 if( aExt
.indexOfAsciiL( "*.", 2 ) != 0 )
165 aExtBuf
.appendAscii( "*." );
166 aExtBuf
.append( aExt
);
170 // get the description string
171 CFTypeRef xDescString
= CFDictionaryGetValue( (CFDictionaryRef
)xDict
, CFSTR("WebPluginTypeDescription" ) );
172 if( !xDescString
|| CFGetTypeID( xDescString
) != CFStringGetTypeID() )
174 rtl::OUString aDescription
= getString( (CFStringRef
)xDescString
);
176 PluginDescription
* pNew
= new PluginDescription
;
177 // set plugin name (path to library)
178 pNew
->PluginName
= i_rBundleURL
;
180 pNew
->Mimetype
= aMimetype
;
181 // set extension line
182 pNew
->Extension
= aExtBuf
.makeStringAndClear();
184 pNew
->Description
= aDescription
;
186 io_rDescriptions
.push_back( pNew
);
189 #if OSL_DEBUG_LEVEL > 1
191 "Inserting from PList:\n"
194 " Description: %s\n",
195 OUStringToOString( pNew
->Mimetype
, RTL_TEXTENCODING_UTF8
).getStr(),
196 OUStringToOString( pNew
->Extension
, RTL_TEXTENCODING_UTF8
).getStr(),
197 OUStringToOString( pNew
->Description
, RTL_TEXTENCODING_UTF8
).getStr()
206 static int parseMimeString( const rtl::OUString
& i_rBundleURL
, list
< PluginDescription
* >& io_rDescriptions
, const char* i_pMime
)
211 rtl_TextEncoding aEncoding
= osl_getThreadTextEncoding();
214 aMIME
.append( i_pMime
);
216 if( aMIME
.getLength() < 1 )
219 OString aLine
= aMIME
.makeStringAndClear();
222 sal_Int32 nIndex
= 0;
223 while( nIndex
!= -1 )
225 OString aType
= aLine
.getToken( 0, ';', nIndex
);
227 sal_Int32 nTypeIndex
= 0;
228 OString aMimetype
= aType
.getToken( 0, ':', nTypeIndex
);
229 OString aExtLine
= aType
.getToken( 0, ':', nTypeIndex
);
230 if( nTypeIndex
< 0 ) // ensure at least three tokens
232 OString aDesc
= aType
.getToken( 0, ':', nTypeIndex
);
234 // create extension list string
235 sal_Int32 nExtIndex
= 0;
236 OStringBuffer aExtension
;
237 while( nExtIndex
!= -1 )
239 OString aExt
= aExtLine
.getToken( 0, ',', nExtIndex
);
240 if( aExt
.indexOf( "*." ) != 0 )
241 aExtension
.append( "*." );
242 aExtension
.append( aExt
);
243 if( nExtIndex
!= -1 )
244 aExtension
.append( ';' );
247 PluginDescription
* pNew
= new PluginDescription
;
248 // set plugin name (path to library)
249 pNew
->PluginName
= i_rBundleURL
;
251 pNew
->Mimetype
= OStringToOUString( aMimetype
, aEncoding
);
252 // set extension line
253 pNew
->Extension
= OStringToOUString( aExtension
.makeStringAndClear(), aEncoding
);
255 pNew
->Description
= OStringToOUString( aDesc
, aEncoding
);
256 io_rDescriptions
.push_back( pNew
);
259 #if OSL_DEBUG_LEVEL > 1
261 "Inserting from mime string:\n"
264 " Description: %s\n",
265 OUStringToOString( pNew
->Mimetype
, aEncoding
).getStr(),
266 OUStringToOString( pNew
->Extension
, aEncoding
).getStr(),
267 OUStringToOString( pNew
->Description
, aEncoding
).getStr()
274 // this is so ugly it you want to tear your eyes out
275 static rtl::OUString
GetNextPluginStringFromHandle(Handle h
, short *index
)
277 char* pPascalBytes
= (*h
+ *index
);
278 sal_uInt32 nLen
= (unsigned char)pPascalBytes
[0];
279 rtl::OStringBuffer
aBuf( nLen
);
280 aBuf
.append( pPascalBytes
+1, nLen
);
282 return rtl::OStringToOUString( aBuf
.makeStringAndClear(), RTL_TEXTENCODING_UTF8
);
285 static int parseMimeResource( CFBundleRef i_xBundle
,
287 const rtl::OUString
& i_rBundleURL
,
288 list
< PluginDescription
* >& io_rDescriptions
)
291 // just to hurt our eyes more there is an alternative mimetype function plus the possibility
292 // of a resource fork. Must be a case of think different.
299 BPSupportedMIMETypes aMIMETypesStrangeStruct
= {kBPSupportedMIMETypesStructVers_1
, NULL
, NULL
};
301 BP_GetSupportedMIMETypesUPP pBPGetSupp
= (BP_GetSupportedMIMETypesUPP
)osl_getAsciiFunctionSymbol( i_rMod
, "BP_GetSupportedMIMETypes" );
303 noErr
== pBPGetSupp( &aMIMETypesStrangeStruct
, 0 ) &&
304 aMIMETypesStrangeStruct
.typeStrings
)
306 HLock( aMIMETypesStrangeStruct
.typeStrings
);
307 if( aMIMETypesStrangeStruct
.infoStrings
) // it's possible some plugins have infoStrings missing
308 HLock( aMIMETypesStrangeStruct
.infoStrings
);
310 else // Try to get data from the resource fork
312 xRes
= CFBundleOpenBundleResourceMap( i_xBundle
);
315 aMIMETypesStrangeStruct
.typeStrings
= Get1Resource('STR#', 128);
316 if( aMIMETypesStrangeStruct
.typeStrings
)
318 DetachResource( aMIMETypesStrangeStruct
.typeStrings
);
319 HLock( aMIMETypesStrangeStruct
.typeStrings
);
320 aMIMETypesStrangeStruct
.infoStrings
= Get1Resource('STR#', 127);
321 if( aMIMETypesStrangeStruct
.infoStrings
)
323 DetachResource( aMIMETypesStrangeStruct
.infoStrings
);
324 HLock( aMIMETypesStrangeStruct
.infoStrings
);
330 if( aMIMETypesStrangeStruct
.typeStrings
&& aMIMETypesStrangeStruct
.infoStrings
)
332 short nVariantCount
= (**(short**)aMIMETypesStrangeStruct
.typeStrings
) / 2;
333 // Fill in the info struct based on the data in the BPSupportedMIMETypes struct
334 // this is an array of pascal string of unknown (!) encoding
335 // whoever thought of this deserves a fair beating
337 short descriptionIndex
= 2;
338 for( int i
= 0; i
< nVariantCount
; i
++ )
340 rtl::OUString aMimetype
= GetNextPluginStringFromHandle( aMIMETypesStrangeStruct
.typeStrings
, &mimeIndex
);
341 rtl::OUString aExtLine
= GetNextPluginStringFromHandle( aMIMETypesStrangeStruct
.typeStrings
, &mimeIndex
);
342 rtl::OUString aDescription
;
343 if( aMIMETypesStrangeStruct
.infoStrings
)
344 aDescription
= GetNextPluginStringFromHandle( aMIMETypesStrangeStruct
.infoStrings
, &descriptionIndex
);
346 // create extension list string
347 sal_Int32 nExtIndex
= 0;
348 OUStringBuffer aExtension
;
349 while( nExtIndex
!= -1 )
351 OUString aExt
= aExtLine
.getToken( 0, ',', nExtIndex
);
352 if( aExt
.indexOfAsciiL( "*.", 2 ) != 0 )
353 aExtension
.appendAscii( "*." );
354 aExtension
.append( aExt
);
355 if( nExtIndex
!= -1 )
356 aExtension
.append( sal_Unicode(';') );
359 PluginDescription
* pNew
= new PluginDescription
;
360 // set plugin name (path to library)
361 pNew
->PluginName
= i_rBundleURL
;
363 pNew
->Mimetype
= aMimetype
;
364 // set extension line
365 pNew
->Extension
= aExtension
.makeStringAndClear();
367 pNew
->Description
= aDescription
;
368 io_rDescriptions
.push_back( pNew
);
371 #if OSL_DEBUG_LEVEL > 1
373 "Inserting from resource:\n"
376 " Description: %s\n",
377 OUStringToOString( pNew
->Mimetype
, RTL_TEXTENCODING_UTF8
).getStr(),
378 OUStringToOString( pNew
->Extension
, RTL_TEXTENCODING_UTF8
).getStr(),
379 OUStringToOString( pNew
->Description
, RTL_TEXTENCODING_UTF8
).getStr()
387 if( aMIMETypesStrangeStruct
.typeStrings
)
389 HUnlock( aMIMETypesStrangeStruct
.typeStrings
);
390 DisposeHandle( aMIMETypesStrangeStruct
.typeStrings
);
392 if( aMIMETypesStrangeStruct
.infoStrings
)
394 HUnlock( aMIMETypesStrangeStruct
.infoStrings
);
395 DisposeHandle( aMIMETypesStrangeStruct
.infoStrings
);
398 CFBundleCloseBundleResourceMap( i_xBundle
, xRes
);
403 // check some known bad plugins to avoid crashes
404 static bool checkBlackList( CFBundleRef i_xBundle
)
406 rtl::OUString aBundleName
;
407 CFTypeRef bundlename
= CFBundleGetValueForInfoDictionaryKey( i_xBundle
, CFSTR("CFBundleName"));
408 if( bundlename
&& CFGetTypeID(bundlename
) == CFStringGetTypeID() )
409 aBundleName
= getString( static_cast<CFStringRef
>(bundlename
) );
411 rtl::OUString aBundleVersion
;
412 CFTypeRef bundleversion
= CFBundleGetValueForInfoDictionaryKey( i_xBundle
, CFSTR("CFBundleVersion"));
413 if( bundleversion
&& CFGetTypeID(bundleversion
) == CFStringGetTypeID() )
414 aBundleVersion
= getString( static_cast<CFStringRef
>(bundleversion
) );
416 // #i102735# VLC plugin prior to 1.0 tends to crash
417 if( aBundleName
.equalsAscii( "VLC Plug-in" ) )
419 sal_Int32 nIndex
= 0;
420 rtl::OUString
aMajor( aBundleVersion
.getToken( 0, '.', nIndex
) );
421 if( aMajor
.toInt32() < 1 )
423 #if OSL_DEBUG_LEVEL > 1
424 fprintf( stderr
, "rejecting VCL plugin (%s %s)\n",
425 rtl::OUStringToOString( aBundleName
, RTL_TEXTENCODING_UTF8
).getStr(),
426 rtl::OUStringToOString( aBundleVersion
, RTL_TEXTENCODING_UTF8
).getStr()
436 static int getPluginDescriptions( CFBundleRef i_xBundle
, list
< PluginDescription
* >& io_rDescriptions
)
438 int nDescriptions
= 0;
440 return nDescriptions
;
442 if( checkBlackList( i_xBundle
) )
445 rtl::OUString aPlugURL
;
446 CFURLRef xURL
= CFBundleCopyBundleURL( i_xBundle
);
447 aPlugURL
= getString( xURL
);
450 #if OSL_DEBUG_LEVEL > 1
451 rtl::OUString aPlugName
, aPlugDescription
;
452 CFTypeRef name
= CFBundleGetValueForInfoDictionaryKey( i_xBundle
, CFSTR("WebPluginName"));
453 if( name
&& CFGetTypeID(name
) == CFStringGetTypeID() )
454 aPlugName
= getString( static_cast<CFStringRef
>(name
) );
456 CFTypeRef description
= CFBundleGetValueForInfoDictionaryKey( i_xBundle
, CFSTR("WebPluginDescription"));
457 if( description
&& CFGetTypeID(description
) == CFStringGetTypeID() )
458 aPlugDescription
= getString( static_cast<CFStringRef
>(description
) );
460 fprintf( stderr
, "URL: %s\nname: %s\ndescription: %s\n",
461 rtl::OUStringToOString( aPlugURL
, RTL_TEXTENCODING_UTF8
).getStr(),
462 rtl::OUStringToOString( aPlugName
, RTL_TEXTENCODING_UTF8
).getStr(),
463 rtl::OUStringToOString( aPlugDescription
, RTL_TEXTENCODING_UTF8
).getStr()
468 // get location of plugin library
469 CFURLRef xLibURL
= CFBundleCopyExecutableURL( i_xBundle
);
472 // get the file system path
473 rtl::OUString
aModuleURL( CFURLtoOSLURL( xLibURL
) );
474 CFRelease( xLibURL
);
476 #if OSL_DEBUG_LEVEL > 1
477 fprintf( stderr
, "exec URL = %s\n", rtl::OUStringToOString( aModuleURL
, RTL_TEXTENCODING_UTF8
).getStr() );
480 /* TODO: originally the C++ wrapper for oslModule was used here, but that led to
481 mysterious crashes in the event loop (pointing to heap corruption). Why using
482 the C style oslModule should fix this is completely unknown. It may be that
483 we have just hidden the heap corruption a little more.
485 oslModule aMod
= osl_loadModule( aModuleURL
.pData
, SAL_LOADMODULE_DEFAULT
);
489 // check for at least the init function of a plugin
490 if( ! osl_getAsciiFunctionSymbol( aMod
, "NP_Initialize") &&
491 ! osl_getAsciiFunctionSymbol( aMod
, "NP_GetEntryPoints" ) )
496 // ask the plist of the bundle for mimetypes
497 nDescriptions
= parsePlist( i_xBundle
, aPlugURL
, io_rDescriptions
);
500 osl_unloadModule( aMod
);
501 return nDescriptions
;
504 // resolve the symbol that might get us the mimetypes
505 const char* (*pGetMimeDescription
)() = (const char*(*)())osl_getAsciiFunctionSymbol( aMod
, "_NP_GetMIMEDescription" );
506 if( pGetMimeDescription
)
508 const char* pMime
= pGetMimeDescription();
511 nDescriptions
= parseMimeString( aPlugURL
, io_rDescriptions
, pMime
);
514 osl_unloadModule( aMod
);
515 return nDescriptions
;
520 // and as last resort check the resource of the bundle
521 nDescriptions
= parseMimeResource( i_xBundle
, aMod
, aPlugURL
, io_rDescriptions
);
522 osl_unloadModule( aMod
);
524 return nDescriptions
;
527 // Unix specific implementation
528 static bool CheckPlugin( const rtl::OUString
& rPath
, list
< PluginDescription
* >& rDescriptions
)
530 #if OSL_DEBUG_LEVEL > 1
531 fprintf( stderr
, "Trying path %s ... ", rtl::OUStringToOString( rPath
, RTL_TEXTENCODING_UTF8
).getStr() );
533 CFURLRef xURL
= createURL( rPath
);
535 CFArrayRef xBundles
= CFBundleCreateBundlesFromDirectory( NULL
, xURL
, CFSTR("plugin") );
539 CFIndex nBundles
= CFArrayGetCount( xBundles
);
541 #if OSL_DEBUG_LEVEL > 1
542 fprintf( stderr
, "got %d bundles\n", (int)nBundles
);
545 int nDescriptions
= 0;
546 for( CFIndex i
= 0; i
< nBundles
; i
++ )
548 CFBundleRef xBundle
= (CFBundleRef
)CFArrayGetValueAtIndex( xBundles
, i
);
549 nDescriptions
+= getPluginDescriptions( xBundle
, rDescriptions
);
551 CFRelease( xBundle
);
553 CFRelease( xBundles
);
556 return nDescriptions
> 0;
559 static rtl::OUString
FindFolderURL( FSVolumeRefNum vRefNum
, OSType folderType
)
564 OSErr err
= FSFindFolder( vRefNum
, folderType
, kDontCreateFolder
, &aFSRef
);
567 CFURLRef xURL
= CFURLCreateFromFSRef( NULL
, &aFSRef
);
568 aRet
= getString( xURL
);
575 Sequence
<PluginDescription
> XPluginManager_Impl::getPluginDescriptions() throw()
577 static Sequence
<PluginDescription
> aDescriptions
;
578 static BOOL bHavePlugins
= FALSE
;
581 std::list
<PluginDescription
*> aPlugins
;
583 static const char* pNPXPluginPath
= getenv( "MOZ_PLUGIN_PATH" );
586 std::list
< rtl::OUString
> aPaths
;
589 CFMutableStringRef xMutableString
= CFStringCreateMutable( NULL
, 0 );
590 CFStringAppendCString( xMutableString
, pNPXPluginPath
, kCFStringEncodingUTF8
);
591 CFURLRef xURL
= CFURLCreateWithFileSystemPath( NULL
, xMutableString
, kCFURLPOSIXPathStyle
, true );
592 CFRelease( xMutableString
);
593 aPaths
.push_back( getString( xURL
) );
597 rtl::OUString aPath
= FindFolderURL( kUserDomain
, kInternetPlugInFolderType
);
598 if( aPath
.getLength() )
599 aPaths
.push_back( aPath
);
600 aPath
= FindFolderURL( kLocalDomain
, kInternetPlugInFolderType
);
601 if( aPath
.getLength() )
602 aPaths
.push_back( aPath
);
603 aPath
= FindFolderURL( kOnAppropriateDisk
, kInternetPlugInFolderType
);
604 if( aPath
.getLength() )
605 aPaths
.push_back( aPath
);
608 const Sequence
< ::rtl::OUString
>& rPaths( PluginManager::getAdditionalSearchPaths() );
609 for( sal_Int32 i
= 0; i
< rPaths
.getLength(); i
++ )
611 aPaths
.push_back( getURLFromPath( rPaths
.getConstArray()[i
] ) );
614 for( std::list
< rtl::OUString
>::const_iterator it
= aPaths
.begin(); it
!= aPaths
.end(); ++it
)
616 rtl::OUString
aPath( *it
);
617 #if OSL_DEBUG_LEVEL > 1
618 fprintf( stderr
, "check path %s\n", rtl::OUStringToOString( *it
, RTL_TEXTENCODING_UTF8
).getStr() );
620 CheckPlugin( aPath
, aPlugins
);
624 // create return value
625 aDescriptions
= Sequence
<PluginDescription
>( aPlugins
.size() );
626 #if OSL_DEBUG_LEVEL > 1
627 fprintf( stderr
, "found %d plugins\n", (int)aPlugins
.size() );
629 list
<PluginDescription
*>::iterator iter
;
631 for( iter
= aPlugins
.begin(); iter
!= aPlugins
.end(); ++iter
)
633 aDescriptions
.getArray()[ nPlug
++ ] = **iter
;
639 return aDescriptions
;