bpo-9949: Enable symlink traversal for ntpath.realpath (GH-15287) · python/cpython@c30c869 (original) (raw)

`@@ -7,13 +7,22 @@

`

7

7

`from test import support, test_genericpath

`

8

8

`from tempfile import TemporaryFile

`

9

9

``

``

10

+

10

11

`try:

`

11

12

`import nt

`

12

13

`except ImportError:

`

13

14

`# Most tests can complete without the nt module,

`

14

15

`# but for those that require it we import here.

`

15

16

`nt = None

`

16

17

``

``

18

`+

try:

`

``

19

`+

ntpath._getfinalpathname

`

``

20

`+

except AttributeError:

`

``

21

`+

HAVE_GETFINALPATHNAME = False

`

``

22

`+

else:

`

``

23

`+

HAVE_GETFINALPATHNAME = True

`

``

24

+

``

25

+

17

26

`def tester(fn, wantResult):

`

18

27

`fn = fn.replace("\", "\\")

`

19

28

`gotResult = eval(fn)

`

`@@ -194,6 +203,189 @@ def test_normpath(self):

`

194

203

`tester("ntpath.normpath('\\.\NUL')", r'\.\NUL')

`

195

204

`tester("ntpath.normpath('\\?\D:/XY\Z')", r'\?\D:/XY\Z')

`

196

205

``

``

206

`+

def test_realpath_curdir(self):

`

``

207

`+

expected = ntpath.normpath(os.getcwd())

`

``

208

`+

tester("ntpath.realpath('.')", expected)

`

``

209

`+

tester("ntpath.realpath('./.')", expected)

`

``

210

`+

tester("ntpath.realpath('/'.join(['.'] * 100))", expected)

`

``

211

`+

tester("ntpath.realpath('.\.')", expected)

`

``

212

`+

tester("ntpath.realpath('\'.join(['.'] * 100))", expected)

`

``

213

+

``

214

`+

def test_realpath_pardir(self):

`

``

215

`+

expected = ntpath.normpath(os.getcwd())

`

``

216

`+

tester("ntpath.realpath('..')", ntpath.dirname(expected))

`

``

217

`+

tester("ntpath.realpath('../..')",

`

``

218

`+

ntpath.dirname(ntpath.dirname(expected)))

`

``

219

`+

tester("ntpath.realpath('/'.join(['..'] * 50))",

`

``

220

`+

ntpath.splitdrive(expected)[0] + '\')

`

``

221

`+

tester("ntpath.realpath('..\..')",

`

``

222

`+

ntpath.dirname(ntpath.dirname(expected)))

`

``

223

`+

tester("ntpath.realpath('\'.join(['..'] * 50))",

`

``

224

`+

ntpath.splitdrive(expected)[0] + '\')

`

``

225

+

``

226

`+

@support.skip_unless_symlink

`

``

227

`+

@unittest.skipUnless(HAVE_GETFINALPATHNAME, 'need _getfinalpathname')

`

``

228

`+

def test_realpath_basic(self):

`

``

229

`+

ABSTFN = ntpath.abspath(support.TESTFN)

`

``

230

`+

open(ABSTFN, "wb").close()

`

``

231

`+

self.addCleanup(support.unlink, ABSTFN)

`

``

232

`+

self.addCleanup(support.unlink, ABSTFN + "1")

`

``

233

+

``

234

`+

os.symlink(ABSTFN, ABSTFN + "1")

`

``

235

`+

self.assertEqual(ntpath.realpath(ABSTFN + "1"), ABSTFN)

`

``

236

`+

self.assertEqual(ntpath.realpath(os.fsencode(ABSTFN + "1")),

`

``

237

`+

os.fsencode(ABSTFN))

`

``

238

+

``

239

`+

@support.skip_unless_symlink

`

``

240

`+

@unittest.skipUnless(HAVE_GETFINALPATHNAME, 'need _getfinalpathname')

`

``

241

`+

def test_realpath_relative(self):

`

``

242

`+

ABSTFN = ntpath.abspath(support.TESTFN)

`

``

243

`+

open(ABSTFN, "wb").close()

`

``

244

`+

self.addCleanup(support.unlink, ABSTFN)

`

``

245

`+

self.addCleanup(support.unlink, ABSTFN + "1")

`

``

246

+

``

247

`+

os.symlink(ABSTFN, ntpath.relpath(ABSTFN + "1"))

`

``

248

`+

self.assertEqual(ntpath.realpath(ABSTFN + "1"), ABSTFN)

`

``

249

+

``

250

`+

@support.skip_unless_symlink

`

``

251

`+

@unittest.skipUnless(HAVE_GETFINALPATHNAME, 'need _getfinalpathname')

`

``

252

`+

def test_realpath_broken_symlinks(self):

`

``

253

`+

ABSTFN = ntpath.abspath(support.TESTFN)

`

``

254

`+

os.mkdir(ABSTFN)

`

``

255

`+

self.addCleanup(support.rmtree, ABSTFN)

`

``

256

+

``

257

`+

with support.change_cwd(ABSTFN):

`

``

258

`+

os.mkdir("subdir")

`

``

259

`+

os.chdir("subdir")

`

``

260

`+

os.symlink(".", "recursive")

`

``

261

`+

os.symlink("..", "parent")

`

``

262

`+

os.chdir("..")

`

``

263

`+

os.symlink(".", "self")

`

``

264

`+

os.symlink("missing", "broken")

`

``

265

`+

os.symlink(r"broken\bar", "broken1")

`

``

266

`+

os.symlink(r"self\self\broken", "broken2")

`

``

267

`+

os.symlink(r"subdir\parent\subdir\parent\broken", "broken3")

`

``

268

`+

os.symlink(ABSTFN + r"\broken", "broken4")

`

``

269

`+

os.symlink(r"recursive..\broken", "broken5")

`

``

270

+

``

271

`+

self.assertEqual(ntpath.realpath("broken"),

`

``

272

`+

ABSTFN + r"\missing")

`

``

273

`+

self.assertEqual(ntpath.realpath(r"broken\foo"),

`

``

274

`+

ABSTFN + r"\missing\foo")

`

``

275

`+

self.assertEqual(ntpath.realpath(r"broken1"),

`

``

276

`+

ABSTFN + r"\missing\bar")

`

``

277

`+

self.assertEqual(ntpath.realpath(r"broken1\baz"),

`

``

278

`+

ABSTFN + r"\missing\bar\baz")

`

``

279

`+

self.assertEqual(ntpath.realpath("broken2"),

`

``

280

`+

ABSTFN + r"\missing")

`

``

281

`+

self.assertEqual(ntpath.realpath("broken3"),

`

``

282

`+

ABSTFN + r"\missing")

`

``

283

`+

self.assertEqual(ntpath.realpath("broken4"),

`

``

284

`+

ABSTFN + r"\missing")

`

``

285

`+

self.assertEqual(ntpath.realpath("broken5"),

`

``

286

`+

ABSTFN + r"\missing")

`

``

287

+

``

288

`+

self.assertEqual(ntpath.realpath(b"broken"),

`

``

289

`+

os.fsencode(ABSTFN + r"\missing"))

`

``

290

`+

self.assertEqual(ntpath.realpath(rb"broken\foo"),

`

``

291

`+

os.fsencode(ABSTFN + r"\missing\foo"))

`

``

292

`+

self.assertEqual(ntpath.realpath(rb"broken1"),

`

``

293

`+

os.fsencode(ABSTFN + r"\missing\bar"))

`

``

294

`+

self.assertEqual(ntpath.realpath(rb"broken1\baz"),

`

``

295

`+

os.fsencode(ABSTFN + r"\missing\bar\baz"))

`

``

296

`+

self.assertEqual(ntpath.realpath(b"broken2"),

`

``

297

`+

os.fsencode(ABSTFN + r"\missing"))

`

``

298

`+

self.assertEqual(ntpath.realpath(rb"broken3"),

`

``

299

`+

os.fsencode(ABSTFN + r"\missing"))

`

``

300

`+

self.assertEqual(ntpath.realpath(b"broken4"),

`

``

301

`+

os.fsencode(ABSTFN + r"\missing"))

`

``

302

`+

self.assertEqual(ntpath.realpath(b"broken5"),

`

``

303

`+

os.fsencode(ABSTFN + r"\missing"))

`

``

304

+

``

305

`+

@support.skip_unless_symlink

`

``

306

`+

@unittest.skipUnless(HAVE_GETFINALPATHNAME, 'need _getfinalpathname')

`

``

307

`+

def test_realpath_symlink_loops(self):

`

``

308

`+

Bug #930024, return the path unchanged if we get into an infinite

`

``

309

`+

symlink loop.

`

``

310

`+

ABSTFN = ntpath.abspath(support.TESTFN)

`

``

311

`+

self.addCleanup(support.unlink, ABSTFN)

`

``

312

`+

self.addCleanup(support.unlink, ABSTFN + "1")

`

``

313

`+

self.addCleanup(support.unlink, ABSTFN + "2")

`

``

314

`+

self.addCleanup(support.unlink, ABSTFN + "y")

`

``

315

`+

self.addCleanup(support.unlink, ABSTFN + "c")

`

``

316

`+

self.addCleanup(support.unlink, ABSTFN + "a")

`

``

317

+

``

318

`+

P = "\\?\"

`

``

319

+

``

320

`+

os.symlink(ABSTFN, ABSTFN)

`

``

321

`+

self.assertEqual(ntpath.realpath(ABSTFN), P + ABSTFN)

`

``

322

+

``

323

`+

cycles are non-deterministic as to which path is returned, but

`

``

324

`+

it will always be the fully resolved path of one member of the cycle

`

``

325

`+

os.symlink(ABSTFN + "1", ABSTFN + "2")

`

``

326

`+

os.symlink(ABSTFN + "2", ABSTFN + "1")

`

``

327

`+

expected = (P + ABSTFN + "1", P + ABSTFN + "2")

`

``

328

`+

self.assertIn(ntpath.realpath(ABSTFN + "1"), expected)

`

``

329

`+

self.assertIn(ntpath.realpath(ABSTFN + "2"), expected)

`

``

330

+

``

331

`+

self.assertIn(ntpath.realpath(ABSTFN + "1\x"),

`

``

332

`+

(ntpath.join(r, "x") for r in expected))

`

``

333

`+

self.assertEqual(ntpath.realpath(ABSTFN + "1\.."),

`

``

334

`+

ntpath.dirname(ABSTFN))

`

``

335

`+

self.assertEqual(ntpath.realpath(ABSTFN + "1\..\x"),

`

``

336

`+

ntpath.dirname(P + ABSTFN) + "\x")

`

``

337

`+

os.symlink(ABSTFN + "x", ABSTFN + "y")

`

``

338

`+

self.assertEqual(ntpath.realpath(ABSTFN + "1\..\"

`

``

339

`+

`

``

340

`+

P + ABSTFN + "x")

`

``

341

`+

self.assertIn(ntpath.realpath(ABSTFN + "1\..\"

`

``

342

`+

`

``

343

`+

expected)

`

``

344

+

``

345

`+

os.symlink(ntpath.basename(ABSTFN) + "a\b", ABSTFN + "a")

`

``

346

`+

self.assertEqual(ntpath.realpath(ABSTFN + "a"), P + ABSTFN + "a")

`

``

347

+

``

348

`+

os.symlink("..\" + ntpath.basename(ntpath.dirname(ABSTFN))

`

``

349

`+

`

``

350

`+

self.assertEqual(ntpath.realpath(ABSTFN + "c"), P + ABSTFN + "c")

`

``

351

+

``

352

`+

Test using relative path as well.

`

``

353

`+

self.assertEqual(ntpath.realpath(ntpath.basename(ABSTFN)), P + ABSTFN)

`

``

354

+

``

355

`+

@support.skip_unless_symlink

`

``

356

`+

@unittest.skipUnless(HAVE_GETFINALPATHNAME, 'need _getfinalpathname')

`

``

357

`+

def test_realpath_symlink_prefix(self):

`

``

358

`+

ABSTFN = ntpath.abspath(support.TESTFN)

`

``

359

`+

self.addCleanup(support.unlink, ABSTFN + "3")

`

``

360

`+

self.addCleanup(support.unlink, "\\?\" + ABSTFN + "3.")

`

``

361

`+

self.addCleanup(support.unlink, ABSTFN + "3link")

`

``

362

`+

self.addCleanup(support.unlink, ABSTFN + "3.link")

`

``

363

+

``

364

`+

with open(ABSTFN + "3", "wb") as f:

`

``

365

`+

f.write(b'0')

`

``

366

`+

os.symlink(ABSTFN + "3", ABSTFN + "3link")

`

``

367

+

``

368

`+

with open("\\?\" + ABSTFN + "3.", "wb") as f:

`

``

369

`+

f.write(b'1')

`

``

370

`+

os.symlink("\\?\" + ABSTFN + "3.", ABSTFN + "3.link")

`

``

371

+

``

372

`+

self.assertEqual(ntpath.realpath(ABSTFN + "3link"),

`

``

373

`+

ABSTFN + "3")

`

``

374

`+

self.assertEqual(ntpath.realpath(ABSTFN + "3.link"),

`

``

375

`+

"\\?\" + ABSTFN + "3.")

`

``

376

+

``

377

`+

Resolved paths should be usable to open target files

`

``

378

`+

with open(ntpath.realpath(ABSTFN + "3link"), "rb") as f:

`

``

379

`+

self.assertEqual(f.read(), b'0')

`

``

380

`+

with open(ntpath.realpath(ABSTFN + "3.link"), "rb") as f:

`

``

381

`+

self.assertEqual(f.read(), b'1')

`

``

382

+

``

383

`+

When the prefix is included, it is not stripped

`

``

384

`+

self.assertEqual(ntpath.realpath("\\?\" + ABSTFN + "3link"),

`

``

385

`+

"\\?\" + ABSTFN + "3")

`

``

386

`+

self.assertEqual(ntpath.realpath("\\?\" + ABSTFN + "3.link"),

`

``

387

`+

"\\?\" + ABSTFN + "3.")

`

``

388

+

197

389

`def test_expandvars(self):

`

198

390

`with support.EnvironmentVarGuard() as env:

`

199

391

`env.clear()

`

`@@ -288,11 +480,11 @@ def test_abspath(self):

`

288

480

``

289

481

`def test_relpath(self):

`

290

482

`tester('ntpath.relpath("a")', 'a')

`

291

``

`-

tester('ntpath.relpath(os.path.abspath("a"))', 'a')

`

``

483

`+

tester('ntpath.relpath(ntpath.abspath("a"))', 'a')

`

292

484

`tester('ntpath.relpath("a/b")', 'a\b')

`

293

485

`tester('ntpath.relpath("../a/b")', '..\a\b')

`

294

486

`with support.temp_cwd(support.TESTFN) as cwd_dir:

`

295

``

`-

currentdir = os.path.basename(cwd_dir)

`

``

487

`+

currentdir = ntpath.basename(cwd_dir)

`

296

488

`tester('ntpath.relpath("a", "../b")', '..\'+currentdir+'\a')

`

297

489

`tester('ntpath.relpath("a/b", "../c")', '..\'+currentdir+'\a\b')

`

298

490

`tester('ntpath.relpath("a", "b/c")', '..\..\a')

`

`@@ -417,7 +609,7 @@ def test_ismount(self):

`

417

609

`# locations below cannot then refer to mount points

`

418

610

`#

`

419

611

`drive, path = ntpath.splitdrive(sys.executable)

`

420

``

`-

with support.change_cwd(os.path.dirname(sys.executable)):

`

``

612

`+

with support.change_cwd(ntpath.dirname(sys.executable)):

`

421

613

`self.assertFalse(ntpath.ismount(drive.lower()))

`

422

614

`self.assertFalse(ntpath.ismount(drive.upper()))

`

423

615

``