// insert.cpp
/**
* Copyright (C) 2008 10gen Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see .
*
* As a special exception, the copyright holders give permission to link the
* code of portions of this program with the OpenSSL library under certain
* conditions as described in each individual source file and distribute
* linked combinations including the program with the OpenSSL library. You
* must comply with the GNU Affero General Public License in all respects for
* all of the code used other than as permitted herein. If you modify file(s)
* with this exception, you may extend this exception to your version of the
* file(s), but you are not obligated to do so. If you do not wish to do so,
* delete this exception statement from your version. If you delete this
* exception statement from all source files in the program, then also delete
* it in the license file.
*/
#include "mongo/db/ops/insert.h"
#include "mongo/db/global_timestamp.h"
#include "mongo/util/mongoutils/str.h"
namespace mongo {
using std::string;
using namespace mongoutils;
StatusWith fixDocumentForInsert( const BSONObj& doc ) {
if ( doc.objsize() > BSONObjMaxUserSize )
return StatusWith( ErrorCodes::BadValue,
str::stream()
<< "object to insert too large"
<< ". size in bytes: " << doc.objsize()
<< ", max size: " << BSONObjMaxUserSize );
bool firstElementIsId = doc.firstElement().fieldNameStringData() == "_id";
bool hasTimestampToFix = false;
{
BSONObjIterator i( doc );
while ( i.more() ) {
BSONElement e = i.next();
if ( e.type() == bsonTimestamp && e.timestampValue() == 0 ) {
// we replace Timestamp(0,0) at the top level with a correct value
// in the fast pass, we just mark that we want to swap
hasTimestampToFix = true;
}
const char* fieldName = e.fieldName();
if ( fieldName[0] == '$' ) {
return StatusWith( ErrorCodes::BadValue,
str::stream()
<< "Document can't have $ prefixed field names: "
<< e.fieldName() );
}
// check no regexp for _id (SERVER-9502)
// also, disallow undefined and arrays
if ( str::equals( fieldName, "_id") ) {
if ( e.type() == RegEx ) {
return StatusWith( ErrorCodes::BadValue,
"can't use a regex for _id" );
}
if ( e.type() == Undefined ) {
return StatusWith( ErrorCodes::BadValue,
"can't use a undefined for _id" );
}
if ( e.type() == Array ) {
return StatusWith( ErrorCodes::BadValue,
"can't use an array for _id" );
}
if ( e.type() == Object ) {
BSONObj o = e.Obj();
Status s = o.storageValidEmbedded();
if ( !s.isOK() )
return StatusWith( s );
}
}
}
}
if ( firstElementIsId && !hasTimestampToFix )
return StatusWith( BSONObj() );
bool hadId = firstElementIsId;
BSONObjIterator i( doc );
BSONObjBuilder b( doc.objsize() + 16 );
if ( firstElementIsId ) {
b.append( doc.firstElement() );
i.next();
}
else {
BSONElement e = doc["_id"];
if ( e.type() ) {
b.append( e );
hadId = true;
}
else {
b.appendOID( "_id", NULL, true );
}
}
while ( i.more() ) {
BSONElement e = i.next();
if ( hadId && e.fieldNameStringData() == "_id" ) {
// no-op
}
else if ( e.type() == bsonTimestamp && e.timestampValue() == 0 ) {
b.append( e.fieldName(), getNextGlobalTimestamp() );
}
else {
b.append( e );
}
}
return StatusWith( b.obj() );
}
Status userAllowedWriteNS( StringData ns ) {
return userAllowedWriteNS( nsToDatabaseSubstring( ns ), nsToCollectionSubstring( ns ) );
}
Status userAllowedWriteNS( const NamespaceString& ns ) {
return userAllowedWriteNS( ns.db(), ns.coll() );
}
Status userAllowedWriteNS( StringData db, StringData coll ) {
if ( coll == "system.profile" ) {
return Status( ErrorCodes::BadValue,
str::stream() << "cannot write to '" << db << ".system.profile'" );
}
return userAllowedCreateNS( db, coll );
}
Status userAllowedCreateNS( StringData db, StringData coll ) {
// validity checking
if ( db.size() == 0 )
return Status( ErrorCodes::BadValue, "db cannot be blank" );
if ( !NamespaceString::validDBName( db ) )
return Status( ErrorCodes::BadValue, "invalid db name" );
if ( coll.size() == 0 )
return Status( ErrorCodes::BadValue, "collection cannot be blank" );
if ( !NamespaceString::validCollectionName( coll ) )
return Status( ErrorCodes::BadValue, "invalid collection name" );
if ( db.size() + 1 /* dot */ + coll.size() > NamespaceString::MaxNsCollectionLen )
return Status( ErrorCodes::BadValue,
str::stream()
<< "fully qualified namespace " << db << '.' << coll << " is too long "
<< "(max is " << NamespaceString::MaxNsCollectionLen << " bytes)" );
// check spceial areas
if ( db == "system" )
return Status( ErrorCodes::BadValue, "cannot use 'system' database" );
if ( coll.startsWith( "system." ) ) {
if ( coll == "system.indexes" ) return Status::OK();
if ( coll == "system.js" ) return Status::OK();
if ( coll == "system.profile" ) return Status::OK();
if ( coll == "system.users" ) return Status::OK();
if ( db == "admin" ) {
if ( coll == "system.version" ) return Status::OK();
if ( coll == "system.roles" ) return Status::OK();
if ( coll == "system.new_users" ) return Status::OK();
if ( coll == "system.backup_users" ) return Status::OK();
}
if ( db == "local" ) {
if ( coll == "system.replset" ) return Status::OK();
}
return Status( ErrorCodes::BadValue,
str::stream() << "cannot write to '" << db << "." << coll << "'" );
}
// some special rules
if ( coll.find( ".system." ) != string::npos ) {
// this matches old (2.4 and older) behavior, but I'm not sure its a good idea
return Status( ErrorCodes::BadValue,
str::stream() << "cannot write to '" << db << "." << coll << "'" );
}
return Status::OK();
}
}