diff --git a/qa/libcmis/test-commons.cxx b/qa/libcmis/test-commons.cxx
index df271e9..6555358 100644
--- a/qa/libcmis/test-commons.cxx
+++ b/qa/libcmis/test-commons.cxx
@@ -64,11 +64,14 @@ class CommonsTest : public CppUnit::TestFixture
// Methods that should never be called
void objectTypeNocallTest();
+ void httpSessionCRLFInjectionTest();
+
CPPUNIT_TEST_SUITE( CommonsTest );
CPPUNIT_TEST( oauth2DataCopyTest );
CPPUNIT_TEST( oauth2HandlerCopyTest );
CPPUNIT_TEST( objectTypeCopyTest );
CPPUNIT_TEST( objectTypeNocallTest );
+ CPPUNIT_TEST( httpSessionCRLFInjectionTest );
CPPUNIT_TEST_SUITE_END( );
};
@@ -107,32 +110,22 @@ void CommonsTest::oauth2HandlerCopyTest( )
{
OAuth2DataPtr data( new OAuth2Data ( "url", "token", "scope", "redirect",
"clientid", "clientsecret" ) );
- HttpSession session( "user", "pass" );
- OAuth2Handler handler( &session, data );
+ HttpSession source( "user", "pass" );
+ HttpSession owner( "user", "pass" );
+ OAuth2Handler handler( &source, data );
handler.m_access = "access";
handler.m_refresh = "refresh";
handler.m_oauth2Parser = &DummyOAuth2Parser;
- {
- OAuth2Handler copy;
- copy = handler;
-
- CPPUNIT_ASSERT_EQUAL( &session, copy.m_session );
- CPPUNIT_ASSERT_EQUAL( data, copy.m_data );
- CPPUNIT_ASSERT_EQUAL( handler.m_access, copy.m_access );
- CPPUNIT_ASSERT_EQUAL( handler.m_refresh, copy.m_refresh );
- CPPUNIT_ASSERT_EQUAL( &DummyOAuth2Parser, copy.m_oauth2Parser );
- }
-
- {
- OAuth2Handler copy( handler );
+ OAuth2Handler copy( &owner, handler );
- CPPUNIT_ASSERT_EQUAL( &session, copy.m_session );
- CPPUNIT_ASSERT_EQUAL( data, copy.m_data );
- CPPUNIT_ASSERT_EQUAL( handler.m_access, copy.m_access );
- CPPUNIT_ASSERT_EQUAL( handler.m_refresh, copy.m_refresh );
- CPPUNIT_ASSERT_EQUAL( &DummyOAuth2Parser, copy.m_oauth2Parser );
- }
+ // m_session must follow the new owner, not the source, so that the
+ // copy keeps working after `source` is destroyed.
+ CPPUNIT_ASSERT_EQUAL( &owner, copy.m_session );
+ CPPUNIT_ASSERT_EQUAL( data, copy.m_data );
+ CPPUNIT_ASSERT_EQUAL( handler.m_access, copy.m_access );
+ CPPUNIT_ASSERT_EQUAL( handler.m_refresh, copy.m_refresh );
+ CPPUNIT_ASSERT_EQUAL( &DummyOAuth2Parser, copy.m_oauth2Parser );
}
static void assertObjectTypeEquals( const ObjectType& expected, const ObjectType& actual )
@@ -220,4 +213,21 @@ void CommonsTest::objectTypeNocallTest( )
}
}
+void CommonsTest::httpSessionCRLFInjectionTest( )
+{
+ HttpSession session( "user", "pass" );
+ std::string body( "hi" );
+ std::istringstream is( body );
+ try
+ {
+ session.httpPostRequest( "http://example.test/",
+ is,
+ "text/plain\r\nX-Injected: yes" );
+ CPPUNIT_FAIL( "httpPostRequest with CRLF in contentType should throw" );
+ }
+ catch ( const Exception& )
+ {
+ }
+}
+
CPPUNIT_TEST_SUITE_REGISTRATION( CommonsTest );
diff --git a/qa/libcmis/test-decoder.cxx b/qa/libcmis/test-decoder.cxx
index c7b4748..e44d7e5 100644
--- a/qa/libcmis/test-decoder.cxx
+++ b/qa/libcmis/test-decoder.cxx
@@ -61,6 +61,7 @@ class DecoderTest : public CppUnit::TestFixture
void base64DecodePaddedBlockTest( );
void base64DecodeNoEqualsPaddedBlockTest( );
void base64DecodeSplitRunsTest( );
+ void base64DecodeExcessPaddingTest( );
void base64EncodeSimpleBlockTest( );
void base64EncodePaddedBlockTest( );
@@ -74,6 +75,7 @@ class DecoderTest : public CppUnit::TestFixture
CPPUNIT_TEST( base64DecodePaddedBlockTest );
CPPUNIT_TEST( base64DecodeNoEqualsPaddedBlockTest );
CPPUNIT_TEST( base64DecodeSplitRunsTest );
+ CPPUNIT_TEST( base64DecodeExcessPaddingTest );
CPPUNIT_TEST( base64EncodeSimpleBlockTest );
CPPUNIT_TEST( base64EncodePaddedBlockTest );
CPPUNIT_TEST( base64EncodeSplitRunsTest );
@@ -183,6 +185,18 @@ void DecoderTest::base64DecodeSplitRunsTest( )
CPPUNIT_ASSERT_EQUAL( string( "pleasure." ), getActual( ) );
}
+void DecoderTest::base64DecodeExcessPaddingTest( )
+{
+ // A corrupt stream with more '=' than a base64 block can have. We must
+ // stay within the decodeBase64 buffer bounds. Ignore the block and the
+ // previous valid run of "pleasure." must still get output.
+ data->setEncoding( BASE64_ENCODING );
+ string input( "cGxlYXN1cmUu========" );
+ data->decode( ( void* )input.c_str( ), 1, input.size( ) );
+ data->finish( );
+ CPPUNIT_ASSERT_EQUAL( string( "pleasure." ), getActual( ) );
+}
+
void DecoderTest::base64EncodeSimpleBlockTest( )
{
data->setEncoding( BASE64_ENCODING );
diff --git a/qa/libcmis/test-jsonutils.cxx b/qa/libcmis/test-jsonutils.cxx
index 4a9fd49..22cfeab 100644
--- a/qa/libcmis/test-jsonutils.cxx
+++ b/qa/libcmis/test-jsonutils.cxx
@@ -56,6 +56,7 @@ class JsonTest : public CppUnit::TestFixture
{
public:
void parseTest( );
+ void parseDeepNestingTest( );
void parseTypeTest( );
void createFromPropertyTest( );
void createFromPropertiesTest( );
@@ -64,6 +65,7 @@ class JsonTest : public CppUnit::TestFixture
CPPUNIT_TEST_SUITE( JsonTest );
CPPUNIT_TEST( parseTest );
+ CPPUNIT_TEST( parseDeepNestingTest );
CPPUNIT_TEST( parseTypeTest );
CPPUNIT_TEST( createFromPropertyTest );
CPPUNIT_TEST( createFromPropertiesTest );
@@ -114,6 +116,18 @@ void JsonTest::parseTest( )
CPPUNIT_ASSERT_EQUAL_MESSAGE( "Wrong editable", string( "true"), editable );
}
+void JsonTest::parseDeepNestingTest( )
+{
+ // A response with thousands of [[[[...]]]] would blow the stack inside
+ // boost::property_tree::json_parser::read_json. Json::parse must
+ // refuse to feed it to read_json, falling back to the malformed-input
+ // path.
+ string deep( 5000, '[' );
+ deep.append( 5000, ']' );
+ Json json = Json::parse( deep );
+ CPPUNIT_ASSERT_EQUAL( deep, json.toString( ) );
+}
+
void JsonTest::parseTypeTest( )
{
Json json = parseFile( DATA_DIR "/gdrive/jsontest-good.json" );
diff --git a/qa/libcmis/test-soap.cxx b/qa/libcmis/test-soap.cxx
index e401971..55b1e4a 100644
--- a/qa/libcmis/test-soap.cxx
+++ b/qa/libcmis/test-soap.cxx
@@ -69,12 +69,14 @@ class SoapTest : public CppUnit::TestFixture
void parseResponseTest( );
void parseResponseXmlTest( );
void parseResponseFaultTest( );
+ void parseResponseNoNamespaceTest( );
// RelatedMultipart tests
void serializeMultipartSimpleTest( );
void serializeMultipartComplexTest( );
void parseMultipartTest( );
+ void parseMultipartEmptyFieldsTest( );
void getStreamFromNodeXopTest( );
void getStreamFromNodeBase64Test( );
@@ -91,10 +93,12 @@ class SoapTest : public CppUnit::TestFixture
CPPUNIT_TEST( parseResponseTest );
CPPUNIT_TEST( parseResponseXmlTest );
CPPUNIT_TEST( parseResponseFaultTest );
+ CPPUNIT_TEST( parseResponseNoNamespaceTest );
CPPUNIT_TEST( serializeMultipartSimpleTest );
CPPUNIT_TEST( serializeMultipartComplexTest );
CPPUNIT_TEST( parseMultipartTest );
+ CPPUNIT_TEST( parseMultipartEmptyFieldsTest );
CPPUNIT_TEST( getStreamFromNodeXopTest );
CPPUNIT_TEST( getStreamFromNodeBase64Test );
@@ -316,6 +320,21 @@ void SoapTest::parseResponseFaultTest( )
}
}
+void SoapTest::parseResponseNoNamespaceTest( )
+{
+ SoapResponseFactory factory;
+ factory.setMapping( getTestMapping() );
+ factory.setNamespaces( getTestNamespaces( ) );
+ factory.setDetailMapping( getTestDetailMapping( ) );
+
+ string xml = ""
+ ""
+ "";
+
+ vector< SoapResponsePtr > actual = factory.parseResponse( xml );
+ CPPUNIT_ASSERT_EQUAL_MESSAGE( "Wrong number of responses", size_t( 0 ), actual.size( ) );
+}
+
void SoapTest::serializeMultipartSimpleTest( )
{
string partName = "data";
@@ -443,6 +462,32 @@ void SoapTest::parseMultipartTest( )
CPPUNIT_ASSERT_EQUAL_MESSAGE( "Wrong part2 part content", part2Content, actualPart2->getContent( ) );
}
+void SoapTest::parseMultipartEmptyFieldsTest( )
+{
+ // Body, contentType params and Content-ID can all be empty in a
+ // bogus response.
+ string boundary = "abc";
+
+ {
+ // Empty start= and an empty body.
+ string contentType = "multipart/related; start=\"\"; boundary=\"" + boundary + "\"";
+ RelatedMultipart multipart( "", contentType );
+ CPPUNIT_ASSERT_EQUAL( string( ), multipart.getStartId( ) );
+ }
+
+ {
+ // A part with an empty Content-Id and an empty body.
+ string body = "\r\n--" + boundary + "\r\n" +
+ "Content-Id: \r\n" +
+ "Content-Type: text/plain\r\n" +
+ "\r\n" +
+ "\r\n--" + boundary + "--\r\n";
+ string contentType = "multipart/related; boundary=\"" + boundary + "\"";
+ RelatedMultipart multipart( body, contentType );
+ CPPUNIT_ASSERT_EQUAL( boundary, multipart.getBoundary( ) );
+ }
+}
+
void SoapTest::getStreamFromNodeXopTest( )
{
// Create the test multipart
diff --git a/src/cmis-client.cxx b/src/cmis-client.cxx
index 943f5d5..2f9297b 100644
--- a/src/cmis-client.cxx
+++ b/src/cmis-client.cxx
@@ -481,7 +481,15 @@ void CmisClient::execute( )
streamId = m_vm["stream-id"].as();
boost::shared_ptr< istream > in = document->getContentStream( streamId );
- ofstream out( document->getContentFilename().c_str() );
+ string filename = document->getContentFilename();
+ if ( filename.empty() ||
+ filename == "." || filename == ".." ||
+ filename.find( '/' ) != string::npos ||
+ filename.find( '\\' ) != string::npos )
+ {
+ throw CommandException( "Refusing server-supplied filename: " + filename );
+ }
+ ofstream out( filename.c_str() );
out << in->rdbuf();
out.close();
}
diff --git a/src/libcmis-c/document.cxx b/src/libcmis-c/document.cxx
index 74d04d9..05f296b 100644
--- a/src/libcmis-c/document.cxx
+++ b/src/libcmis-c/document.cxx
@@ -148,15 +148,14 @@ void libcmis_document_getContentStream(
boost::shared_ptr< istream > stream = doc->getContentStream( );
stream->seekg( 0 );
- int bufSize = 2048;
- char* buf = new char[ bufSize ];
+ size_t bufSize = 2048;
+ std::vector< char > buf( bufSize );
while ( !stream->eof( ) )
{
- stream->read( buf, bufSize );
+ stream->read( buf.data(), bufSize );
size_t read = stream->gcount( );
- writeFn( ( const void * )buf, size_t( 1 ), read, userData );
+ writeFn( ( const void * )buf.data(), size_t( 1 ), read, userData );
}
- delete[] buf;
}
}
catch ( const libcmis::Exception& e )
@@ -203,14 +202,13 @@ void libcmis_document_setContentStream(
boost::shared_ptr< std::ostream > stream( new stringstream( ) );
size_t bufSize = 2048;
- char* buf = new char[ bufSize ];
+ std::vector< char > buf( bufSize );
size_t read = 0;
do
{
- read = readFn( ( void * )buf, size_t( 1 ), bufSize, userData );
- stream->write( buf, read );
+ read = readFn( ( void * )buf.data(), size_t( 1 ), bufSize, userData );
+ stream->write( buf.data(), read );
} while ( read == bufSize );
- delete[] buf;
DocumentPtr doc = dynamic_pointer_cast< libcmis::Document >( document->handle );
if ( doc )
@@ -358,14 +356,13 @@ libcmis_DocumentPtr libcmis_document_checkIn(
boost::shared_ptr< std::ostream > stream( new stringstream( ) );
size_t bufSize = 2048;
- char * buf = new char[ bufSize ];
+ std::vector< char > buf( bufSize );
size_t read = 0;
do
{
- read = readFn( ( void * )buf, size_t( 1 ), bufSize, userData );
- stream->write( buf, read );
+ read = readFn( ( void * )buf.data(), size_t( 1 ), bufSize, userData );
+ stream->write( buf.data(), read );
} while ( read == bufSize );
- delete[] buf;
// Create the property map
PropertyPtrMap propertiesMap;
diff --git a/src/libcmis-c/folder.cxx b/src/libcmis-c/folder.cxx
index 8d7555a..c34be9a 100644
--- a/src/libcmis-c/folder.cxx
+++ b/src/libcmis-c/folder.cxx
@@ -276,14 +276,13 @@ libcmis_DocumentPtr libcmis_folder_createDocument(
boost::shared_ptr< std::ostream > stream( new stringstream( ) );
size_t bufSize = 2048;
- char* buf = new char[ bufSize ];
+ std::vector< char > buf( bufSize );
size_t read = 0;
do
{
- read = readFn( ( void * )buf, size_t( 1 ), bufSize, userData );
- stream->write( buf, read );
+ read = readFn( ( void * )buf.data(), size_t( 1 ), bufSize, userData );
+ stream->write( buf.data(), read );
} while ( read == bufSize );
- delete[] buf;
// Create the property map
PropertyPtrMap propertiesMap;
diff --git a/src/libcmis/allowable-actions.cxx b/src/libcmis/allowable-actions.cxx
index 533069c..62fe51b 100644
--- a/src/libcmis/allowable-actions.cxx
+++ b/src/libcmis/allowable-actions.cxx
@@ -54,8 +54,11 @@ namespace libcmis
try
{
xmlChar* content = xmlNodeGetContent( node );
- m_enabled = parseBool( string( ( char* )content ) );
- xmlFree( content );
+ if ( content )
+ {
+ m_enabled = parseBool( string( ( char* )content ) );
+ xmlFree( content );
+ }
}
catch ( const Exception& )
{
diff --git a/src/libcmis/atom-document.cxx b/src/libcmis/atom-document.cxx
index dc6c8cf..9b8bd5f 100644
--- a/src/libcmis/atom-document.cxx
+++ b/src/libcmis/atom-document.cxx
@@ -29,6 +29,7 @@
#include "atom-document.hxx"
#include
+#include
#include
#include
@@ -249,7 +250,7 @@ libcmis::DocumentPtr AtomDocument::checkOut( )
AtomObject::writeAtomEntry( writer, props, stream, string( ) );
xmlTextWriterEndDocument( writer );
- string str( ( const char * )xmlBufferContent( buf ) );
+ string str( ( const char * )xmlBufferContent( buf ), xmlBufferLength( buf ) );
istringstream is( str );
xmlFreeTextWriter( writer );
@@ -277,12 +278,11 @@ libcmis::DocumentPtr AtomDocument::checkOut( )
}
string respBuf = resp->getStream( )->str();
- xmlDocPtr doc = xmlReadMemory( respBuf.c_str(), respBuf.size(), checkedOutUrl.c_str(), NULL, 0 );
- if ( NULL == doc )
+ std::shared_ptr< xmlDoc > doc( xmlReadMemory( respBuf.c_str(), respBuf.size(), checkedOutUrl.c_str(), NULL, 0 ), xmlFreeDoc );
+ if ( !doc )
throw libcmis::Exception( "Failed to parse object infos" );
- libcmis::ObjectPtr created = getSession( )->createObjectFromEntryDoc( doc, AtomPubSession::RESULT_DOCUMENT );
- xmlFreeDoc( doc );
+ libcmis::ObjectPtr created = getSession( )->createObjectFromEntryDoc( doc.get(), AtomPubSession::RESULT_DOCUMENT );
libcmis::DocumentPtr pwc = boost::dynamic_pointer_cast< libcmis::Document >( created );
if ( !pwc.get( ) )
@@ -352,7 +352,7 @@ libcmis::DocumentPtr AtomDocument::checkIn( bool isMajor, string comment,
AtomObject::writeAtomEntry( writer, properties, stream, contentType );
xmlTextWriterEndDocument( writer );
- string str( ( const char * )xmlBufferContent( buf ) );
+ string str( ( const char * )xmlBufferContent( buf ), xmlBufferLength( buf ) );
istringstream is( str );
xmlFreeTextWriter( writer );
@@ -373,16 +373,15 @@ libcmis::DocumentPtr AtomDocument::checkIn( bool isMajor, string comment,
// Get the returned entry and update using it
string respBuf = response->getStream( )->str( );
- xmlDocPtr doc = xmlReadMemory( respBuf.c_str(), respBuf.size(), checkInUrl.c_str(), NULL, 0 );
- if ( NULL == doc )
+ std::shared_ptr< xmlDoc > doc( xmlReadMemory( respBuf.c_str(), respBuf.size(), checkInUrl.c_str(), NULL, 0 ), xmlFreeDoc );
+ if ( !doc )
throw libcmis::Exception( "Failed to parse object infos" );
- libcmis::ObjectPtr newVersion = getSession( )->createObjectFromEntryDoc( doc, AtomPubSession::RESULT_DOCUMENT );
+ libcmis::ObjectPtr newVersion = getSession( )->createObjectFromEntryDoc( doc.get(), AtomPubSession::RESULT_DOCUMENT );
if ( newVersion->getId( ) == getId( ) )
- refreshImpl( doc );
- xmlFreeDoc( doc );
+ refreshImpl( doc.get() );
return boost::dynamic_pointer_cast< libcmis::Document >( newVersion );
}
@@ -468,8 +467,11 @@ void AtomDocument::extractInfos( xmlDocPtr doc )
{
xmlNodePtr contentNd = xpathObj->nodesetval->nodeTab[0];
xmlChar* src = xmlGetProp( contentNd, BAD_CAST( "src" ) );
- m_contentUrl = string( ( char* ) src );
- xmlFree( src );
+ if ( src )
+ {
+ m_contentUrl = string( ( char* ) src );
+ xmlFree( src );
+ }
}
xmlXPathFreeObject( xpathObj );
}
diff --git a/src/libcmis/atom-folder.cxx b/src/libcmis/atom-folder.cxx
index 7c479ff..28fc1b7 100644
--- a/src/libcmis/atom-folder.cxx
+++ b/src/libcmis/atom-folder.cxx
@@ -152,7 +152,7 @@ libcmis::FolderPtr AtomFolder::createFolder( const PropertyPtrMap& properties )
AtomObject::writeAtomEntry( writer, properties, stream, string( ) );
xmlTextWriterEndDocument( writer );
- string str( ( const char * )xmlBufferContent( buf ) );
+ string str( ( const char * )xmlBufferContent( buf ), xmlBufferLength( buf ) );
istringstream is( str );
xmlFreeTextWriter( writer );
diff --git a/src/libcmis/atom-object-type.cxx b/src/libcmis/atom-object-type.cxx
index 8f50d04..3c89f7b 100644
--- a/src/libcmis/atom-object-type.cxx
+++ b/src/libcmis/atom-object-type.cxx
@@ -28,6 +28,7 @@
#include "atom-object-type.hxx"
+#include
#include
#include
@@ -99,8 +100,8 @@ vector< libcmis::ObjectTypePtr > AtomObjectType::getChildren( )
void AtomObjectType::refreshImpl( xmlDocPtr doc )
{
- bool createdDoc = ( NULL == doc );
- if ( createdDoc )
+ std::shared_ptr< xmlDoc > ownedDoc;
+ if ( NULL == doc )
{
string pattern = m_session->getAtomRepository()->getUriTemplate( UriTemplate::TypeById );
map< string, string > vars;
@@ -125,16 +126,15 @@ void AtomObjectType::refreshImpl( xmlDocPtr doc )
throw e.getCmisException( );
}
- doc = xmlReadMemory( buf.c_str(), buf.size(), m_selfUrl.c_str(), NULL, 0 );
+ ownedDoc.reset( xmlReadMemory( buf.c_str(), buf.size(), m_selfUrl.c_str(), NULL, 0 ), xmlFreeDoc );
- if ( NULL == doc )
+ if ( !ownedDoc )
throw libcmis::Exception( "Failed to parse object infos" );
+
+ doc = ownedDoc.get();
}
extractInfos( doc );
-
- if ( createdDoc )
- xmlFreeDoc( doc );
}
void AtomObjectType::extractInfos( xmlDocPtr doc )
diff --git a/src/libcmis/atom-object.cxx b/src/libcmis/atom-object.cxx
index 5564745..b4d6a23 100644
--- a/src/libcmis/atom-object.cxx
+++ b/src/libcmis/atom-object.cxx
@@ -30,6 +30,7 @@
#include
#include
+#include
#include
#include
@@ -130,7 +131,7 @@ libcmis::ObjectPtr AtomObject::updateProperties( const PropertyPtrMap& propertie
AtomObject::writeAtomEntry( writer, properties, stream, string( ) );
xmlTextWriterEndDocument( writer );
- string str( ( const char * )xmlBufferContent( buf ) );
+ string str( ( const char * )xmlBufferContent( buf ), xmlBufferLength( buf ) );
istringstream is( str );
xmlFreeTextWriter( writer );
@@ -149,14 +150,13 @@ libcmis::ObjectPtr AtomObject::updateProperties( const PropertyPtrMap& propertie
}
string respBuf = response->getStream( )->str( );
- xmlDocPtr doc = xmlReadMemory( respBuf.c_str(), respBuf.size(), getInfosUrl().c_str(), NULL, 0 );
- if ( NULL == doc )
+ std::shared_ptr< xmlDoc > doc( xmlReadMemory( respBuf.c_str(), respBuf.size(), getInfosUrl().c_str(), NULL, 0 ), xmlFreeDoc );
+ if ( !doc )
throw libcmis::Exception( "Failed to parse object infos" );
- libcmis::ObjectPtr updated = getSession( )->createObjectFromEntryDoc( doc );
+ libcmis::ObjectPtr updated = getSession( )->createObjectFromEntryDoc( doc.get() );
if ( updated->getId( ) == getId( ) )
- refreshImpl( doc );
- xmlFreeDoc( doc );
+ refreshImpl( doc.get() );
return updated;
}
@@ -173,12 +173,10 @@ libcmis::AllowableActionsPtr AtomObject::getAllowableActions( )
{
libcmis::HttpResponsePtr response = getSession()->httpGetRequest( link->getHref() );
string buf = response->getStream()->str();
- xmlDocPtr doc = xmlReadMemory( buf.c_str(), buf.size(), link->getHref().c_str(), NULL, 0 );
- xmlNodePtr actionsNode = xmlDocGetRootElement( doc );
+ std::shared_ptr< xmlDoc > doc( xmlReadMemory( buf.c_str(), buf.size(), link->getHref().c_str(), NULL, 0 ), xmlFreeDoc );
+ xmlNodePtr actionsNode = xmlDocGetRootElement( doc.get() );
if ( actionsNode )
m_allowableActions.reset( new libcmis::AllowableActions( actionsNode ) );
-
- xmlFreeDoc( doc );
}
catch ( CurlException& )
{
@@ -191,8 +189,8 @@ libcmis::AllowableActionsPtr AtomObject::getAllowableActions( )
void AtomObject::refreshImpl( xmlDocPtr doc )
{
- bool createdDoc = ( NULL == doc );
- if ( createdDoc )
+ std::shared_ptr< xmlDoc > ownedDoc;
+ if ( NULL == doc )
{
string buf;
try
@@ -204,11 +202,12 @@ void AtomObject::refreshImpl( xmlDocPtr doc )
throw e.getCmisException( );
}
- doc = xmlReadMemory( buf.c_str(), buf.size(), getInfosUrl().c_str(), NULL, 0 );
+ ownedDoc.reset( xmlReadMemory( buf.c_str(), buf.size(), getInfosUrl().c_str(), NULL, 0 ), xmlFreeDoc );
- if ( NULL == doc )
+ if ( !ownedDoc )
throw libcmis::Exception( "Failed to parse object infos" );
+ doc = ownedDoc.get();
}
// Cleanup the structures before setting them again
@@ -219,9 +218,6 @@ void AtomObject::refreshImpl( xmlDocPtr doc )
m_renditions.clear( );
extractInfos( doc );
-
- if ( createdDoc )
- xmlFreeDoc( doc );
}
void AtomObject::remove( bool allVersions )
@@ -272,7 +268,7 @@ void AtomObject::move( boost::shared_ptr< libcmis::Folder > source, boost::share
AtomObject::writeAtomEntry( writer, getProperties( ), stream, string( ) );
xmlTextWriterEndDocument( writer );
- string str( ( const char * )xmlBufferContent( buf ) );
+ string str( ( const char * )xmlBufferContent( buf ), xmlBufferLength( buf ) );
istringstream is( str );
xmlFreeTextWriter( writer );
xmlBufferFree( buf );
@@ -302,11 +298,10 @@ void AtomObject::move( boost::shared_ptr< libcmis::Folder > source, boost::share
// refresh self from response
string respBuf = response->getStream( )->str( );
- xmlDocPtr doc = xmlReadMemory( respBuf.c_str(), respBuf.size(), getInfosUrl().c_str(), NULL, 0 );
- if ( NULL == doc )
+ std::shared_ptr< xmlDoc > doc( xmlReadMemory( respBuf.c_str(), respBuf.size(), getInfosUrl().c_str(), NULL, 0 ), xmlFreeDoc );
+ if ( !doc )
throw libcmis::Exception( "Failed to parse object infos" );
- refreshImpl( doc );
- xmlFreeDoc( doc );
+ refreshImpl( doc.get() );
}
string AtomObject::getInfosUrl( )
diff --git a/src/libcmis/base-session.cxx b/src/libcmis/base-session.cxx
index 9489440..ded61dc 100644
--- a/src/libcmis/base-session.cxx
+++ b/src/libcmis/base-session.cxx
@@ -123,7 +123,7 @@ void BaseSession::setNoSSLCertificateCheck( bool noCheck )
void BaseSession::setOAuth2Data( libcmis::OAuth2DataPtr oauth2 )
{
- m_oauth2Handler = new OAuth2Handler( this, oauth2 );
+ m_oauth2Handler.reset( new OAuth2Handler( this, oauth2 ) );
m_oauth2Handler->setOAuth2Parser( OAuth2Providers::getOAuth2Parser( getBindingUrl( ) ) );
oauth2Authenticate( );
diff --git a/src/libcmis/gdrive-session.cxx b/src/libcmis/gdrive-session.cxx
index 5155338..b794a83 100644
--- a/src/libcmis/gdrive-session.cxx
+++ b/src/libcmis/gdrive-session.cxx
@@ -69,7 +69,7 @@ GDriveSession::~GDriveSession()
void GDriveSession::setOAuth2Data( libcmis::OAuth2DataPtr oauth2 )
{
- m_oauth2Handler = new OAuth2Handler( this, oauth2 );
+ m_oauth2Handler.reset( new OAuth2Handler( this, oauth2 ) );
m_oauth2Handler->setOAuth2Parser( OAuth2Providers::getOAuth2Parser( getBindingUrl( ) ) );
oauth2Authenticate( );
diff --git a/src/libcmis/http-session.cxx b/src/libcmis/http-session.cxx
index 044a153..d2187d0 100644
--- a/src/libcmis/http-session.cxx
+++ b/src/libcmis/http-session.cxx
@@ -156,13 +156,34 @@ namespace
};
}
+namespace libcmis {
+
+void rejectControlChars( const string& s, const char* what )
+{
+ if ( s.find_first_of( "\r\n", 0 ) != string::npos ||
+ s.find( '\0' ) != string::npos )
+ throw Exception( string( "Invalid character in " ) + what );
+}
+
+void applyTransferLimits( CURL* curlHandle )
+{
+ constexpr curl_off_t MAX_RESPONSE_SIZE = 1024L * 1024L * 1024L; // 1 GiB
+ constexpr long LOW_SPEED_TIME_SECS = 30L;
+
+ curl_easy_setopt( curlHandle, CURLOPT_MAXFILESIZE_LARGE, MAX_RESPONSE_SIZE );
+ curl_easy_setopt( curlHandle, CURLOPT_LOW_SPEED_LIMIT, 1L );
+ curl_easy_setopt( curlHandle, CURLOPT_LOW_SPEED_TIME, LOW_SPEED_TIME_SECS );
+}
+
+}
+
HttpSession::HttpSession( string username, string password, bool noSslCheck,
libcmis::OAuth2DataPtr oauth2, bool verbose,
libcmis::CurlInitProtocolsFunction initProtocolsFunction) :
m_curlHandle( NULL ),
m_CurlInitProtocolsFunction(initProtocolsFunction),
m_no100Continue( false ),
- m_oauth2Handler( NULL ),
+ m_oauth2Handler( ),
m_username( username ),
m_password( password ),
m_authProvided( false ),
@@ -185,7 +206,9 @@ HttpSession::HttpSession( const HttpSession& copy ) :
m_curlHandle( NULL ),
m_CurlInitProtocolsFunction( copy.m_CurlInitProtocolsFunction ),
m_no100Continue( copy.m_no100Continue ),
- m_oauth2Handler( copy.m_oauth2Handler ),
+ m_oauth2Handler( copy.m_oauth2Handler ?
+ new OAuth2Handler( this, *copy.m_oauth2Handler ) :
+ nullptr ),
m_username( copy.m_username ),
m_password( copy.m_password ),
m_authProvided( copy.m_authProvided ),
@@ -204,7 +227,7 @@ HttpSession::HttpSession( const HttpSession& copy ) :
HttpSession::HttpSession( ) :
m_curlHandle( NULL ),
m_no100Continue( false ),
- m_oauth2Handler( NULL ),
+ m_oauth2Handler( ),
m_username( ),
m_password( ),
m_authProvided( false ),
@@ -227,7 +250,9 @@ HttpSession& HttpSession::operator=( const HttpSession& copy )
m_curlHandle = NULL;
m_CurlInitProtocolsFunction = copy.m_CurlInitProtocolsFunction;
m_no100Continue = copy.m_no100Continue;
- m_oauth2Handler = copy.m_oauth2Handler;
+ m_oauth2Handler.reset( copy.m_oauth2Handler ?
+ new OAuth2Handler( this, *copy.m_oauth2Handler ) :
+ nullptr );
m_username = copy.m_username;
m_password = copy.m_password;
m_authProvided = copy.m_authProvided;
@@ -250,7 +275,6 @@ HttpSession::~HttpSession( )
{
if ( NULL != m_curlHandle )
curl_easy_cleanup( m_curlHandle );
- delete( m_oauth2Handler );
}
string& HttpSession::getUsername( )
@@ -662,6 +686,12 @@ void HttpSession::checkCredentials( )
void HttpSession::httpRunRequest( string url, vector< string > headers, bool redirect )
{
+ libcmis::rejectControlChars( url, "URL" );
+ for ( vector< string >::const_iterator it = headers.begin( ); it != headers.end( ); ++it )
+ libcmis::rejectControlChars( *it, "header" );
+
+ libcmis::applyTransferLimits( m_curlHandle );
+
// Redirect
curl_easy_setopt( m_curlHandle, CURLOPT_FOLLOWLOCATION, redirect);
@@ -679,13 +709,17 @@ void HttpSession::httpRunRequest( string url, vector< string > headers, bool red
// If we are using OAuth2, then add the proper header with token to authenticate
// Otherwise, just set the credentials normally using in libcurl options
- if ( m_oauth2Handler != NULL && !m_oauth2Handler->getHttpHeader( ).empty() )
+ if ( m_oauth2Handler && !m_oauth2Handler->getHttpHeader( ).empty() )
{
+ string oauthHeader = m_oauth2Handler->getHttpHeader();
+ libcmis::rejectControlChars( oauthHeader, "OAuth header" );
headers_slist.reset(curl_slist_append(headers_slist.release(),
- m_oauth2Handler->getHttpHeader().c_str()));
+ oauthHeader.c_str()));
}
else if ( !getUsername().empty() )
{
+ libcmis::rejectControlChars( getUsername(), "username" );
+ libcmis::rejectControlChars( getPassword(), "password" );
curl_easy_setopt( m_curlHandle, CURLOPT_HTTPAUTH, m_authMethod );
curl_easy_setopt( m_curlHandle, CURLOPT_USERNAME, getUsername().c_str() );
curl_easy_setopt( m_curlHandle, CURLOPT_PASSWORD, getPassword().c_str() );
@@ -829,7 +863,7 @@ long HttpSession::getHttpStatus( )
void HttpSession::setOAuth2Data( libcmis::OAuth2DataPtr oauth2 )
{
- m_oauth2Handler = new OAuth2Handler( this, oauth2 );
+ m_oauth2Handler.reset( new OAuth2Handler( this, oauth2 ) );
}
void HttpSession::oauth2Authenticate( )
diff --git a/src/libcmis/http-session.hxx b/src/libcmis/http-session.hxx
index 6ee8ad8..24f2327 100644
--- a/src/libcmis/http-session.hxx
+++ b/src/libcmis/http-session.hxx
@@ -29,6 +29,7 @@
#define _HTTP_SESSION_HXX_
#include
+#include
#include
#include
#include
@@ -45,6 +46,19 @@ class OAuth2Handler;
namespace libcmis {
typedef void(*CurlInitProtocolsFunction)(CURL *);
+
+ /** Throw libcmis::Exception if s contains CR, LF or NUL. libcurl
+ started rejecting these in CURLOPT_URL at 7.84 and in
+ curl_slist_append at 7.86.
+ */
+ void rejectControlChars(const std::string& s, const char* what);
+
+ /** Bound a single HTTP request so a remote that wanted to grow the
+ client's buffer or pin its connection cannot do so indefinitely.
+ Cap response size at 1 GB and abort the transfer if there is no
+ data transfer for 30s.
+ */
+ void applyTransferLimits(CURL* curlHandle);
}
class CurlException : public std::exception
@@ -101,7 +115,7 @@ class HttpSession
private:
bool m_no100Continue;
protected:
- OAuth2Handler* m_oauth2Handler;
+ std::unique_ptr m_oauth2Handler;
std::string m_username;
std::string m_password;
bool m_authProvided;
@@ -168,12 +182,12 @@ class HttpSession
virtual void httpRunRequest( std::string url,
std::vector< std::string > headers = std::vector< std::string > ( ),
bool redirect = true );
+ void initProtocols( );
private:
void checkCredentials( );
void checkOAuth2( std::string url );
void oauth2Refresh( );
- void initProtocols( );
};
#endif
diff --git a/src/libcmis/json-utils.cxx b/src/libcmis/json-utils.cxx
index ff0be7c..7de3b83 100644
--- a/src/libcmis/json-utils.cxx
+++ b/src/libcmis/json-utils.cxx
@@ -178,19 +178,59 @@ void Json::add( const Json& json )
}
}
+namespace
+{
+ // boost::property_tree::json_parser::read_json has with no nesting cap.
+ // Refuse to feed read_json input whose bracket/brace nesting goes past
+ // some arbitrary large value.
+ constexpr size_t MAX_JSON_DEPTH = 100;
+
+ bool exceedsMaxJsonDepth( const std::string& s )
+ {
+ size_t depth = 0;
+ bool inString = false;
+ bool escape = false;
+ for ( char c : s )
+ {
+ if ( escape ) { escape = false; continue; }
+ if ( inString )
+ {
+ if ( c == '\\' ) escape = true;
+ else if ( c == '"' ) inString = false;
+ continue;
+ }
+ if ( c == '"' ) inString = true;
+ else if ( c == '[' || c == '{' )
+ {
+ if ( ++depth > MAX_JSON_DEPTH ) return true;
+ }
+ else if ( ( c == ']' || c == '}' ) && depth > 0 ) --depth;
+ }
+ return false;
+ }
+
+ Json malformedJsonFallback( const std::string& str )
+ {
+ return Json( str.c_str( ) );
+ }
+}
+
Json Json::parse( const string& str )
{
+ if ( exceedsMaxJsonDepth( str ) )
+ return malformedJsonFallback( str );
+
ptree pTree;
- std::stringstream ss( str );
+ std::stringstream ss( str );
if ( ss.good( ) )
{
- try
+ try
{
property_tree::json_parser::read_json( ss, pTree );
}
catch ( boost::exception const& )
{
- return Json( str.c_str( ) );
+ return malformedJsonFallback( str );
}
}
return Json( pTree );
diff --git a/src/libcmis/oauth2-handler.cxx b/src/libcmis/oauth2-handler.cxx
index a340b80..6c7374e 100644
--- a/src/libcmis/oauth2-handler.cxx
+++ b/src/libcmis/oauth2-handler.cxx
@@ -30,6 +30,7 @@
#include "oauth2-handler.hxx"
+#include
#include
#include
@@ -38,6 +39,29 @@
using namespace std;
+namespace
+{
+ // Turn an RFC 6749 token-endpoint error response into a libcmis::Exception
+ // whose message names the failure mode the server reported (the standard
+ // "error" and "error_description" fields), so callers can distinguish
+ // "invalid_grant: refresh token expired" from "your network is down."
+ libcmis::Exception tokenError( const string& what, const Json& jresp )
+ {
+ string err = jresp[ "error" ].toString( );
+ string desc = jresp[ "error_description" ].toString( );
+ string msg = what;
+ if ( !err.empty( ) )
+ {
+ msg += " (";
+ msg += err;
+ if ( !desc.empty( ) )
+ msg += ": " + desc;
+ msg += ")";
+ }
+ return libcmis::Exception( msg );
+ }
+}
+
OAuth2Handler::OAuth2Handler(HttpSession* session, libcmis::OAuth2DataPtr data) :
m_session( session ),
m_data( data ),
@@ -50,8 +74,8 @@ OAuth2Handler::OAuth2Handler(HttpSession* session, libcmis::OAuth2DataPtr data)
}
-OAuth2Handler::OAuth2Handler( const OAuth2Handler& copy ) :
- m_session( copy.m_session ),
+OAuth2Handler::OAuth2Handler( HttpSession* session, const OAuth2Handler& copy ) :
+ m_session( session ),
m_data( copy.m_data ),
m_access( copy.m_access ),
m_refresh( copy.m_refresh ),
@@ -69,20 +93,6 @@ OAuth2Handler::OAuth2Handler( ):
m_data.reset( new libcmis::OAuth2Data() );
}
-OAuth2Handler& OAuth2Handler::operator=( const OAuth2Handler& copy )
-{
- if ( this != © )
- {
- m_session = copy.m_session;
- m_data = copy.m_data;
- m_access = copy.m_access;
- m_refresh = copy.m_refresh;
- m_oauth2Parser = copy.m_oauth2Parser;
- }
-
- return *this;
-}
-
OAuth2Handler::~OAuth2Handler( )
{
@@ -91,12 +101,12 @@ OAuth2Handler::~OAuth2Handler( )
void OAuth2Handler::fetchTokens( string authCode )
{
string post =
- "code=" + authCode +
- "&client_id=" + m_data->getClientId() +
- "&redirect_uri=" + m_data->getRedirectUri() +
+ "code=" + libcmis::escape( authCode ) +
+ "&client_id=" + libcmis::escape( m_data->getClientId() ) +
+ "&redirect_uri=" + libcmis::escape( m_data->getRedirectUri() ) +
"&grant_type=authorization_code" ;
if(boost::starts_with(m_data->getTokenUrl(), "https://oauth2.googleapis.com/"))
- post += "&client_secret=" + m_data->getClientSecret();
+ post += "&client_secret=" + libcmis::escape( m_data->getClientSecret() );
else
post += "&scope=" + libcmis::escape( m_data->getScope() );
@@ -118,17 +128,20 @@ void OAuth2Handler::fetchTokens( string authCode )
Json jresp = Json::parse( resp->getStream( )->str( ) );
m_access = jresp[ "access_token" ].toString( );
m_refresh = jresp[ "refresh_token" ].toString( );
+
+ if ( m_access.empty( ) )
+ throw tokenError( "Token request returned no access_token", jresp );
}
void OAuth2Handler::refresh( )
{
m_access = string( );
string post =
- "refresh_token=" + m_refresh +
- "&client_id=" + m_data->getClientId() +
+ "refresh_token=" + libcmis::escape( m_refresh ) +
+ "&client_id=" + libcmis::escape( m_data->getClientId() ) +
"&grant_type=refresh_token" ;
if(boost::starts_with(m_data->getTokenUrl(), "https://oauth2.googleapis.com/"))
- post += "&client_secret=" + m_data->getClientSecret();
+ post += "&client_secret=" + libcmis::escape( m_data->getClientSecret() );
istringstream is( post );
libcmis::HttpResponsePtr resp;
@@ -144,15 +157,18 @@ void OAuth2Handler::refresh( )
Json jresp = Json::parse( resp->getStream( )->str( ) );
m_access = jresp[ "access_token" ].toString();
+
+ if ( m_access.empty( ) )
+ throw tokenError( "Token refresh returned no access_token", jresp );
}
string OAuth2Handler::getAuthURL( )
{
return m_data->getAuthUrl() +
"?scope=" + libcmis::escape( m_data->getScope( ) ) +
- "&redirect_uri="+ m_data->getRedirectUri( ) +
+ "&redirect_uri=" + libcmis::escape( m_data->getRedirectUri( ) ) +
"&response_type=code" +
- "&client_id=" + m_data->getClientId( );
+ "&client_id=" + libcmis::escape( m_data->getClientId( ) );
}
string OAuth2Handler::getAccessToken( )
diff --git a/src/libcmis/oauth2-handler.hxx b/src/libcmis/oauth2-handler.hxx
index bb9a394..19916b7 100644
--- a/src/libcmis/oauth2-handler.hxx
+++ b/src/libcmis/oauth2-handler.hxx
@@ -52,10 +52,14 @@ class OAuth2Handler
OAuth2Handler( HttpSession* session, libcmis::OAuth2DataPtr data );
- OAuth2Handler( const OAuth2Handler& copy );
- ~OAuth2Handler( );
+ // Deep-copy into a new owning session. m_session must point at the
+ // new owner, not at the soon-to-be-destroyed source HttpSession.
+ OAuth2Handler( HttpSession* session, const OAuth2Handler& copy );
+
+ OAuth2Handler( const OAuth2Handler& ) = delete;
+ OAuth2Handler& operator=( const OAuth2Handler& ) = delete;
- OAuth2Handler& operator=( const OAuth2Handler& copy );
+ ~OAuth2Handler( );
std::string getAuthURL();
diff --git a/src/libcmis/oauth2-providers.cxx b/src/libcmis/oauth2-providers.cxx
index 1c8d3cc..b81fefc 100644
--- a/src/libcmis/oauth2-providers.cxx
+++ b/src/libcmis/oauth2-providers.cxx
@@ -74,10 +74,10 @@ string OAuth2Providers::OAuth2Alfresco( HttpSession* session, const string& auth
if ( !parseResponse( res.c_str( ), loginPost, loginLink ) )
return string( );
- loginPost += "username=";
- loginPost += string( username );
+ loginPost += "username=";
+ loginPost += libcmis::escape( string( username ) );
loginPost += "&password=";
- loginPost += string( password );
+ loginPost += libcmis::escape( string( password ) );
loginPost += "&action=Grant";
istringstream loginIs( loginPost );
@@ -126,7 +126,11 @@ int OAuth2Providers::parseResponse ( const char* response, string& post, string&
HTML_PARSE_NOWARNING | HTML_PARSE_RECOVER | HTML_PARSE_NOERROR );
if ( doc == NULL ) return 0;
xmlTextReaderPtr reader = xmlReaderWalker( doc );
- if ( reader == NULL ) return 0;
+ if ( reader == NULL )
+ {
+ xmlFreeDoc( doc );
+ return 0;
+ }
bool readInputField = false;
bool bIsRightForm = false;
@@ -209,7 +213,11 @@ string OAuth2Providers::parseCode( const char* response )
HTML_PARSE_NOWARNING | HTML_PARSE_RECOVER | HTML_PARSE_NOERROR );
if ( doc == NULL ) return authCode;
xmlTextReaderPtr reader = xmlReaderWalker( doc );
- if ( reader == NULL ) return authCode;
+ if ( reader == NULL )
+ {
+ xmlFreeDoc( doc );
+ return authCode;
+ }
while ( true )
{
diff --git a/src/libcmis/onedrive-session.cxx b/src/libcmis/onedrive-session.cxx
index 89d5a50..6104305 100644
--- a/src/libcmis/onedrive-session.cxx
+++ b/src/libcmis/onedrive-session.cxx
@@ -66,7 +66,7 @@ OneDriveSession::~OneDriveSession()
void OneDriveSession::setOAuth2Data( libcmis::OAuth2DataPtr oauth2 )
{
- m_oauth2Handler = new OAuth2Handler( this, oauth2 );
+ m_oauth2Handler.reset( new OAuth2Handler( this, oauth2 ) );
m_oauth2Handler->setOAuth2Parser( OAuth2Providers::getOAuth2Parser( getBindingUrl( ) ) );
oauth2Authenticate( );
diff --git a/src/libcmis/property.cxx b/src/libcmis/property.cxx
index 41c181b..8e4c8d1 100644
--- a/src/libcmis/property.cxx
+++ b/src/libcmis/property.cxx
@@ -214,8 +214,11 @@ namespace libcmis
if ( xmlStrEqual( child->name, BAD_CAST( "value" ) ) )
{
xmlChar* content = xmlNodeGetContent( child );
- values.push_back( string( ( char * ) content ) );
- xmlFree( content );
+ if ( content )
+ {
+ values.push_back( string( ( char * ) content ) );
+ xmlFree( content );
+ }
}
}
property.reset( new Property( propType, values ) );
diff --git a/src/libcmis/repository.cxx b/src/libcmis/repository.cxx
index 0a7cbe6..331127e 100644
--- a/src/libcmis/repository.cxx
+++ b/src/libcmis/repository.cxx
@@ -132,6 +132,8 @@ namespace libcmis
string localName( ( char* ) child->name );
xmlChar* content = xmlNodeGetContent( child );
+ if ( content == NULL )
+ continue;
string value( ( char* )content );
xmlFree( content );
@@ -247,6 +249,8 @@ namespace libcmis
string localName( ( char* ) child->name );
xmlChar* content = xmlNodeGetContent( child );
+ if ( content == NULL )
+ continue;
string value( ( char* )content );
xmlFree( content );
diff --git a/src/libcmis/sharepoint-session.cxx b/src/libcmis/sharepoint-session.cxx
index 591d3cb..03debf0 100644
--- a/src/libcmis/sharepoint-session.cxx
+++ b/src/libcmis/sharepoint-session.cxx
@@ -191,6 +191,17 @@ Json SharePointSession::getJsonFromUrl( string url )
/* Overwriting HttpSession::httpRunRequest to add the "accept:application/json" header */
void SharePointSession::httpRunRequest( string url, vector< string > headers, bool redirect )
{
+ libcmis::rejectControlChars( url, "URL" );
+ for ( vector< string >::const_iterator it = headers.begin( ); it != headers.end( ); ++it )
+ libcmis::rejectControlChars( *it, "header" );
+
+ // The base class entry points already call initProtocols() right after
+ // their curl_easy_reset, but re-assert it here so this override stays
+ // safe if a future caller skips the reset+initProtocols pattern.
+ initProtocols();
+
+ libcmis::applyTransferLimits( m_curlHandle );
+
// Redirect
curl_easy_setopt( m_curlHandle, CURLOPT_FOLLOWLOCATION, redirect);
@@ -215,6 +226,8 @@ void SharePointSession::httpRunRequest( string url, vector< string > headers, bo
if ( !getUsername().empty() && !getPassword().empty() )
{
+ libcmis::rejectControlChars( getUsername(), "username" );
+ libcmis::rejectControlChars( getPassword(), "password" );
curl_easy_setopt( m_curlHandle, CURLOPT_HTTPAUTH, m_authMethod );
curl_easy_setopt( m_curlHandle, CURLOPT_USERNAME, getUsername().c_str() );
curl_easy_setopt( m_curlHandle, CURLOPT_PASSWORD, getPassword().c_str() );
@@ -405,4 +418,5 @@ void SharePointSession::fetchDigestCodeCurl( )
string res = response->getStream( )->str( );
Json jsonRes = Json::parse( res );
m_digestCode = jsonRes["d"]["GetContextWebInformation"]["FormDigestValue"].toString( );
+ libcmis::rejectControlChars( m_digestCode, "FormDigestValue" );
}
diff --git a/src/libcmis/ws-relatedmultipart.cxx b/src/libcmis/ws-relatedmultipart.cxx
index 87a4f1a..f1df59a 100644
--- a/src/libcmis/ws-relatedmultipart.cxx
+++ b/src/libcmis/ws-relatedmultipart.cxx
@@ -97,7 +97,7 @@ RelatedMultipart::RelatedMultipart( const string& body, const string& contentTyp
{
string name = param.substr( 0, eqPos );
string value = param.substr( eqPos + 1 );
- if ( value[0] == '"' && value[value.length() - 1] == '"' )
+ if ( value.length() >= 2 && value[0] == '"' && value[value.length() - 1] == '"' )
value = value.substr( 1, value.length( ) - 2 );
name = libcmis::trim( name );
@@ -106,7 +106,7 @@ RelatedMultipart::RelatedMultipart( const string& body, const string& contentTyp
{
m_startId = value;
// Remove the '<' '>' around the id if any
- if ( m_startId[0] == '<' && m_startId[m_startId.size()-1] == '>' )
+ if ( m_startId.length() >= 2 && m_startId[0] == '<' && m_startId[m_startId.size()-1] == '>' )
m_startId = m_startId.substr( 1, m_startId.size() - 2 );
}
else if ( name == "boundary" )
@@ -127,7 +127,7 @@ RelatedMultipart::RelatedMultipart( const string& body, const string& contentTyp
if ( boost::starts_with( bodyFixed, "--" + m_boundary + "\r\n" ) )
bodyFixed = "\r\n" + bodyFixed;
- if ( bodyFixed[bodyFixed.length() - 1 ] != '\n' )
+ if ( bodyFixed.empty() || bodyFixed[bodyFixed.length() - 1 ] != '\n' )
bodyFixed += '\n';
string lineEnd( "\n" );
@@ -153,7 +153,7 @@ RelatedMultipart::RelatedMultipart( const string& body, const string& contentTyp
if ( !cid.empty() && !type.empty( ) )
{
// Remove potential \r at the end of the body part
- if ( partBody[partBody.length() - 1] == '\r' )
+ if ( !partBody.empty() && partBody[partBody.length() - 1] == '\r' )
partBody.pop_back();
RelatedPartPtr relatedPart( new RelatedPart( name, type, partBody ) );
@@ -185,7 +185,7 @@ RelatedMultipart::RelatedMultipart( const string& body, const string& contentTyp
{
cid = libcmis::trim( headerValue );
// Remove the '<' '>' around the id if any
- if ( cid[0] == '<' && cid[cid.size()-1] == '>' )
+ if ( cid.length() >= 2 && cid[0] == '<' && cid[cid.size()-1] == '>' )
cid = cid.substr( 1, cid.size() - 2 );
}
else if ( headerName == "Content-Type" )
@@ -320,6 +320,8 @@ boost::shared_ptr< istream > getStreamFromNode( xmlNodePtr node, RelatedMultipar
{
// Get the content from the multipart
xmlChar* value = xmlGetProp( child, BAD_CAST( "href" ) );
+ if ( value == NULL )
+ continue;
string href( ( char* )value );
xmlFree( value );
// Get the Content ID from the href (cid:content-id)
@@ -340,14 +342,16 @@ boost::shared_ptr< istream > getStreamFromNode( xmlNodePtr node, RelatedMultipar
if ( stream.get( ) == NULL )
{
xmlChar* content = xmlNodeGetContent( node );
+ if ( content )
+ {
+ stream.reset( new stringstream( ) );
+ libcmis::EncodedData decoder( stream.get( ) );
+ decoder.setEncoding( "base64" );
+ decoder.decode( ( void* )content, 1, xmlStrlen( content ) );
+ decoder.finish( );
- stream.reset( new stringstream( ) );
- libcmis::EncodedData decoder( stream.get( ) );
- decoder.setEncoding( "base64" );
- decoder.decode( ( void* )content, 1, xmlStrlen( content ) );
- decoder.finish( );
-
- xmlFree( content );
+ xmlFree( content );
+ }
}
return stream;
}
diff --git a/src/libcmis/ws-requests.cxx b/src/libcmis/ws-requests.cxx
index 8235c0b..83eacf4 100644
--- a/src/libcmis/ws-requests.cxx
+++ b/src/libcmis/ws-requests.cxx
@@ -48,6 +48,8 @@ CmisSoapFaultDetail::CmisSoapFaultDetail( xmlNodePtr node ) :
for ( xmlNodePtr child = node->children; child; child = child->next )
{
xmlChar* content = xmlNodeGetContent( child );
+ if ( content == NULL )
+ continue;
string value( ( char * )content );
xmlFree( content );
@@ -155,6 +157,8 @@ SoapResponsePtr GetRepositoriesResponse::create( xmlNodePtr node, RelatedMultipa
for ( xmlNodePtr repoChild = child->children; repoChild; repoChild = repoChild->next )
{
xmlChar* content = xmlNodeGetContent( repoChild );
+ if ( content == NULL )
+ continue;
string value( ( char* ) content );
xmlFree( content );
diff --git a/src/libcmis/ws-soap.cxx b/src/libcmis/ws-soap.cxx
index 4bf7d2f..8b2e457 100644
--- a/src/libcmis/ws-soap.cxx
+++ b/src/libcmis/ws-soap.cxx
@@ -52,20 +52,29 @@ SoapFault::SoapFault( xmlNodePtr node, SoapResponseFactory* factory ) :
if ( xmlStrEqual( child->name, BAD_CAST( "faultcode" ) ) )
{
xmlChar* content = xmlNodeGetContent( child );
- xmlChar* prefix = NULL;
- xmlChar* localName = xmlSplitQName2( content, &prefix );
- if (localName == NULL)
- localName = xmlStrdup( content );
- m_faultcode = string( ( char* )localName );
- xmlFree( content );
- xmlFree( prefix );
- xmlFree( localName );
+ if ( content )
+ {
+ xmlChar* prefix = NULL;
+ xmlChar* localName = xmlSplitQName2( content, &prefix );
+ if (localName == NULL)
+ localName = xmlStrdup( content );
+ if ( localName )
+ {
+ m_faultcode = string( ( char* )localName );
+ xmlFree( localName );
+ }
+ xmlFree( content );
+ xmlFree( prefix );
+ }
}
else if ( xmlStrEqual( child->name, BAD_CAST( "faultstring" ) ) )
{
xmlChar* content = xmlNodeGetContent( child );
- m_faultstring = string( ( char* )content );
- xmlFree( content );
+ if ( content )
+ {
+ m_faultstring = string( ( char* )content );
+ xmlFree( content );
+ }
}
else if ( xmlStrEqual( child->name, BAD_CAST( "detail" ) ) )
{
@@ -171,7 +180,8 @@ vector< SoapResponsePtr > SoapResponseFactory::parseResponse( RelatedMultipart&
xmlNodePtr node = xpathObj->nodesetval->nodeTab[i];
// Is it a fault?
- if ( xmlStrEqual( BAD_CAST( NS_SOAP_ENV_URL ), node->ns->href ) &&
+ if ( node->ns && node->ns->href &&
+ xmlStrEqual( BAD_CAST( NS_SOAP_ENV_URL ), node->ns->href ) &&
xmlStrEqual( BAD_CAST( "Fault" ), node->name ) )
{
throw SoapFault( node, this );
@@ -191,7 +201,9 @@ SoapResponsePtr SoapResponseFactory::createResponse( xmlNodePtr node, RelatedMul
{
SoapResponsePtr response;
- string ns( ( const char* ) node->ns->href );
+ string ns;
+ if ( node->ns && node->ns->href )
+ ns = string( ( const char* ) node->ns->href );
string name( ( const char* ) node->name );
string id = "{" + ns + "}" + name;
map< string, SoapResponseCreator >::iterator it = m_mapping.find( id );
@@ -326,7 +338,7 @@ string SoapRequest::createEnvelope( const string& username, const string& passwo
xmlTextWriterEndElement( writer ); // End of S:Envelope
xmlTextWriterEndDocument( writer );
- string str( ( const char * )xmlBufferContent( buf ) );
+ string str( ( const char * )xmlBufferContent( buf ), xmlBufferLength( buf ) );
xmlFreeTextWriter( writer );
xmlBufferFree( buf );
diff --git a/src/libcmis/xml-utils.cxx b/src/libcmis/xml-utils.cxx
index 64c5ae5..597ad79 100644
--- a/src/libcmis/xml-utils.cxx
+++ b/src/libcmis/xml-utils.cxx
@@ -182,6 +182,8 @@ namespace libcmis
decoded[1] = ( m_pendingValue & 0xFF00 ) >> 8;
decoded[2] = ( m_pendingValue & 0xFF );
+ if ( missingBytes > 3 )
+ missingBytes = 3;
write( decoded, 1, 3 - missingBytes );
m_pendingRank = 0;
@@ -239,6 +241,8 @@ namespace libcmis
decoded[1] = ( blockValue & 0xFF00 ) >> 8;
decoded[2] = ( blockValue & 0xFF );
+ if ( missingBytes > 3 )
+ missingBytes = 3;
write( decoded, 1, 3 - missingBytes );
byteRank = 0;
@@ -346,8 +350,11 @@ namespace libcmis
if ( xpathObj && xpathObj->nodesetval && xpathObj->nodesetval->nodeNr > 0 )
{
xmlChar* pContent = xmlNodeGetContent( xpathObj->nodesetval->nodeTab[0] );
- value = string( ( char* )pContent );
- xmlFree( pContent );
+ if ( pContent )
+ {
+ value = string( ( char* )pContent );
+ xmlFree( pContent );
+ }
}
xmlXPathFreeObject( xpathObj );
}