1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
3 * This file is part of the LibreOffice project.
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
9 * This file incorporates work covered by the following license notice:
11 * Licensed to the Apache Software Foundation (ASF) under one or more
12 * contributor license agreements. See the NOTICE file distributed
13 * with this work for additional information regarding copyright
14 * ownership. The ASF licenses this file to you under the Apache
15 * License, Version 2.0 (the "License"); you may not use this file
16 * except in compliance with the License. You may obtain a copy of
17 * the License at http://www.apache.org/licenses/LICENSE-2.0 .
20 #include "osl/diagnose.h"
22 #include "com/sun/star/task/XInteractionAbort.hpp"
23 #include "com/sun/star/ucb/XWebDAVCommandEnvironment.hpp"
25 #include "ucbhelper/simpleauthenticationrequest.hxx"
26 #include "comphelper/seekableinput.hxx"
28 #include "DAVAuthListenerImpl.hxx"
29 #include "DAVResourceAccess.hxx"
31 using namespace http_dav_ucp
;
32 using namespace com::sun::star
;
34 //=========================================================================
35 //=========================================================================
37 // DAVAuthListener_Impl Implementation.
39 //=========================================================================
40 //=========================================================================
42 //=========================================================================
44 int DAVAuthListener_Impl::authenticate(
45 const ::rtl::OUString
& inRealm
,
46 const ::rtl::OUString
& inHostName
,
47 ::rtl::OUString
& inoutUserName
,
48 ::rtl::OUString
& outPassWord
,
49 sal_Bool bCanUseSystemCredentials
,
50 sal_Bool bUsePreviousCredentials
)
54 uno::Reference
< task::XInteractionHandler
> xIH
55 = m_xEnv
->getInteractionHandler();
59 // Providing previously retrieved credentials will cause the password
60 // container to reject these. Thus, the credential input dialog will be shown again.
61 // #102871# - Supply username and password from previous try.
62 // Password container service depends on this!
63 if ( inoutUserName
.getLength() == 0 && bUsePreviousCredentials
)
64 inoutUserName
= m_aPrevUsername
;
66 if ( outPassWord
.getLength() == 0 && bUsePreviousCredentials
)
67 outPassWord
= m_aPrevPassword
;
69 rtl::Reference
< ucbhelper::SimpleAuthenticationRequest
> xRequest
70 = new ucbhelper::SimpleAuthenticationRequest(
71 m_aURL
, inHostName
, inRealm
, inoutUserName
,
72 outPassWord
, ::rtl::OUString(),
73 true /*bAllowPersistentStoring*/,
74 bCanUseSystemCredentials
);
75 xIH
->handle( xRequest
.get() );
77 rtl::Reference
< ucbhelper::InteractionContinuation
> xSelection
78 = xRequest
->getSelection();
80 if ( xSelection
.is() )
82 // Handler handled the request.
83 uno::Reference
< task::XInteractionAbort
> xAbort(
84 xSelection
.get(), uno::UNO_QUERY
);
88 ucbhelper::InteractionSupplyAuthentication
> & xSupp
89 = xRequest
->getAuthenticationSupplier();
91 sal_Bool bUseSystemCredentials
= sal_False
;
93 if ( bCanUseSystemCredentials
)
95 = xSupp
->getUseSystemCredentials();
97 if ( bUseSystemCredentials
)
99 // This is the (strange) way to tell neon to use
100 // system credentials.
101 inoutUserName
= rtl::OUString();
102 outPassWord
= rtl::OUString();
106 inoutUserName
= xSupp
->getUserName();
107 outPassWord
= xSupp
->getPassword();
110 // #102871# - Remember username and password.
111 m_aPrevUsername
= inoutUserName
;
112 m_aPrevPassword
= outPassWord
;
124 //=========================================================================
125 //=========================================================================
127 // DAVResourceAccess Implementation.
129 //=========================================================================
130 //=========================================================================
132 //=========================================================================
133 DAVResourceAccess::DAVResourceAccess(
134 const uno::Reference
< lang::XMultiServiceFactory
> & rSMgr
,
135 rtl::Reference
< DAVSessionFactory
> const & rSessionFactory
,
136 const rtl::OUString
& rURL
)
138 m_xSessionFactory( rSessionFactory
),
143 //=========================================================================
144 DAVResourceAccess::DAVResourceAccess( const DAVResourceAccess
& rOther
)
145 : m_aURL( rOther
.m_aURL
),
146 m_aPath( rOther
.m_aPath
),
147 m_xSession( rOther
.m_xSession
),
148 m_xSessionFactory( rOther
.m_xSessionFactory
),
149 m_xSMgr( rOther
.m_xSMgr
),
150 m_aRedirectURIs( rOther
.m_aRedirectURIs
)
154 //=========================================================================
155 DAVResourceAccess
& DAVResourceAccess::operator=(
156 const DAVResourceAccess
& rOther
)
158 m_aURL
= rOther
.m_aURL
;
159 m_aPath
= rOther
.m_aPath
;
160 m_xSession
= rOther
.m_xSession
;
161 m_xSessionFactory
= rOther
.m_xSessionFactory
;
162 m_xSMgr
= rOther
.m_xSMgr
;
163 m_aRedirectURIs
= rOther
.m_aRedirectURIs
;
168 //=========================================================================
169 void DAVResourceAccess::PROPFIND(
171 const std::vector
< rtl::OUString
> & rPropertyNames
,
172 std::vector
< DAVResource
> & rResources
,
173 const uno::Reference
< ucb::XCommandEnvironment
> & xEnv
)
174 throw( DAVException
)
185 DAVRequestHeaders aHeaders
;
187 getUserRequestHeaders( xEnv
,
189 rtl::OUString::createFromAscii(
193 m_xSession
->PROPFIND( getRequestURI(),
197 DAVRequestEnvironment(
199 new DAVAuthListener_Impl( xEnv
, m_aURL
),
202 catch ( DAVException
& e
)
205 bRetry
= handleException( e
, errorCount
);
213 //=========================================================================
214 void DAVResourceAccess::PROPFIND(
216 std::vector
< DAVResourceInfo
> & rResInfo
,
217 const uno::Reference
< ucb::XCommandEnvironment
> & xEnv
)
218 throw( DAVException
)
229 DAVRequestHeaders aHeaders
;
230 getUserRequestHeaders( xEnv
,
232 rtl::OUString::createFromAscii(
236 m_xSession
->PROPFIND( getRequestURI(),
239 DAVRequestEnvironment(
241 new DAVAuthListener_Impl( xEnv
, m_aURL
),
244 catch ( DAVException
& e
)
247 bRetry
= handleException( e
, errorCount
);
255 //=========================================================================
256 void DAVResourceAccess::PROPPATCH(
257 const std::vector
< ProppatchValue
>& rValues
,
258 const uno::Reference
< ucb::XCommandEnvironment
>& xEnv
)
259 throw( DAVException
)
270 DAVRequestHeaders aHeaders
;
271 getUserRequestHeaders( xEnv
,
273 rtl::OUString::createFromAscii(
277 m_xSession
->PROPPATCH( getRequestURI(),
279 DAVRequestEnvironment(
281 new DAVAuthListener_Impl( xEnv
, m_aURL
),
284 catch ( DAVException
& e
)
287 bRetry
= handleException( e
, errorCount
);
295 //=========================================================================
296 void DAVResourceAccess::HEAD(
297 const std::vector
< rtl::OUString
> & rHeaderNames
,
298 DAVResource
& rResource
,
299 const uno::Reference
< ucb::XCommandEnvironment
>& xEnv
)
300 throw( DAVException
)
311 DAVRequestHeaders aHeaders
;
312 getUserRequestHeaders( xEnv
,
314 rtl::OUString::createFromAscii( "HEAD" ),
317 m_xSession
->HEAD( getRequestURI(),
320 DAVRequestEnvironment(
322 new DAVAuthListener_Impl( xEnv
, m_aURL
),
325 catch ( DAVException
& e
)
328 bRetry
= handleException( e
, errorCount
);
336 //=========================================================================
337 uno::Reference
< io::XInputStream
> DAVResourceAccess::GET(
338 const uno::Reference
< ucb::XCommandEnvironment
> & xEnv
)
339 throw( DAVException
)
343 uno::Reference
< io::XInputStream
> xStream
;
351 DAVRequestHeaders aHeaders
;
352 getUserRequestHeaders( xEnv
,
354 rtl::OUString::createFromAscii( "GET" ),
357 xStream
= m_xSession
->GET( getRequestURI(),
358 DAVRequestEnvironment(
360 new DAVAuthListener_Impl(
364 catch ( DAVException
& e
)
367 bRetry
= handleException( e
, errorCount
);
377 //=========================================================================
378 void DAVResourceAccess::GET(
379 uno::Reference
< io::XOutputStream
> & rStream
,
380 const uno::Reference
< ucb::XCommandEnvironment
> & xEnv
)
381 throw( DAVException
)
392 DAVRequestHeaders aHeaders
;
393 getUserRequestHeaders( xEnv
,
395 rtl::OUString::createFromAscii( "GET" ),
398 m_xSession
->GET( getRequestURI(),
400 DAVRequestEnvironment(
402 new DAVAuthListener_Impl( xEnv
, m_aURL
),
405 catch ( DAVException
& e
)
408 bRetry
= handleException( e
, errorCount
);
416 //=========================================================================
417 uno::Reference
< io::XInputStream
> DAVResourceAccess::GET(
418 const std::vector
< rtl::OUString
> & rHeaderNames
,
419 DAVResource
& rResource
,
420 const uno::Reference
< ucb::XCommandEnvironment
> & xEnv
)
421 throw( DAVException
)
425 uno::Reference
< io::XInputStream
> xStream
;
433 DAVRequestHeaders aHeaders
;
434 getUserRequestHeaders( xEnv
,
436 rtl::OUString::createFromAscii( "GET" ),
439 xStream
= m_xSession
->GET( getRequestURI(),
442 DAVRequestEnvironment(
444 new DAVAuthListener_Impl(
448 catch ( DAVException
& e
)
451 bRetry
= handleException( e
, errorCount
);
461 //=========================================================================
462 void DAVResourceAccess::GET(
463 uno::Reference
< io::XOutputStream
> & rStream
,
464 const std::vector
< rtl::OUString
> & rHeaderNames
,
465 DAVResource
& rResource
,
466 const uno::Reference
< ucb::XCommandEnvironment
> & xEnv
)
467 throw( DAVException
)
478 DAVRequestHeaders aHeaders
;
479 getUserRequestHeaders( xEnv
,
481 rtl::OUString::createFromAscii( "GET" ),
484 m_xSession
->GET( getRequestURI(),
488 DAVRequestEnvironment(
490 new DAVAuthListener_Impl( xEnv
, m_aURL
),
493 catch ( DAVException
& e
)
496 bRetry
= handleException( e
, errorCount
);
504 //=========================================================================
505 void DAVResourceAccess::abort()
506 throw( DAVException
)
508 // 17.11.09 (tkr): abort currently disabled caused by issue i106766
510 // m_xSession->abort();
511 OSL_TRACE( "Not implemented. -> #i106766#" );
514 //=========================================================================
517 void resetInputStream( const uno::Reference
< io::XInputStream
> & rStream
)
518 throw( DAVException
)
522 uno::Reference
< io::XSeekable
> xSeekable(
523 rStream
, uno::UNO_QUERY
);
524 if ( xSeekable
.is() )
526 xSeekable
->seek( 0 );
530 catch ( lang::IllegalArgumentException
const & )
533 catch ( io::IOException
const & )
537 throw DAVException( DAVException::DAV_INVALID_ARG
);
542 //=========================================================================
543 void DAVResourceAccess::PUT(
544 const uno::Reference
< io::XInputStream
> & rStream
,
545 const uno::Reference
< ucb::XCommandEnvironment
> & xEnv
)
546 throw( DAVException
)
550 // Make stream seekable, if it not. Needed, if request must be retried.
551 uno::Reference
< io::XInputStream
> xSeekableStream
552 = comphelper::OSeekableInputWrapper::CheckSeekableCanWrap(
560 resetInputStream( xSeekableStream
);
565 DAVRequestHeaders aHeaders
;
566 getUserRequestHeaders( xEnv
,
568 rtl::OUString::createFromAscii( "PUT" ),
571 m_xSession
->PUT( getRequestURI(),
573 DAVRequestEnvironment(
575 new DAVAuthListener_Impl( xEnv
, m_aURL
),
578 catch ( DAVException
& e
)
581 bRetry
= handleException( e
, errorCount
);
589 //=========================================================================
590 uno::Reference
< io::XInputStream
> DAVResourceAccess::POST(
591 const rtl::OUString
& rContentType
,
592 const rtl::OUString
& rReferer
,
593 const uno::Reference
< io::XInputStream
> & rInputStream
,
594 const uno::Reference
< ucb::XCommandEnvironment
>& xEnv
)
595 throw ( DAVException
)
599 // Make stream seekable, if it not. Needed, if request must be retried.
600 uno::Reference
< io::XInputStream
> xSeekableStream
601 = comphelper::OSeekableInputWrapper::CheckSeekableCanWrap(
602 rInputStream
, m_xSMgr
);
604 uno::Reference
< io::XInputStream
> xStream
;
611 resetInputStream( xSeekableStream
);
617 DAVRequestHeaders aHeaders
;
618 getUserRequestHeaders( xEnv
,
620 rtl::OUString::createFromAscii( "POST" ),
623 xStream
= m_xSession
->POST( getRequestURI(),
627 DAVRequestEnvironment(
629 new DAVAuthListener_Impl(
633 catch ( DAVException
& e
)
636 bRetry
= handleException( e
, errorCount
);
640 if ( e
.getError() == DAVException::DAV_HTTP_REDIRECT
)
642 // #i74980# - Upon POST redirect, do a GET.
652 //=========================================================================
653 void DAVResourceAccess::POST(
654 const rtl::OUString
& rContentType
,
655 const rtl::OUString
& rReferer
,
656 const uno::Reference
< io::XInputStream
> & rInputStream
,
657 uno::Reference
< io::XOutputStream
> & rOutputStream
,
658 const uno::Reference
< ucb::XCommandEnvironment
>& xEnv
)
659 throw ( DAVException
)
663 // Make stream seekable, if it not. Needed, if request must be retried.
664 uno::Reference
< io::XInputStream
> xSeekableStream
665 = comphelper::OSeekableInputWrapper::CheckSeekableCanWrap(
666 rInputStream
, m_xSMgr
);
674 resetInputStream( xSeekableStream
);
680 DAVRequestHeaders aHeaders
;
681 getUserRequestHeaders( xEnv
,
683 rtl::OUString::createFromAscii( "POST" ),
686 m_xSession
->POST( getRequestURI(),
691 DAVRequestEnvironment(
693 new DAVAuthListener_Impl( xEnv
, m_aURL
),
696 catch ( DAVException
& e
)
699 bRetry
= handleException( e
, errorCount
);
703 if ( e
.getError() == DAVException::DAV_HTTP_REDIRECT
)
705 // #i74980# - Upon POST redirect, do a GET.
706 GET( rOutputStream
, xEnv
);
714 //=========================================================================
715 void DAVResourceAccess::MKCOL(
716 const uno::Reference
< ucb::XCommandEnvironment
> & xEnv
)
717 throw( DAVException
)
728 DAVRequestHeaders aHeaders
;
729 getUserRequestHeaders( xEnv
,
731 rtl::OUString::createFromAscii( "MKCOL" ),
734 m_xSession
->MKCOL( getRequestURI(),
735 DAVRequestEnvironment(
737 new DAVAuthListener_Impl( xEnv
, m_aURL
),
740 catch ( DAVException
& e
)
743 bRetry
= handleException( e
, errorCount
);
751 //=========================================================================
752 void DAVResourceAccess::COPY(
753 const ::rtl::OUString
& rSourcePath
,
754 const ::rtl::OUString
& rDestinationURI
,
756 const uno::Reference
< ucb::XCommandEnvironment
> & xEnv
)
757 throw( DAVException
)
768 DAVRequestHeaders aHeaders
;
769 getUserRequestHeaders( xEnv
,
771 rtl::OUString::createFromAscii( "COPY" ),
774 m_xSession
->COPY( rSourcePath
,
776 DAVRequestEnvironment(
778 new DAVAuthListener_Impl( xEnv
, m_aURL
),
782 catch ( DAVException
& e
)
785 bRetry
= handleException( e
, errorCount
);
793 //=========================================================================
794 void DAVResourceAccess::MOVE(
795 const ::rtl::OUString
& rSourcePath
,
796 const ::rtl::OUString
& rDestinationURI
,
798 const uno::Reference
< ucb::XCommandEnvironment
> & xEnv
)
799 throw( DAVException
)
810 DAVRequestHeaders aHeaders
;
811 getUserRequestHeaders( xEnv
,
813 rtl::OUString::createFromAscii( "MOVE" ),
816 m_xSession
->MOVE( rSourcePath
,
818 DAVRequestEnvironment(
820 new DAVAuthListener_Impl( xEnv
, m_aURL
),
824 catch ( DAVException
& e
)
827 bRetry
= handleException( e
, errorCount
);
835 //=========================================================================
836 void DAVResourceAccess::DESTROY(
837 const uno::Reference
< ucb::XCommandEnvironment
> & xEnv
)
838 throw( DAVException
)
849 DAVRequestHeaders aHeaders
;
850 getUserRequestHeaders( xEnv
,
852 rtl::OUString::createFromAscii(
856 m_xSession
->DESTROY( getRequestURI(),
857 DAVRequestEnvironment(
859 new DAVAuthListener_Impl( xEnv
, m_aURL
),
862 catch ( DAVException
& e
)
865 bRetry
= handleException( e
, errorCount
);
873 //=========================================================================
875 void DAVResourceAccess::LOCK(
877 const uno::Reference
< ucb::XCommandEnvironment
> & xEnv
)
878 throw ( DAVException
)
889 DAVRequestHeaders aHeaders
;
890 getUserRequestHeaders( xEnv
,
892 rtl::OUString::createFromAscii( "LOCK" ),
895 m_xSession
->LOCK( getRequestURI(),
897 DAVRequestEnvironment(
899 new DAVAuthListener_Impl( xEnv
, m_aURL
),
902 catch ( DAVException
& e
)
905 bRetry
= handleException( e
, errorCount
);
913 #if 0 // currently not used, but please don't remove code
914 //=========================================================================
915 // refresh existing lock.
916 sal_Int64
DAVResourceAccess::LOCK(
918 const uno::Reference
< ucb::XCommandEnvironment
> & xEnv
)
919 throw ( DAVException
)
923 sal_Int64 nNewTimeout
= 0;
931 DAVRequestHeaders aHeaders
;
932 getUserRequestHeaders( xEnv
,
934 rtl::OUString::createFromAscii( "LOCK" ),
937 nNewTimeout
= m_xSession
->LOCK( getRequestURI(),
939 DAVRequestEnvironment(
941 new DAVAuthListener_Impl(
945 catch ( DAVException
& e
)
948 bRetry
= handleException( e
, errorCount
);
959 //=========================================================================
960 void DAVResourceAccess::UNLOCK(
961 const uno::Reference
< ucb::XCommandEnvironment
> & xEnv
)
962 throw ( DAVException
)
973 DAVRequestHeaders aHeaders
;
974 getUserRequestHeaders( xEnv
,
976 rtl::OUString::createFromAscii( "UNLOCK" ),
979 m_xSession
->UNLOCK( getRequestURI(),
980 DAVRequestEnvironment(
982 new DAVAuthListener_Impl( xEnv
, m_aURL
),
985 catch ( DAVException
& e
)
988 bRetry
= handleException( e
, errorCount
);
996 //=========================================================================
997 void DAVResourceAccess::setURL( const rtl::OUString
& rNewURL
)
998 throw( DAVException
)
1000 osl::Guard
< osl::Mutex
> aGuard( m_aMutex
);
1002 m_aPath
= rtl::OUString(); // Next initialize() will create new session.
1005 //=========================================================================
1006 // init dav session and path
1007 void DAVResourceAccess::initialize()
1008 throw ( DAVException
)
1010 osl::Guard
< osl::Mutex
> aGuard( m_aMutex
);
1011 if ( m_aPath
.getLength() == 0 )
1013 SerfUri
aURI( m_aURL
);
1014 rtl::OUString
aPath( aURI
.GetPath() );
1016 /* #134089# - Check URI */
1017 if ( !aPath
.getLength() )
1018 throw DAVException( DAVException::DAV_INVALID_ARG
);
1020 /* #134089# - Check URI */
1021 if ( !aURI
.GetHost().getLength() )
1022 throw DAVException( DAVException::DAV_INVALID_ARG
);
1024 if ( !m_xSession
.is() || !m_xSession
->CanUse( m_aURL
) )
1028 // create new webdav session
1030 = m_xSessionFactory
->createDAVSession( m_aURL
, m_xSMgr
);
1032 if ( !m_xSession
.is() )
1036 // Own URI is needed for redirect cycle detection.
1037 m_aRedirectURIs
.push_back( aURI
);
1042 // Not only the path has to be encoded
1043 m_aURL
= aURI
.GetURI();
1047 //=========================================================================
1048 const rtl::OUString
& DAVResourceAccess::getRequestURI() const
1050 OSL_ENSURE( m_xSession
.is(),
1051 "DAVResourceAccess::getRequestURI - Not initialized!" );
1053 // In case a proxy is used we have to use the absolute URI for a request.
1054 if ( m_xSession
->UsesProxy() )
1060 //=========================================================================
1062 void DAVResourceAccess::getUserRequestHeaders(
1063 const uno::Reference
< ucb::XCommandEnvironment
> & xEnv
,
1064 const rtl::OUString
& rURI
,
1065 const rtl::OUString
& rMethod
,
1066 DAVRequestHeaders
& rRequestHeaders
)
1070 uno::Reference
< ucb::XWebDAVCommandEnvironment
> xDAVEnv(
1071 xEnv
, uno::UNO_QUERY
);
1075 uno::Sequence
< beans::NamedValue
> aRequestHeaders
1076 = xDAVEnv
->getUserRequestHeaders( rURI
, rMethod
);
1078 for ( sal_Int32 n
= 0; n
< aRequestHeaders
.getLength(); ++n
)
1080 rtl::OUString aValue
;
1081 sal_Bool isString
= aRequestHeaders
[ n
].Value
>>= aValue
;
1085 OSL_ENSURE( isString
,
1086 "DAVResourceAccess::getUserRequestHeaders :"
1087 "Value is not a string! Ignoring..." );
1090 rRequestHeaders
.push_back(
1091 DAVRequestHeader( aRequestHeaders
[ n
].Name
, aValue
) );
1097 //=========================================================================
1098 sal_Bool
DAVResourceAccess::detectRedirectCycle(
1099 const rtl::OUString
& rRedirectURL
)
1100 throw ( DAVException
)
1102 osl::Guard
< osl::Mutex
> aGuard( m_aMutex
);
1104 SerfUri
aUri( rRedirectURL
);
1106 std::vector
< SerfUri
>::const_iterator it
= m_aRedirectURIs
.begin();
1107 std::vector
< SerfUri
>::const_iterator end
= m_aRedirectURIs
.end();
1111 if ( aUri
== (*it
) )
1120 //=========================================================================
1121 void DAVResourceAccess::resetUri()
1123 osl::Guard
< osl::Mutex
> aGuard( m_aMutex
);
1124 if ( ! m_aRedirectURIs
.empty() )
1126 std::vector
< SerfUri
>::const_iterator it
= m_aRedirectURIs
.begin();
1128 SerfUri
aUri( (*it
) );
1129 m_aRedirectURIs
.clear();
1130 setURL ( aUri
.GetURI() );
1135 //=========================================================================
1136 sal_Bool
DAVResourceAccess::handleException( DAVException
& e
, int errorCount
)
1137 throw ( DAVException
)
1139 switch ( e
.getError() )
1141 case DAVException::DAV_HTTP_REDIRECT
:
1142 if ( !detectRedirectCycle( e
.getData() ) )
1144 // set new URL and path.
1145 setURL( e
.getData() );
1150 // --> tkr #67048# copy & paste images doesn't display.
1151 // if we have a bad connection try again. Up to three times.
1152 case DAVException::DAV_HTTP_ERROR
:
1153 // retry up to three times, if not a client-side error.
1154 if ( ( e
.getStatus() < 400 || e
.getStatus() >= 500 ||
1155 e
.getStatus() == 413 ) &&
1162 // --> tkr: if connection has said retry then retry!
1163 case DAVException::DAV_HTTP_RETRY
:
1167 return sal_False
; // Abort
1171 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */