Skip to content
This repository has been archived by the owner on Dec 10, 2017. It is now read-only.

Commit

Permalink
Map uploads
Browse files Browse the repository at this point in the history
  • Loading branch information
strager committed Oct 16, 2011
1 parent d6e2e59 commit bd3f85f
Show file tree
Hide file tree
Showing 11 changed files with 253 additions and 8 deletions.
1 change: 1 addition & 0 deletions .gitignore
Expand Up @@ -3,3 +3,4 @@
/prod_tmp /prod_tmp
/demo /demo
/maps /maps
/server/maps
3 changes: 3 additions & 0 deletions server/.htaccess
Expand Up @@ -2,6 +2,9 @@ DirectorySlash On
Options FollowSymLinks Indexes Options FollowSymLinks Indexes
DirectoryIndex index.php DirectoryIndex index.php


# User upload is probably under 60MB
php_value upload_max_filesize 60M

RewriteEngine on RewriteEngine on


RewriteCond %{REQUEST_FILENAME} -d RewriteCond %{REQUEST_FILENAME} -d
Expand Down
3 changes: 3 additions & 0 deletions server/maps/.htaccess
@@ -0,0 +1,3 @@
# Prevent maps from being evil
Options -Indexes -ExecCGI
php_flag engine off
2 changes: 1 addition & 1 deletion server/src/config/default.inc.php
Expand Up @@ -11,7 +11,7 @@
$config->forum_table_prefix = 'forum_'; $config->forum_table_prefix = 'forum_';


$config->owp_script_path = APP_ROOT . '/../../owp.js'; // KINDA BROKEN FIXME $config->owp_script_path = APP_ROOT . '/../../owp.js'; // KINDA BROKEN FIXME
$config->owp_maps_root = APP_ROOT . '/../../maps'; $config->owp_maps_root = APP_ROOT . '/../maps';
$config->owp_skin_root = APP_ROOT . '/../../skin'; $config->owp_skin_root = APP_ROOT . '/../../skin';


$config->ga_account_id = null; $config->ga_account_id = null;
Expand Down
4 changes: 4 additions & 0 deletions server/src/lib/applicationfactory.php
Expand Up @@ -43,4 +43,8 @@ function new_socialnetworks($c) {
$sn->twitter = $this->twitter; $sn->twitter = $this->twitter;
return $sn; return $sn;
} }

function new_model_MapGateway($c) {
return new model_MapGateway($this->owp_maps_root, $c->create('PDO'));
}
} }
2 changes: 2 additions & 0 deletions server/src/lib/components/game/game.php
Expand Up @@ -13,6 +13,8 @@ function map($name) {
switch ($name) { switch ($name) {
case 'play': case 'play':
return 'components_game_play'; return 'components_game_play';
case 'upload':
return 'components_game_upload';
} }
} }


Expand Down
49 changes: 49 additions & 0 deletions server/src/lib/components/game/upload.php
@@ -0,0 +1,49 @@
<?php

class components_game_upload extends owpcomponent {
protected $templates;
protected $mapGateway;

function __construct(k_TemplateFactory $templates, model_MapGateway $mapGateway) {
$this->templates = $templates;
$this->mapGateway = $mapGateway;
}

function renderHTML() {
$this->document->setTitle('Upload map');

$t = $this->templates->create('upload-map');
return $t->render($this, array(
));
}

function postMultipart() {
// Konstrukt's file access crap won't give us a fucking file handle,
// so we do things the old fashion way.
$osz = @$_FILES['osz'];
if ($osz) {
if ($osz['error']) {
return new k_HttpResponse(500, 'Error code ' . $osz['error']);
}

$maps = null;
$oszFilePath = $osz['tmp_name'];
try {
$maps = $this->mapGateway->saveOsz($oszFilePath);
} catch (Exception $e) {
// wtf finally
unlink($oszFilePath);
throw $e;
}

if (empty($maps)) {
return new k_HttpResponse(500);
}

// TODO Uploaded maps page
return new k_SeeOther($this->url(array('game', 'play'), $maps[0]->urlParams()));
} else {
return new k_HttpResponse(400, 'Bad request'); // Bad request
}
}
}
144 changes: 141 additions & 3 deletions server/src/lib/model/mapgateway.php
Expand Up @@ -2,13 +2,13 @@


class model_MapGateway { class model_MapGateway {
private static $mapFields = 'id, song_name, artist_name, difficulty_name, mapper_name, map_root, map_file, UNIX_TIMESTAMP(submit_date) AS submit_date'; private static $mapFields = 'id, song_name, artist_name, difficulty_name, mapper_name, map_root, map_file, UNIX_TIMESTAMP(submit_date) AS submit_date';
//private static $osuFilenameRe = '/^((?<artist>.*) - (?<song>.*) \((?<mapper>.*)\) \\[(?<difficulty>.*)\\])\.osu$/i';


protected $mapsRoot; protected $mapsRoot;
protected $db; protected $db;


function __construct(owpjs $owpjs, PDO $db) { function __construct($mapsRoot, PDO $db) {
// FIXME MapGateway shouldn't depend upon owpjs $this->mapsRoot = $mapsRoot;
$this->mapsRoot = $owpjs->mapsRoot;
$this->db = $db; $this->db = $db;
} }


Expand All @@ -35,6 +35,21 @@ function getMapFromParts($parts) {
return new model_Map($this, $row); return new model_Map($this, $row);
} }


private function getMapsByIDs($ids) {
$quotedIDs = array();
foreach ($ids as $id) {
$quotedIDs[] = $this->db->quote((int) $id);
}

$result = $this->db->query('SELECT ' . self::$mapFields . ' FROM owp_maps WHERE id IN (' . implode(', ', $quotedIDs) . ')');

$maps = array();
while (($row = $result->fetch()) !== false) {
$maps[] = new model_Map($this, $row);
}
return $maps;
}

function getAllMaps() { function getAllMaps() {
$statement = $this->db->prepare('SELECT ' . self::$mapFields . ' FROM owp_maps WHERE is_public = 1'); $statement = $this->db->prepare('SELECT ' . self::$mapFields . ' FROM owp_maps WHERE is_public = 1');
$statement->execute(); $statement->execute();
Expand All @@ -46,4 +61,127 @@ function getAllMaps() {


return $maps; return $maps;
} }

function saveOsz($oszFilePath) {
$zip = new ZipArchive();
if ($zip->open($oszFilePath) !== true) {
throw new Exception('Failed to open osz');
}

$mapDirectory = realpath($this->makeMapDirectory());

$safeFiles = array();
$osuFiles = array();
for ($i = 0; $i < $zip->numFiles; ++$i) {
$stat = $zip->statIndex($i);
$filename = $stat['name'];
$outPath = relativePath($mapDirectory, $mapDirectory . '/' . $filename);

if (preg_match(',^[\\\\\\/\\.],', $outPath)) {
// File entry is something like "foo/../../../../etc/passwd"
// or this is a dot file.
// Ignore for security reasons
} else {
if (preg_match('/\\.osu$/i', $filename)) {
$osuFiles[] = $filename;
}

$safeFiles[] = $filename;
}
}

if (empty($osuFiles)) {
throw new Exception('Not an osz file');
}

if (!$zip->extractTo($mapDirectory, $safeFiles)) {
throw new Exception('Failed to extract osz');
}

$zip->close();

$statement = $this->db->prepare('INSERT INTO owp_maps (song_name, artist_name, difficulty_name, mapper_name, map_root, map_file) VALUES (:song_name, :artist_name, :difficulty_name, :mapper_name, :map_root, :map_file)');

$mapIDs = array();
foreach ($osuFiles as $osuFile) {
// Hacky way to read an .osu file
// Example:
//
// Title:THANK YOU FOR PLAYING
// Artist:SUPER STAR -MITSURU-
// Creator:Natteke
// Version:Normal
// Source:Beatmania IIDX

$songName = $artistName = $mapperName = $difficultyName = '(null)';

$path = $mapDirectory . '/' . $osuFile;
$source = file_get_contents($path);

$matches = null;
if (preg_match('/^Title:(.*)$/m', $source, $matches)) {
$songName = trim($matches[1]);
}
if (preg_match('/^Artist:(.*)$/m', $source, $matches)) {
$artistName = trim($matches[1]);
}
if (preg_match('/^Version:(.*)$/m', $source, $matches)) {
$difficultyName = trim($matches[1]);
}
if (preg_match('/^Creator:(.*)$/m', $source, $matches)) {
$mapperName = trim($matches[1]);
}

$statement->bindValue('song_name', $songName);
$statement->bindValue('artist_name', $artistName);
$statement->bindValue('difficulty_name', $difficultyName);
$statement->bindValue('mapper_name', $mapperName);
$statement->bindValue('map_root', relativePath($this->mapsRoot, $mapDirectory));
$statement->bindValue('map_file', $osuFile);
$statement->execute();
$id = $this->db->lastInsertId();
$statement->closeCursor();

$mapIDs[] = $id;
}

return $this->getMapsByIDs($mapIDs);
}

function makeMapDirectory() {
$dirnameChars = 'abcdefghijklmnopqrstuvwxyz0123456789';
$length = 10;
$tries = 0;

if (!is_writable($this->mapsRoot)) {
throw new Exception('Map root not write-able');
}

while (true) {
++$tries;

if ($tries % 10 === 0) {
// Every 10 tries, increase directory name length
++$length;
}

if ($tries >= 50) {
throw new Exception('Exhausted');
}

// Random directory name
$dirname = substr(str_shuffle(str_repeat($dirnameChars, $length)), 0, $length);
$fullPath = $this->mapsRoot . '/' . $dirname;
if (file_exists($fullPath)) {
continue;
}

$success = mkdir($fullPath);
if (!$success) {
continue;
}

return $fullPath;
}
}
} }
4 changes: 3 additions & 1 deletion server/src/templates/maplist.tpl.php
@@ -1,8 +1,10 @@
<h1>owp maps</h1> <h1>owp maps</h1>
<p><a href="<?php e(url(array('game', 'upload'))); ?>">Upload custom map</a></p>

<p>Choose a map to play:</p> <p>Choose a map to play:</p>


<ul> <ul>
<?php foreach ($maps as $map) { ?> <?php foreach ($maps as $map) { ?>
<li><a href="<?php echo e(url(array('game', 'play'), $map->urlParams())); ?>"><?php echo e($map->title()); ?></a></li> <li><a href="<?php e(url(array('game', 'play'), $map->urlParams())); ?>"><?php echo e($map->title()); ?></a></li>
<?php } ?> <?php } ?>
</ul> </ul>
6 changes: 6 additions & 0 deletions server/src/templates/upload-map.tpl.php
@@ -0,0 +1,6 @@
<h1>Upload map</h1>

<form method="POST" enctype="multipart/form-data">
<p><label>Upload osu! <tt>.osz</tt> file:<br>
<input type="file" required name="osz"></label> <input type="submit" value="Upload"></p>
</form>
43 changes: 40 additions & 3 deletions server/src/util.php
@@ -1,13 +1,50 @@
<?php <?php


// http://us2.php.net/manual/en/function.realpath.php#105876 function normalizePath($path, $ps = DIRECTORY_SEPARATOR) {
$back = 0;
$stack = array();

foreach (explode($ps, $path) as $index => $part) {
switch ($part) {
case '':
if ($index === 0) {
// Initial /
array_push($stack, '');
} else {
// Double / or trailing /; ignore
}
break;

case '.':
// Ignore
break;

case '..':
if (empty($stack)) {
++$back;
} else {
array_pop($stack);
}
break;

default:
array_push($stack, $part);
break;
}
}

return str_pad('', $back * 3, '..' . $ps) . implode($ps, $stack);
}

// Based upon http://us2.php.net/manual/en/function.realpath.php#105876
// Public domain // Public domain
function relativePath($from, $to, $ps = DIRECTORY_SEPARATOR) { function relativePath($from, $to, $ps = DIRECTORY_SEPARATOR) {
$from = normalizePath($from, $ps);
$to = normalizePath($to, $ps);
$arFrom = explode($ps, rtrim($from, $ps)); $arFrom = explode($ps, rtrim($from, $ps));
$arTo = explode($ps, rtrim($to, $ps)); $arTo = explode($ps, rtrim($to, $ps));


while(count($arFrom) && count($arTo) && ($arFrom[0] == $arTo[0])) while (count($arFrom) && count($arTo) && ($arFrom[0] == $arTo[0])) {
{
array_shift($arFrom); array_shift($arFrom);
array_shift($arTo); array_shift($arTo);
} }
Expand Down

0 comments on commit bd3f85f

Please sign in to comment.