Pages

12/13/2012

Ruby's Symbol-To-Proc

Here is how symbol-To-Proc works, explained in reduction steps.

First, I'll just create a variable 'words' as an array of strings, so we can have something to work with.
words = %w{test hello world}

Starts to use this symbol, '&' = symbol-to-proc
words.map(&:upcase)

'&' is symbol-to-proc character, which means it interprets the following symbol(':upcase') with .to_proc()
words.map(&:upcase.to_proc)

If you look into the source code of Symbol.to_proc ('to_proc' method in 'Symbol' class), you'll find it returns something like this back.
words.map(&proc{|obj,*args| obj.send(:upcase,*args)})

Then, '&' convert the proc into a block (notice the braces).
words.map{|obj,*args| obj.send(:upcase,*args)}

Since, '.map' only provides one argument for a block. The *args is neglected.
words.map{|obj| obj.send(:upcase)}

'.send' is similarly called directly with the symbol.
words.map{|obj| obj.upcase}

The result is
=> ["TEST", "WORLD", "HELLO"]

Now, let's try it with a method with 2 arguments, such as '.inject'

Here are the normal ways that we can write inject with.
(1..10).inject(:+)  #only with Ruby 1.9
(1..10).inject(&:+)
(1..10).inject(0,:+)  #only with Ruby 1.9
(1..10).inject(0,&:+)
(1..10).inject{|result,element| result+element}
(1..10).inject(0){|result,element| result+element}
*whereas 0 is initial value of the summation

Now, the reduction of the '.inject'.

Starting from the basic one.
(1..10).inject(&:+)

with Symbol-to-proc, it interprets this symbol (':+') as it is a proc (or called with .to_proc)
(1..10).inject(&:+.to_proc)

What .to_proc does with a symbol is
(1..10).inject(&proc{|obj,*args| obj.send(:+,*args)})

Then, this proc becomes a block for .inject
(1..10).inject{|obj,*args| obj.send(:+,*args)}

Since '.inject' can have a block that takes 2 arguments, I'll rename the arguments as
(1..10).inject{|result,element| result.send(:+,element)}

So then, '.send' can be substituted with a direct call
(1..10).inject{|result,element| result.+(element)}

Or with out using dot (only for operators)
(1..10).inject{|result,element| result + element}

Finally, the result is
=> 55

** remark: every code line is syntactically correct.

Suggest For Further Watching/Readings:
 - http://www.youtube.com/watch?v=aISNtCAZlMg
 - http://phrogz.net/symbol-to-proc-with-multiple-arguments
 - http://www.potstuck.com/2011/08/06/ruby-symbols-instead-of-blocks/

12/12/2012

Comparing TDD/BDD Framework for PHP

Comparing TDD/BDD Framework for PHP
 - TDD = Test Driven Development
 - BDD = Behavior Driven Development

This comparison focusing on unit-testing (testing at class/method level).
Browser integration feature is not in this scope

Type PHPUnit PHPUnit-Story PHPSpec PHPSpec2 Spectrum
Syntax
assertEquals('word', $wrapper->wrap('word', 5));
	$this->given('New game')
	     ->when('Player rolls', 5)
	     ->and('Player rolls', 5)
	     ->and('Player rolls', 3)
	     ->then('Score should be', 16);

	but actually use this internally:
		$this->assertEquals($arguments[0], $world['game']->score());
$this->spec($this->_bowling->score)->should->equal(0);
$this->getPayment()->getType()->shouldReturn(“visa”);
verify($person->firstName, '==', 'Bob');
Example Code Block from http://www.phpunit.de/manual/3.3/en/phpunit-book.html Example 13.1
<?php
require_once 'PHPUnit/Framework.php';
require_once 'BankAccount.php';
 
class BankAccountTest extends PHPUnit_Framework_TestCase
{
    protected $ba;
 
    protected function setUp()
    {
        $this->ba = new BankAccount;
    }
 
    public function testBalanceIsInitiallyZero()
    {
        $this->assertEquals(0, $this->ba->getBalance());
    }
 
    public function testBalanceCannotBecomeNegative()
    {
        try {
            $this->ba->withdrawMoney(1);
        }
 
        catch (BankAccountException $e) {
            $this->assertEquals(0, $this->ba->getBalance());
 
            return;
        }
 
        $this->fail();
    }
 
    public function testBalanceCannotBecomeNegative2()
    {
        try {
            $this->ba->depositMoney(-1);
        }
 
        catch (BankAccountException $e) {
            $this->assertEquals(0, $this->ba->getBalance());
 
            return;
        }
 
        $this->fail();
    }
}
?>
from http://sebastian-bergmann.de/archives/738-Support-for-BDD-and-Stories-in-PHPUnit-3.3.html
<?php
require_once 'PHPUnit/Extensions/Story/TestCase.php';
 
require_once 'BowlingGame.php';
 
class BowlingGameSpec extends PHPUnit_Extensions_Story_TestCase
{
    /**
     * @scenario
     */
    public function scoreForOneSpareIs16()
    {
        $this->given('New game')
             ->when('Player rolls', 5)
             ->and('Player rolls', 5)
             ->and('Player rolls', 3)
             ->then('Score should be', 16);
    }
 
    public function runGiven(&$world, $action, $arguments)
    {
        switch($action) {
            case 'New game': {
                $world['game']  = new BowlingGame;
                $world['rolls'] = 0;
            }
            break;
 
            default: {
                return $this->notImplemented($action);
            }
        }
    }
 
    public function runWhen(&$world, $action, $arguments)
    {
        switch($action) {
            case 'Player rolls': {
                $world['game']->roll($arguments[0]);
                $world['rolls']++;
            }
            break;
 
            default: {
                return $this->notImplemented($action);
            }
        }
    }
 
    public function runThen(&$world, $action, $arguments)
    {
        switch($action) {
            case 'Score should be': {
                for ($i = $world['rolls']; $i < 20; $i++) {
                    $world['game']->roll(0);
                }
 
                $this->assertEquals($arguments[0], $world['game']->score());
            }
            break;
 
            default: {
                return $this->notImplemented($action);
            }
        }
    }
}
?>
from https://github.com/phpspec/phpspec
<?php
class DescribeNewBowlingGame extends \PHPSpec\Context
{

    private $_bowling;

    public function before()
    {
        $this->_bowling = new Bowling;
    }

    public function itShouldScore0ForGutterGame()
    {
        for ($i=1; $i<=20; $i++) {
            $this->_bowling->hit(0); // someone is really bad at bowling!
        }
        $this->spec($this->_bowling->score)->should->equal(0);
    }

}
from http://phpspec.net/
<?php

namespace spec;

use PHPSpec2\ObjectBehavior;

class Markdown extends ObjectBehavior
{
    function it_should_be_initializable()
    {
        $this->shouldHaveType('Markdown');
    }

    function it_converts_plain_text_to_html_paragraphs()
    {
        $this->toHtml("Hi, there")->shouldReturn("<p>Hi, there</p>");
    }

    public function toHtml()
    {
        return "<p>Hi, there</p>";
    }
}
from https://bitbucket.org/mkharitonov/spectrum/overview
<?php
require_once __DIR__ . '/spectrum/init.php';

describe('AddressBook', function(){
    beforeEach(function(){
        world()->addressBook = new AddressBook();
    });

    context('"MySql" driver', function(){
        beforeEach(function(){
            world()->addressBook->setDriver(new drivers\MySql());
        });
    });

    context('"Files" driver', function(){
        beforeEach(function(){
            world()->addressBook->setDriver(new drivers\Files());
        });
    });

    it('Should find person by first name', function(){
        $person = world()->addressBook->findPerson('Bob');
        verify($person->firstName, '==', 'Bob');
    });

    it('Should find person by phone number in any format', array(
        '+7 (495) 123-456-7',
        '(495) 123-456-7',
        '123-456-7',
    ), function($phoneNumber){
        $person = world()->addressBook->findPerson($phoneNumber);
        verify($person->phoneNumber, '==', '+74951234567');
    });
});

\spectrum\RootDescribe::run();
Matchers from phpunit/PHPUnit/Framework/Assert/Functions.php
  • any()
  • anything()
  • arrayHasKey($key)
  • assertArrayHasKey($key, array $array,
  • $message = '')
  • assertArrayNotHasKey($key, array $array,
  • $message = '')
  • assertAttributeContains($needle,
  • $haystackAttributeName, $haystackClassOrObject, $message = '', $ignoreCase = FALSE, $checkForObjectIdentity = TRUE)
  • assertAttributeContainsOnly($type,
  • $haystackAttributeName, $haystackClassOrObject, $isNativeType = NULL, $message = '')
  • assertAttributeCount($expectedCount,
  • $haystackAttributeName, $haystackClassOrObject, $message = '')
  • assertAttributeEmpty(
  • $haystackAttributeName, $haystackClassOrObject, $message = '')
  • assertAttributeEquals($expected,
  • $actualAttributeName, $actualClassOrObject, $message = '', $delta = 0, $maxDepth = 10, $canonicalize = FALSE, $ignoreCase = FALSE)
  • assertAttributeGreaterThan($expected,
  • $actualAttributeName, $actualClassOrObject, $message = '')
  • assertAttributeGreaterThanOrEqual(
  • $expected, $actualAttributeName, $actualClassOrObject, $message = '')
  • assertAttributeInstanceOf($expected,
  • $attributeName, $classOrObject, $message = '')
  • assertAttributeInternalType($expected,
  • $attributeName, $classOrObject, $message = '')
  • assertAttributeLessThan($expected,
  • $actualAttributeName, $actualClassOrObject, $message = '')
  • assertAttributeLessThanOrEqual($expected,
  • $actualAttributeName, $actualClassOrObject, $message = '')
  • assertAttributeNotContains($needle,
  • $haystackAttributeName, $haystackClassOrObject, $message = '', $ignoreCase = FALSE, $checkForObjectIdentity = TRUE)
  • assertAttributeNotContainsOnly($type,
  • $haystackAttributeName, $haystackClassOrObject, $isNativeType = NULL, $message = '')
  • assertAttributeNotCount($expectedCount,
  • $haystackAttributeName, $haystackClassOrObject, $message = '')
  • assertAttributeNotEmpty(
  • $haystackAttributeName, $haystackClassOrObject, $message = '')
  • assertAttributeNotEquals($expected,
  • $actualAttributeName, $actualClassOrObject, $message = '', $delta = 0, $maxDepth = 10, $canonicalize = FALSE, $ignoreCase = FALSE)
  • assertAttributeNotInstanceOf($expected,
  • $attributeName, $classOrObject, $message = '')
  • assertAttributeNotInternalType($expected,
  • $attributeName, $classOrObject, $message = '')
  • assertAttributeNotSame($expected,
  • $actualAttributeName, $actualClassOrObject, $message = '')
  • assertAttributeSame($expected,
  • $actualAttributeName, $actualClassOrObject, $message = '')
  • assertClassHasAttribute($attributeName,
  • $className, $message = '')
  • assertClassHasStaticAttribute(
  • $attributeName, $className, $message = '')
  • assertClassNotHasAttribute($attributeName,
  • $className, $message = '')
  • assertClassNotHasStaticAttribute(
  • $attributeName, $className, $message = '')
  • assertContains($needle, $haystack,
  • $message = '', $ignoreCase = FALSE, $checkForObjectIdentity = TRUE)
  • assertContainsOnly($type, $haystack,
  • $isNativeType = NULL, $message = '')
  • assertCount($expectedCount, $haystack,
  • $message = '')
  • assertEmpty($actual, $message = '')
  • assertEqualXMLStructure(DOMElement
  • $expectedElement, DOMElement $actualElement, $checkAttributes = FALSE, $message = '')
  • assertEquals($expected, $actual, $message
  • = '', $delta = 0, $maxDepth = 10, $canonicalize = FALSE, $ignoreCase = FALSE)
  • assertFalse($condition, $message = '')
  • assertFileEquals($expected, $actual,
  • $message = '', $canonicalize = FALSE, $ignoreCase = FALSE)
  • assertFileExists($filename, $message = '')
  • assertFileNotEquals($expected, $actual,
  • $message = '', $canonicalize = FALSE, $ignoreCase = FALSE)
  • assertFileNotExists($filename, $message =
  • '')
  • assertGreaterThan($expected, $actual,
  • $message = '')
  • assertGreaterThanOrEqual($expected,
  • $actual, $message = '')
  • assertInstanceOf($expected, $actual,
  • $message = '')
  • assertInternalType($expected, $actual,
  • $message = '')
  • assertJsonStringEqualsJsonString(
  • $expectedJson, $actualJson, $message = '')
  • assertJsonStringNotEqualsJsonString(
  • $expectedJson, $actualJson, $message = '')
  • assertJsonStringEqualsJsonFile(
  • $expectedFile, $actualJson, $message = '')
  • assertJsonStringNotEqualsJsonFile(
  • $expectedFile, $actualJson, $message = '')
  • assertJsonFileNotEqualsJsonFile(
  • $expectedFile, $actualFile, $message = '')
  • assertJsonFileEqualsJsonFile(
  • $expectedFile, $actualFile, $message = '')
  • assertLessThan($expected, $actual,
  • $message = '')
  • assertLessThanOrEqual($expected, $actual,
  • $message = '')
  • assertNotContains($needle, $haystack,
  • $message = '', $ignoreCase = FALSE, $checkForObjectIdentity = TRUE)
  • assertNotContainsOnly($type, $haystack,
  • $isNativeType = NULL, $message = '')
  • assertNotCount($expectedCount, $haystack,
  • $message = '')
  • assertNotEmpty($actual, $message = '')
  • assertNotEquals($expected, $actual,
  • $message = '', $delta = 0, $maxDepth = 10, $canonicalize = FALSE, $ignoreCase = FALSE)
  • assertNotInstanceOf($expected, $actual,
  • $message = '')
  • assertNotInternalType($expected, $actual,
  • $message = '')
  • assertNotNull($actual, $message = '')
  • assertNotRegExp($pattern, $string,
  • $message = '')
  • assertNotSame($expected, $actual, $message
  • = '')
  • assertNotSameSize($expectedCount,
  • $haystack, $message = '')
  • assertNotTag($matcher, $actual, $message =
  • '', $isHtml = TRUE)
  • assertNull($actual, $message = '')
  • assertObjectHasAttribute($attributeName,
  • $object, $message = '')
  • assertObjectNotHasAttribute(
  • $attributeName, $object, $message = '')
  • assertRegExp($pattern, $string, $message =
  • '')
  • assertSame($expected, $actual, $message =
  • '')
  • assertSameSize($expected, $actual,
  • $message = '')
  • assertSelectCount($selector, $count,
  • $actual, $message = '', $isHtml = TRUE)
  • assertSelectEquals($selector, $content,
  • $count, $actual, $message = '', $isHtml = TRUE)
  • assertSelectRegExp($selector, $pattern,
  • $count, $actual, $message = '', $isHtml = TRUE)
  • assertStringEndsNotWith($suffix, $string,
  • $message = '')
  • assertStringEndsWith($suffix, $string,
  • $message = '')
  • assertStringEqualsFile($expectedFile,
  • $actualString, $message = '', $canonicalize = FALSE, $ignoreCase = FALSE)
  • assertStringMatchesFormat($format,
  • $string, $message = '')
  • assertStringMatchesFormatFile($formatFile,
  • $string, $message = '')
  • assertStringNotEqualsFile($expectedFile,
  • $actualString, $message = '', $canonicalize = FALSE, $ignoreCase = FALSE)
  • assertStringNotMatchesFormat($format,
  • $string, $message = '')
  • assertStringNotMatchesFormatFile(
  • $formatFile, $string, $message = '')
  • assertStringStartsNotWith($prefix,
  • $string, $message = '')
  • assertStringStartsWith($prefix, $string,
  • $message = '')
  • assertTag($matcher, $actual, $message =
  • '', $isHtml = TRUE)
  • assertThat($value,
  • PHPUnit_Framework_Constraint $constraint, $message = '')
  • assertTrue($condition, $message = '')
  • assertXmlFileEqualsXmlFile($expectedFile,
  • $actualFile, $message = '')
  • assertXmlFileNotEqualsXmlFile(
  • $expectedFile, $actualFile, $message = '')
  • assertXmlStringEqualsXmlFile(
  • $expectedFile, $actualXml, $message = '')
  • assertXmlStringEqualsXmlString(
  • $expectedXml, $actualXml, $message = '')
  • assertXmlStringNotEqualsXmlFile(
  • $expectedFile, $actualXml, $message = '')
  • assertXmlStringNotEqualsXmlString(
  • $expectedXml, $actualXml, $message = '')
  • at($index)
  • atLeastOnce()
  • attribute(PHPUnit_Framework_Constraint
  • $constraint, $attributeName)
  • attributeEqualTo($attributeName, $value,
  • $delta = 0, $maxDepth = 10, $canonicalize = FALSE, $ignoreCase = FALSE)
  • classHasAttribute($attributeName)
  • classHasStaticAttribute($attributeName)
  • contains($value, $checkForObjectIdentity =
  • TRUE)
  • containsOnly($type)
  • equalTo($value, $delta = 0, $maxDepth =
  • 10, $canonicalize = FALSE, $ignoreCase = FALSE)
  • exactly($count)
  • fileExists()
  • greaterThan($value)
  • greaterThanOrEqual($value)
  • identicalTo($value)
  • isEmpty()
  • isFalse()
  • isInstanceOf($className)
  • isNull()
  • isTrue()
  • callback($callback)
  • isType($type)
  • lessThan($value)
  • lessThanOrEqual($value)
  • logicalAnd()
  • logicalNot(PHPUnit_Framework_Constraint
  • $constraint)
  • logicalOr()
  • logicalXor()
  • matches($string)
  • matchesRegularExpression($pattern)
  • never()
  • objectHasAttribute($attributeName)
  • onConsecutiveCalls()
  • once()
  • returnArgument($argumentIndex)
  • returnCallback($callback)
  • returnSelf()
  • returnValue($value)
  • returnValueMap(array $valueMap)
  • stringContains($string, $case = TRUE)
  • stringEndsWith($suffix)
  • stringStartsWith($prefix)
  • throwException(Exception $exception)
same as PHPUnit
  • be($expected)
  • equal($expected)
  • beEqualTo($expected)
  • beAnInstanceOf($expected)
  • beSet($expected)
  • beArray($expected)
  • beObject($expected)
  • beString($expected)
  • beInt($expected)
  • beFloat($expected)
  • beEmpty($expected)
  • beNull($expected)
  • beFalse($expected)
  • beTrue($expected)
  • beGreaterThanOrEqualTo($expected)
  • beGreaterThan($expected)
  • beLessThanOrEqualTo($expected)
  • beLessThan($expected)
  • containText($expected)
  • haveKey($expected)
  • customMatcher($expected)
  • predecate - ex. beClass,beAClass,beAnClass,haveName,haveAName,haveAnName
  • throwException
In src/PHPSpec2/Matcher
  • BasicMatcher.php
    • positiveMatch($name, $subject, array $arguments)
    • negativeMatch($name, $subject, array $arguments)
    • matches($subject, array $arguments)
    • getFailureException($name, $subject, array $arguments)
    • getNegativeFailureException($name, $subject, array $arguments)
  • ComparisionMatcher.php
    • beLike($subject, array $arguments) : { $subject==$arguments[0]}
  • CountMatcher.php
    • haveCount($subject, array $arguments) : {$arguments[0] === count($subject)}
  • IdentityMatcher
    • return($subject, array $arguments) : {$subject === $arguments[0]}
    • be($subject, array $arguments) : {$subject === $arguments[0]}
    • equal($subject, array $arguments) : {$subject === $arguments[0]}
    • beEqualTo($subject, array $arguments) : {$subject === $arguments[0]}
  • InlineMatcher.php
  • MatchersCollection.php
  • ObjectStateMatcher.php
    • beAbstract
    • beSimple
    • beUserDefined
    • beFinal
    • haveProperty
    • haveAnything
    • haveMethod
    • haveProperty
  • ScalarMatcher.php
    • is_bool
    • beInt
    • beFloat
    • beString
    • beBoolean
  • ThrowMatcher.php
    • throw
  • TypeMatcher.php
    • beAnInstanceOf($subject, array $arguments) : {(null !== $subject) && ($subject instanceof $arguments[0])}
    • returnAnInstanceOf($subject, array $arguments) : {(null !== $subject) && ($subject instanceof $arguments[0])}
    • haveType($subject, array $arguments) : {(null !== $subject) && ($subject instanceof $arguments[0])}
    • implement($subject, array $arguments) : {(null !== $subject) && ($subject instanceof $arguments[0])}
  • eq
  • false
  • gt
  • gte
  • ident : (use ===)
  • instanceofMatcher
  • lt
  • lte
  • null
  • throwException
  • true
Installation
  • PEAR:
    pear config-set auto_discover 1
    pear install pear.phpunit.de/PHPUnit
  • Composer: "phpunit/phpunit" : "3.7.10"
    {
        "require-dev": {
            "phpunit/phpunit": "3.7.*"
        }
    }
  • PHP Archive(PHAR)
    wget http://pear.phpunit.de/get/phpunit.phar
    chmod +x phpunit.phar
Last Update
(from 2012-12-12)
2012-12-10 2012-10-05 2012-09-08 2012-10-08 2012-11-13
Documentation/Example Document: http://www.phpunit.de/manual/3.7/en/index.html (English, French, Japanese) Document/Example: http://www.phpunit.de/manual/current/en/behaviour-driven-development.html (English, French, Japanese) No online document! (To read it compile it from /doc from source from github) Document: http://phpspec.net/#manual Document: http://mkharitonov.net/spectrum/ (old and only russian)
Example: http://spectrum-framework.org/ (English+Live Result)

12/10/2012

ติดตั้ง Naver Line ให้ใช้ภาษาไทยได้บน Linux

วิธีการติดตั้ง Line บน Ubuntu Linux ผ่าน Wine

  1. ต้องมี Wine ติดตั้งอยู่แล้ว  ถ้ายังไม่มีให้ไปติดตั้งใหม่ก่อน .. แนะนำว่าให้ใช้ Line version ใหม่ๆ, ตอนเขียนลง wine 1.5.19, wine-mono0.0.8)
  2. Download ตัวติดตั้ง Line สำหรับ Windows (http://dl.desktop.line.naver.jp/naver/LINE/win/LineInst.exe)
  3. สั่งติดตั้ง Line ตามปกติ ( double click ตัวที่เพิ่งดาวน์โหลดมา หรือสั่ง wine [pathไปหาไฟล์นั้น])
  4. หลังจากติดตั้งเสร็จแล้ว  แก้ภาษาไทยใน Line โดย
    1. copy fonts สำหรับ Windows ติดตั้งลงใน Wine ก่อน
      1. สำหรับ font ที่จะใช้จริงๆ คือ Tahoma (ดาวน์โหลดได้ ที่นี่ หรือจะดึงจาก windows เครื่องอื่นๆก็ได้)  และจะติดตั้ง font ตัวอื่นๆจาก Windows ด้วยก็ได้
      2. พอได้ฟอนต์แล้ว (ไฟล์ .ttf) ให้ copy ไปวางไว้ใน C:\Windows\fonts ของ Line ซึ่งโดยทั่วไปจะอยู่ที่  ~/.wine/drive_c/windows/fonts
    2. รันคำสั่งนี้ใน command เพื่อไปแก้ไขให้ Line เปลี่ยน font ไปใช้เป็น tahoma ทั้งหมด
    3. (คำสั่งนี้ใช้กับถ้าติดตั้ง Line แบบทั่วๆไปที่จะมี path ของ C:\ อยู่ที่ ~/.wine/drive_c/ )
      
      
      บน Linux x86:
      
      sed -i "s/font-family:[^;]*;/font-family:'tahoma';/g" ~/.wine/drive_c/Program\ Files/Naver/LINE/res/skin/basic/css/*
      
      บน Linux x64:
      
      sed -i "s/font-family:[^;]*;/font-family:'tahoma';/g" ~/.wine/drive_c/Program\ Files\ \(x86\)/Naver/LINE/res/skin/basic/css/*
      
  5. ถ้าเปิด Line อยู่ให้ปิดก่อน แล้วทำการเปิดโปรแกรม Line เพื่อเล่น ตามปกติ





 Thanks to Xcode Devil สำหรับวิธีในการทำ

11/22/2012

Using PHPSpec with CodeIgniter

Below is how I use PHPSpec together with CodeIgniter.

First, I create a folder called "phpspec" inside CodeIgniter folder, so I can put all spec files there.
Then, I created a file called "ci_bootstrap.php" almost as same as what's in "system/core/CodeIgniter.php" to initialize CodeIgniter.
Lastly, every test spec file is just needed to require "ci_bootstrap.php" and initialize CodeIgniter in "beforeAll()" before it can be used normally.

 File Structure:
/               (codeigniter base folder)
  + application
  + system
  - phpspec
    - ci_bootstrap.php
    - BranchSpec.php
    - ...

content of ci_bootstrap.php
<?php

/* CI_BootStrap by Anidear
 * 
 * This file is used for starting up CodeIgniter without calling Router.
 * After require/inclide this file, a Controller can be created by using:
 *   $class = 'main';
 *   include_once(APPPATH.'controllers/'.$class.'.php');
 *   $CI = new $class();
 * 
 * Model and Library can be loaded after getting a Controller, such as:
 *   $CI->load->model('Data_model');
 *   $CI->load->library('database');
 * 
 */

//disable E_STRICT in phpspec for codeigniter
error_reporting(E_ALL|~E_STRICT);


define('BASEPATH',realpath('../system/').'/'); //set absolute path to CI system/
define('APPPATH', '../application/'); //set relative path to CI application
set_include_path(
 get_include_path().PATH_SEPARATOR.
 realpath(APPPATH).PATH_SEPARATOR.
 realpath(BASEPATH).PATH_SEPARATOR.'phpspec');  //adding both path for short-name class calling./* If using
 *  - require_once(BASEPATH.'core/CodeIgniter.php');
 * or
 *  - require 'index.php';
 * It needs URL for Router to load specific class & method.
 * But we need to load it manually (by-pass Router)
 */

//load CI (manually)
require BASEPATH.'core/Common.php';
require APPPATH.'config/constants.php';
require BASEPATH.'core/Controller.php';
$_SERVER['REMOTE_ADDR'] = '127.0.0.1';
function &get_instance() { return CI_Controller::get_instance(); }
$CFG =& load_class('Config', 'core');
$GLOBALS['CFG'] = $CFG;     //fix by Anidear
$UNI =& load_class('Utf8', 'core');
$URI =& load_class('URI', 'core');
//$RTR =& load_class('Router', 'core');  //bypass router
//$RTR->_set_routing();  //bypass router
$OUT =& load_class('Output', 'core');
$SEC =& load_class('Security', 'core');
$GLOBALS['SEC'] = $SEC;     //fix by Anidear
$IN =& load_class('Input', 'core');
$LANG =& load_class('Lang', 'core');

//load controller (by Router)
//include(APPPATH.'controllers/'.$RTR->fetch_directory().$RTR->fetch_class().'.php');


Then every test specification file would require this file at top and initialize CI (as in "beforeAll()"  ) before it can be used.

For example: content of BranchSpec.php
<?php
require 'ci_bootstrap.php';

class DescribeBranch extends \PHPSpec\Context {

 //this variable will be used as $this->model
 private $model = NULL;  

 public function beforeAll(){
  //load a controller
  $class = 'main';
  include_once(APPPATH.'controllers/'.$class.'.php');
  $CI = new $class();
  $GLOABALS['CI'] = $CI;  //assign CI as global variable

  //load a model
  $CI->load->model('Branch_model');
  $this->model = $CI->Branch_model;  //assign $this->model
 }
 public function itShouldReturnBranchesAsArray(){
  //$this->pending();
  $branches = $this->model->branches();
  $this->spec($branches)->should->beArray();
  $this->spec($branches)->shouldNot->beEmpty();
 }
}
Above file is for testing a model.

If you need to use any of  "$this->" functions, such as $this->db->get(...)."
A way to do it is to declare "global $CI;" and use "$CI->" instead of "$this-&gt", like this:
global $CI;
$CI->db ->where('branch_id',$branch_id)
 ->where('user_id',$user_id)
 ->where('`date`','2012-11-11')
 ->get('calendar');
Update:

  • I just found that PHPSpec has updated a method to install PHPSpec in a generic project. Although, I am not sure yet how it will have any effect on testing with CodeIgniter.
  • I just learned there are 2 versions of PHPSpec: PHPSpec and PHPSpec2. They are very different. Code from PHPSpec does not work with PHPSpec2. And the code in this blog post is based on PHPSpec (not PHPSpec2). So, if you use PHPSpec2, it will not work.
  • Installing PHPSpec can be done by PHP Composer, just very similar to PHPSpec2 in http://www.phpspec.net/ , except changing the line "phpspec/phpspec2": "*" to "phpspec/phpspec": "*"  Then run composer update to install PHPSpec.


10/09/2012

Display Content of PHP Files Via LFI Using php://filter

I read this blog post and found a clever idea to retrieve source code file from a PHP file using an local file inclusion(LFI) vulnerability. Thanks to @brutelogic who seems to be the original creator if this trick.

a LFI vulnerability is where you can input a file name (or part of it) into URL as a parameter and a PHP  from the URL responds by reading the file out to users.

For example:
a PHP file named "testlfi.php" has a content like this
<?php  include($_GET['file']);  ?>
When querying for the testlfi.php, we can query it like this

http://localhost/testphp/testlfi.php?file=input.php

This means, we give "file" parameter with value "input.php". Therefore, "testlfi.php" would execute "include('input.php')" and prints out the content of "input.php" out to the screen.
The result is now like this:


Unfortunately, LFI is available from using "include","require", "require_once" or "include_once" which actually interpret PHP commands inside the PHP file before it displays the content.
So we cannot really see what is the actual PHP content in the PHP file.

But!!! There is a way to by-pass that process.

using php://filter/convert.base64-encode/resource=input.php
So, the full requesting URL is
http://localhost/testphp/testlfi.php?file=php://filter/convert.base64-encode/resource=input.php

And the result is now becomes:

It is encrypted in BASE64 format.
It can easily be decrypted by a simple Ruby script, or you can use online Base64 decrypter online (Google it)
So we can now see the content of the file like this:

I use irb (interactive-ruby-shell) and use the method "unpack" with "m*" as its argument to get content of the base64-encrypted value.

As you can see from the picture,
the file "input.php" does not just contain the word "Hello World", but it contains "secret" which is now revealed.

9/23/2012

วิธีคำนวนปฏิทินร้อยปีในใจ (พร้อมเหตุและผล) ภาค 2 สำหรับศตวรรษอื่น

จากโพสต์เดิมที่เคยโพสต์เรื่อง วิธีคำนวนปฏิทินร้อยปีในใจ (พร้อมเหตุและผล)

พอดีมีคนถามเรื่องการคิดถอยหลังไปในปีที่ตำกว่าปี 2000
วิธีคิดก็ได้ 2 อย่าง คือ
  1. คำนวนในปี 2000 ให้เสร็จก่อน แล้วค่อยหักลบไปในแต่ละปี (ใช้วิธีง่ายตามที่เขียนไว้ในโพสต์เก่า)
  2. หรือ ย้อนกลับไปหาสูตรคำนวนใหม่ แล้วก็ค่อยใช้วิธีเร็วคิด
ตัวอย่าง คำนวนหาวันที่ 23 เดือน กันยายน 1970 

แบบที่ 1 

ก็คำนวนวันที่ 23 ก.ย. 2000 ก่อน  ซึ่งก็จะได้  23 + 4 + 0 = 27 = 7x3 + 6 = 6 (วันเสาร์)
แต่เนื่องจากย้อนกลับไป 30 ปี
ซึ่งระหว่าง 30 ปีนี้ จะผ่านปีที่เป็น อธิกสุรธิน ตามนี้
2000, 1996, 1992, 1988, 1984, 1980, 1976, 1972 รวม 8 ปี
ถ้า 1 ปี วันเปลี่ยนไป 1 วัน  ผ่านไป 30 ปี วันก็จะเปลี่ยนไป 30 วัน
แต่เนื่องจากมี 8 อธิกสุรธิน ก็เลยต้องเปลี่ยนไปอีก 8 วัน รวมเป็น 38 วัน
หรือเปลี่ยนไปจริงๆ 7x5 + 3 = 3 วัน
ดังนั้น ทำให้ 23 ก.ย. 1970  ก็จะเป็นวัน 6-3 = 3 (วันพุธ)

ตรงที่คิดนับปี สามารถ นำกฎการนับปีแบบ 4ปี และ 12 ปี มาช่วยนับได้
คือ ทุก 12 ปี วันเปลี่ยน 1 วัน, ทุก 4 ปี วันเปลี่ยน 5 วัน
แต่ว่าต้องระวังว่า เมื่อนับถอยหลังไปแล้ว ปีที่เหลืออยู่ อาจจะเป็นปี อธิกสุรธินได้
เช่น ถ้านับจาก 2000 ลงไปหา 1970 แล้วพยายามใช้กฎ 12 กับ 4 ปีให้ได้มากที่สุด
จะสามารถลงไปได้ 12x2 + 4x1 ปี   (ซึ่งจะเปลี่ยนวันไป 1x2 + 5x1 = 7 วัน = 0 วัน)
แต่เนื่องจากปีที่ย้อนลงไปถึงตอนนี้คือ 1972 ซึ่งเป็นปีอธิกสุรธินด้วย
ดังนั้นจะย้อนให้ถึง 1970 วันจะเปลี่ยนไป  1(จากปี 1971) + 2(จากปี 1972) = 3 วัน
ซึ่งก็จะได้ว่า  23 ก.ย. 1970 เป็นวัน 6-3 = 3 (วันพุธ) เหมือนกัน

ถ้าจะย้อนไปเป็นทีละ 100 ปี
ก็จะได้ว่า 100ปี ทำให้วันเปลี่ยนไป 5 วัน  
เพราะ 100ปี = 12x8+4x1 ปี ทำให้วันเปลี่ยนไป 1วันx8 + 5วันx1 = 13 วัน= 6 วัน
แต่ทว่า ปีที่สามารถหารลง 100 ได้ทุกปี จะไม่นับ เป็นปีอธิกสุรธิน
ดังนั้น ถ้าตัดปีสุดท้าย(ปีที่ 100 ออก) วันที่เปลี่ยนจริงๆ ก็จะเป็นแค่ 6-1 = 5 วัน

นี่ทำให้ ถ้าวันที่ 1 ม.ค. 2000 เป็น 6 (วันเสาร์) แล้ว
วันที่ 1 ม.ค. 1900 จะเป็น 6-5 = 1 (วันจันทร์)
วันที่ 1 ม.ค. 1800 จะเป็น 1-5 = (7+1)-5 = 3 วันพุธ

แต่ถ้าจะนับเพิ่มไปทีละ 100 ปี ก็เช่นเดียวกัน
แต่อย่าลืมว่า ปี 2000 เป็นปีอธิกสุรทินพิเศษ(คือหาร 100 ลงตัว และหาร 400 ลงตัวด้วย)
และเมื่อเริ่มนับตั้งแต่ต้นปีแบบนี้ ก็จะนับผ่านวันที่ 29 ก.พ. 2000 ด้วย
ดังนั้น 1 ม.ค. 2100 จะเป็น 6 + 5 + 1 = 12 = 7 + 5 = 5 (วันศุกร์)  (+1เพิ่มเนื่องจากปี 2000)
และ 1 ม.ค. 2200 จะเป็น 5 + 5 = 10 = 7 + 3 = 3 (วันพุธ)

แบบที่ 2

คือสร้างสูตรคำนวนใหม่หมดเลย

ก่อนอื่นลอง สร้างชุดคำนวนเดือนใหม่สำหรับชุดปี 1900 - 1999 ก่อน
โดยหาก่อนว่า ถ้าเป็นวันที่ 1 ม.ค. 1900 เป็นวันอะไร  ได้ว่าเป็น วันจันทร์ (1)
เวลาจำคำนวนหา ค่าเดือน ม.ค. จากวิธีคิดที่ว่า ใช้
วันที่ + ค่าของเดือน + ค่าของปี = วันในสัปดาห์
ดังนั้น 1 ม.ค. 1900
เป็นเลข  1 + ค่าเดือนม.ค. + 0 = 1 (วันจันทร์)
จึงทำให้ค่าเดือน ม.ค. จะเท่ากับ 0
(หมายเหตุ ปี 1900 ไม่เป็นอธิกสุรธิน เพราะเป็นปีที่หารด้วย 100 ลงตัวแต่หาร 400 ไม่ลงตัว)

แล้วจึงนำไปใช้สร้างตารางเดือน โดยใช้ คม: +3 , ยน: +2, เดือนก.พ.: +0
ก็จะได้ว่า
19002000
มกราคม06
กุมภาพันธ์32
มีนาคม32
เมษายน65
พฤษภาคม10
มิถุนายน43
กรกฎาคม65
สิงหาคม21
กันยายน54
ตุลาคม06
พฤศจิกายน32
ธันวาคม64

แล้วก็นำไปคิดปกติ ก็คือ

วันที่ 23 ก.ย. 1970
วันที่ : 23  (ลดรูปให้อยู่ภายใน 7 วันจะเหลือ 7x3 + 2 = 2 )
เดือน : ก.ย. = 5
ปี : 1970 = ปีอธิกสุรทินใกล้สุดคือ 68 + 2ปี = (12ปีx5 + 4ปีx2) + 2 = 17 วัน (ลดรูปแล้วจะเหลือ 3 วัน)
**หมายเหตุ 12 ปีวนเปลี่ยน 1 วัน, 4 ปีวันเปลี่ยน 5 วัน, 1 ปีวันเปลี่ยน 1 วัน

ดังนั้นวันนั้นคือ วันที่ + เดือน + ปี = 2 + 5 + 3 = 10 = 7 + 3 = 3 = วันพุธ


แต่จะสังเกตุได้ว่า ทุกเดือนในสูตรใหม่สำหรับปี 1900 นี้ เพิ่มไปจากปี 2000 แค่ 1 วัน
ถ้าเป็นแบบนี้
เราก็ใช้ตัวเลขแทนเดือนชุดเดิมของปี 2000 ได้ แต่ค่าบวกเพิ่มไปสำหรับแต่ละศตวรรษไปแทนก็ได้

ถ้าทำตามนี้สำหรับปีเริ่มต้นศตวรรษอื่นๆจะได้ว่า
2200 ถึง 2299 = ให้เพิ่ม 3
2100 ถึง 2199 = ให้เพิ่ม 5
2000 ถึง 2099 = ให้เพิ่ม 0
1900 ถึง 1999 = ให้เพิ่ม 1
1800 ถึง 1899 = ให้เพิ่ม 3
ตัวอย่างวิธีใช้ก็คือ

3 ก.ย. 2112
วันที่ : 3
เดือน : ก.ย. = 4  (ใช้ปี 2000 เป็นฐาน)
ปี : 2112 = ปีอธิกสุรทินใกล้สุดคือ 2112 ก็จะได้ +1 เลย (12ปี = +1วัน)
ดังนั้นวันนั้นคือ วันที่ + เดือน + ปี + เลขเพิ่มสำหรับศตวรรษ= 3 + 4 + 1 + 5 = 13 = 7+6 = 6 วันเสาร์


วันที่ 23 ก.ย. 1970
วันที่ : 23  (ลดรูปให้อยู่ภายใน 7 วันจะเหลือ 7x3 + 2 = 2 )
เดือน : ก.ย. = 4 (ใช้ปี 2000 เป็นฐาน)
ปี : 1970 = 70ปี = อธิกสุรทินใกล้สุดคือ 68 + 2ปี = (12ปีx5 + 4ปีx2) + 2 = 17 วัน (ลดรูปแล้วจะเหลือ 3 วัน)
**หมายเหตุ 12 ปีวนเปลี่ยน 1 วัน, 4 ปีวันเปลี่ยน 5 วัน, 1 ปีวันเปลี่ยน 1 วัน
ดังนั้นวันนั้นคือ วันที่ + เดือน + ปี + เลขเพิ่มสำหรับศตวรรษ = 2 + 4 + 3 + 1 = 10 = 7 + 3 = 3 = วันพุธ



8/29/2012

Install Trac with OpenID on Ubuntu Linux 12.04

Here's my short note on installing Trac on Ubuntu 12.04 + authOpenID
Trac is an enhanced wiki and issue tracking system for software development projects. Trac uses a minimalistic approach to web-based software project management (http://trac.edgewall.org/)
Installation:
  1. install packages:
    sudo apt-get install python python-babel trac trac-authopenid trac-git
    
  2. create trac environment. My environment is like this:
    1. /trac   (Trac Parent Directory)
    2. /trac/myproject    (my project directory
    sudo mkdir /trac
    sudo mkdir /trac/myproject
    sudo chown -R your-username:users /trac   #set it to belong to you, for easier setup for now
    
  3. initial Trac project
    trac-admin /trac/myproject initenv
  4. Edit myproject's trac config
    1. Go to /trac/myproject/conf/
    2. use your prefer editor (gedit,kate,vi) to open up "trac.ini"
    3. add this at the bottom for authOpenID
      [components]
      trac.web.auth.* = disabled
      authopenid.* = enabled
    4. Enable file logging(save to /trac/myproject/log/trac.log) by changing "log_type = none" to "log_type = file"
    5. set Header Logo, under "[header_logo]" edit the config like this:
      [header_logo]
      alt = My Project
      height = 200
      link = 
      src = site/images/logo.png
      width = 800
      "site/" means your local "htdocs" (/trac/myproject/htdocs). Meaning it will search for "logo.png" under /trac/myproject/htdocs/images/logo.png
    6. set other settings if you would like
  5. Try running it on standalone first, to see if the configuration works. Use command: "tracd -p 8888 /trac/myproject" .
    1. If it is working you can browse to localhost:8888 and it should be running smoothly.
    2. don't forget to login with OpenID, by clicking "OpenID Login" on top-right corner. So we can capture the OpenID username and use it later
    3. you can stop the server on the command line terminal by pressing Ctrl-C
  6. Setting OpenID user. Since we turned on logging to file, and we tried logged in with OpenID user.
    1. see the log(using vi,tail,kate,gedit) on log file located at /trac/myproject/log/trac.log
    2. What you are looking for is something like this
      2012-08-30 04:39:28,131 Trac[session] DEBUG: Retrieving session for ID u'anidear'
      The OpenID user name is in u'xxxxxxx'. In this case, it is "anidear".
    3. Using trac-admin command to grant permission to this user if he is an admin
      trac-admin /trac/myproject permission add anidear TRAC_ADMIN
      where as "anidear" is my username from previous step.
  7. Update Trac settings using command
    trac-admin /trac/myproject upgrade
    
  8. (Optional) You may need to remove all wiki pages. It can be done by this script 
    #!/bin/sh
    
    # extract the page list from trac (this filter matches only CamelCase words or numbers,
    # it will blow if there are pages which does not fit into this scheme)
    for site in `trac-admin /trac/myproject wiki list | sed -e 's/\([a-zA-Z0-9]*\).*/\1/'`
    do
        # and remove every single page
        trac-admin /trac/myproject wiki remove $site
    done
    
    ref: http://stackoverflow.com/questions/3978755/trac-pages-delete-all
  9. Change permission so Apache can read/write to it.
    But I'm single user on this machine and I want to modify the file sometimes. So I'll just set the group for "www-data" (Apache group) just enough for Apache to read the file. Otherwise, you can set both user and group to belong to Apache buy using "www-data:www-data" for it.
    sudo chown -R :www-data /trac
    sudo chmod -R 775 /trac
    
  10. Setting Trac to works with Apache2:
    1. go to /etc/apache2/sites-available and create a file named "trac-site" (need root privilege) by using any editor
    2. put this in the file:
      Listen 0.0.0.0:9999
      NameVirtualHost *:9999
      <VirtualHost *:9999>
          ServerName trac.local
      
          <Location />
              SetHandler mod_python
              PythonInterpreter main_interpreter
              PythonHandler trac.web.modpython_frontend
      #        PythonOption TracEnv /trac/myproject
              PythonOption TracEnvParentDir /trac
              PythonOption TracUriRoot /
              PythonOption TracLocale en_US.UTF8
              PythonOption PYTHON_EGG_CACHE /tmp
              Order allow,deny
              Allow from all
          </Location>
      #    <Location /login>
      #        AuthType Basic
      #        AuthName "myproject"
      #        AuthUserFile /trac/.htpasswd
      #        Require valid-user
      #    </Location>
      
          ErrorLog /var/log/apache2/trac_error.log
          # Possible values include: debug, info, notice, warn, error, crit,
          # alert, emerg.
          LogLevel warn
          CustomLog /var/log/apache2/trac_access.log combined
      </VirtualHost>
      
      
      1. Listen 0.0.0.0:9999 set to accept any IP that comes to server with port 9999 (you can limit to use within your machine by changing 0.0.0.0 to 127.0.0.1)
      2. NameVirtualHost and VirtualHost are accepting any IP on port 9999 (again, to make it private change * to 127.0.0.1)
      3. "<Location />" is for serving Trac as the root of URL "http://localhost:9999/"
      4. "PythonOption TracEnv" is for using single project. If you want to use only single project, uncomment this line
      5. "PythonOption TracEnvParentDir" points to parent directory of projects
      6. "PythonOption TracUriRoot" is the URL that we want to use when request for Trac main page. "/" means to "http://localhost:9999/"
      7. I comment out normal Trac authentication using htpasswd because we already change to use OpenID authentication.
      8. "ErrorLog","CustomLog","LogLevel" are settings for keep logging on Trac and location to save the logs to
    3. use command "sudo a2en trac-site" to enable this site on Apache2
    4. restart Apache2 using "sudo service apache2 restart"

8/25/2012

บันทึกการเริ่มเขียน Google App Engine ด้วย Python

เริ่มจากหน้า https://developers.google.com/appengine/

ตอนนี้ Google App Engine รองรับการใช้งาน 3 ภาษา:

  • Java
  • Python ( version 2.5 กับ 2.7 เขียนต่างกัน)
  • Go
บล๊อคนี้เลือกจะทำงานด้วย python 2.7 ซึ่งดูใหม่กว่า version 2.5 และมีระบบ threadsafe คือรองรับหลายๆ client แยกกันได้พร้อมกันได้

Python ยังสามารถเลือกได้ว่าจะใช้ framework อะไรในการจัดการหน้าเวป ไม่ว่าจะเป็น "webapp2" หรือ "django" แต่ตาม tutorial ของ Google จะให้เริ่มใช้งานที่ "webapp2" 

วิธีทำ
  1. มี Python 2.7 ลงอยู่ในเครื่อง (http://www.python.org/getit/releases/2.7/)
  2. ดาวน์โหลด App Engine SDK https://developers.google.com/appengine/downloads ซึ่งข้างในจะเป็น python code ที่ถูก zip เอาไว้  ข้างในมีคำสั่งสำคัญๆ สองตัวคือ:
    1. dev_appserver.py  เอาไว้รัน server ไว้ที่เครื่องตัวเอง สำหรับทดลองเขียนโค้ด
    2. appcfg.py เอาไว้สั่งอัพโหลดโค้ดขึ้นไปที่ Google App Engine
  3. แตกไฟล์ zip ที่ได้มา ไปไว้ที่ๆนึง ที่เรียกใช้ได้ถนัดๆ เช่น /opt   แล้วก็จะมีโครงสร้างเป็น
  4. /opt/
    --google_appengine/
    ----dev_appserver.py
    ----appcfg.py
    ----.........
    ----etc.
    
    
    
  5. เริ่มเขียน app.yaml ไว้สำหรับควบคุม app นี้  (app configuration file)
    • ไฟล์นี้จะต่างจากการเขียนสำหรับ python version 2.5 ตรงที่
      • [2.7] จะใช้ runtime: "python27", แต่ [2.5] จะใช้ runtime: "python25"
      • [2.7] จะใช้นามสกุลของไฟล์ว่า .app (ถึงแม้ไฟล์จริงๆจะเป็น .py) แต่ [2.5] ก็ยังใช้ .py เหมือนเดิม ดังนั้น ถ้าใน app.yaml มีบรรทัดไหนเขียนว่า .py จะเป็นสำหรับversion 2.5 และจะเกิด error ขึ้นเวลา compile บนระบบ 2.7
      • [2.7] จะเน้นใช้งานระบบ routing ภายในโค้ดที่เรียกว่า WSGIApplication มากกว่าระบบของ [2.5] ซึ่งจะต้องเขียน routing แยกแต่ละตัวที่ app.yaml เลย
        • ดังนั้น [2.7] อาจจะเขียนในไฟล์ app.yaml แค่
        • handlers:
          - url: /.*
            script: main.app
          
        • แต่ข้างใน main.app จะเป็น
        • app = webapp2.WSGIApplication([('/', MainPage), ('/another', AnotherPage), ('/test/([a-z]*)', TestPage)]);
        • ในขณะที่ [2.5] จะต้องเป็น
        • handlers:
          - url: /another
            script: another.py
          - url: /test/([a-z]*)
            script: test.py
          - url: /.*
            script: main.py
        • more : http://webapp-improved.appspot.com/guide/handlers.html
  6. โค้ดแบบง่ายของ app.yaml จะเป็น

  7. application: helloworld
    version: 1
    runtime: python27
    api_version: 1
    threadsafe: true
    
    handlers:
    - url: /.*
      script: main.app
    

  8. เริ่มเขียนโค้ด main.py

  9. import webapp2
    
    class MainPage(webapp2.RequestHandler):
        def get(self):
            self.response.headers['Content-Type'] = 'text/plain'
            self.response.write('Hello, webapp2 World!')
    
    app = webapp2.WSGIApplication([('/', MainPage)], debug=True)
    
    • import webapp2   ==> เรียกใช้ webapp2 framework
    • class MainPage ==> สร้างคลาส MainPage ให้เป็นชนิด RequestHandler
    • def get(self) ==> สร้าง method ให้รองรับเวลาclient ส่งคำสั่ง HTTP GET มา
      • ตรงนี้จะต้องรับ self ไปเป็นตัวแปรแรกอยู่แล้ว
      • แต่ถ้าอยากจะรับตัวแปรอื่นเพิ่มจาก URL เช่น  อยากได้  /2012/08 แล้วให้ แปลงเป็น year, month จะต้องเขียนเป็น
        • แก้เป็น def get(self, year, month)
        • app = webapp2.WSGIApplication([('/([0-9]{2,4})/([0-9]{1,2})', MainPage)], debug=True)
        • ถ้าเกิดมีการเรียกเข้ามาโดยไม่ได้ใส่ค่านั้นใน URL, ตัวแปร year, month จะกลายเป็นค่า None  และสามารถเช็คค่าได้โดยการใช้  if year is None:
    • เนื่องจาก webapp2 นั้นอัพเกรดมาจาก webapp ดังนั้นจะต้องตามอ่าน documentation ของทั้งสองตัว
    • self มีอะไรให้เล่นหลายอย่าง อ่านเพิ่มตามนี้ http://webapp-improved.appspot.com/guide/handlers.html
    • WSGIApplication เป็นตัวกำหนด routing ให้กับระบบ
    • debug=True บอกว่าถ้าเกิดมีอะไรเกิดข้อผิดพลาดในโปรแกรม(ไม่ใช่เฉพาะrouting) ให้แสดง error trace บนหน้าเวปด้วย
  10. ทดลองรันบนเครื่องตัวเองด้วยคำสั่ง
    • <path ไป AppEngine SDK>/dev_appserver.py   <path ไปหาโค้ด>
    • เช่น /opt/google_appengine/dev_appserver.py   /codes/helloworld/
    • โดย default ตัว server จะ bind ไว้กับ  127.0.0.1:8080
      • ถ้าจะเปลี่ยน bind IP ให้ใช้  -a 0.0.0.0
      • ถ้าจะเปลี่ยน bind port ให้ใช้  --port 80    (อาจจะต้องการสิทธิ์ root สำหรับ port < 8000)
  11. อัพโหลดขึ้น Google App Engine cloud
    1. ไปที่ https://appengine.google.com/ แล้ว login เข้าโดยใช้ gmail
    2. สำหรับคนที่ไม่เคยสร้างเลย อาจจะต้อง verify ตัวเองก่อน โดยการใช้เบอร์โทรศัพท์ เพื่อที่จะรับโค้ดจาก Google ผ่านทาง SMS แล้วจึงนำโค้ดนั้นมากรอกบนหน้าเวปอีกที
    3. เมื่อ verify ผ่านแล้ว  จะเริ่มสร้าง Application (สร้างได้มากสุด 10 applications) โดย
      1. กดปุ่ม Create Application
      2. ก็จะมีให้กำหนดชื่อตัวโปรแกรม Application Identifier ชื่อนี้จะถูกนำไปใช้เป็น URL ด้วย คือจะได้ <appid>.appspot.com
      3. Application Title คือชื่อที่เราคนเดียวจะเห็นเวลาเราเข้ามาดูรายการ application เพราะถ้ามีหลาย application แล้วจะได้ไม่สับสน
      4. กด Create Application
    4. เมื่อสร้าง application เรียบร้อยแล้ว ให้กลับไปแก้ไฟล์ app.yaml เพื่อให้บรรทัด application: ..... มีชื่อตรงกับ Application Identifier ที่เราเพิ่งสร้างขึ้นมา
    5. เมื่อเรียบร้อยแล้ว  ใช้คำสั่งต่อไปนี้ เพื่ออัพโหลด application ขึ้นไป
      • <path ไป AppEngine SDK>/appcfg.py  update  <path ไปหาโค้ด>
      • เช่น  /opt/google_appengine/appcfg.py  update  /codes/helloworld/
      • หรือจะระบุ email ของ account ที่ใช้ login ไปเลยก็ได้โดย เพิ่ม --email=a@gmail.com
  12. เช็คเวบไซต์ที่ http://<appid>.appspot.com ว่าทำงานได้ถูกต้องหรือไม่


References:




8/20/2012

How to Sending Keystrokes Using Ruby

Sending keystrokes requires heavily on interfacing with operating system. Therefore, the solution for Ruby to do this is different for each operating system.

For me, I'm using Ubuntu, and I found couple of tools to use purely out-of-the-box (I mean as a command-line.) These tools are:

Xautomation
My opinion, this is quite useful if you want to send a string, some characters, or single keystroke. But sending combined keystroke is a bit harder and longer to do as demonstrated in the picture below. Another point is, this tool does not have a feature to send keystrokes to a specific a application window. It only can accepts "xwindow" parameter which allows us to send a keystroke to a specific Xwindow(e.g. :0), but cannot specify which application (in that xwindow) it sends to. My guess, it only send to an active application's window only.

To install on Ubuntu:
sudo apt-get install xautomation

After installation, the command that comes with this package is not actually same as package's name, it's "xte". This tool allows you to send commands, such as "str", "key", "keyup", "keydown", "mouseclick", "mouseup", "mousedown", "mousemove", and also delay commands, such as "sleep".

Usage:
xte command param


Xdotool
I found this tool because I wanted to use a Ruby gem package called "xdo" and it requires this command line tool. It turns out this "xdotool" is a powerful tool by itself, and the ruby package("xdo") is just a ruby interface of this tool. Here are reason why I think it's more powerful than Xautomation:
  • it can send keystrokes easier, e.g. xdotool key "ctrl+c"
  • it can specified application's window it sends the keystrokes to
  • it can be set to have delay between keystrokes (default = 12ms)
  • since it allows to send keystrokes to a target window, it has its own window manipulation module built-in, which allows us to:
    • search for a window using window's title name, xwindow, desktop, pid
    • set focus/unfocus the window
    • bind window event, such as mouse-enter, mouse-leave, mouse-click, focus, etc. to an xdotool's command or external command
    • resize, minimize, , close, move window

To installation on Ubuntu:
sudo apt-get install xdotool

Usage:
xdotool command param



** I also found another tool, called "X11-guitest" which seems to be a tool/library in Perl language. Since it's in Perl, not Ruby, so I don't include it here :P

Now, It's time to write code on Ruby
As I have explain above, I am using "xdo" package on Ruby which is an interface to Xdotool. So the process of using Xdo is very similar to Xdotool on command line.

First, get started by install "xdo" gem package
sudo gem install xdo

Then, write code to use Xdo.

Here's my code to cheat on Google's doodle game, "Slalom Canoe 2012"
This game needs a player to press Left and Right alternatively, and repeatedly, as fast as possible.
So why not write a script to do it.
This should work the same way as AutoIt does on Windows, or way better because it is Ruby :)

#!/usr/bin/env ruby

# includes Xdo package
require 'xdo/keyboard'
require 'xdo/mouse'
require 'xdo/xwindow'

# search for window id using title name
win_id = XDo::XWindow.search("Slalom Canoe 2012").last 

# Uncomment to bring the window up (I don't need to bring it up)
# window = XDo::XWindow.new(win_id) # create window object (this does not create an actual new window)
# window.unfocus  #make sure the window is not focus before call .activate
# window.activate #bring the window up

# loop 1000 times to send left and right to the window
1000.times do
 XDo::Keyboard.char("Left",win_id);
 XDo::Keyboard.char("Right",win_id);
end


One thing to point out in the VDO, this code does not require the window to be focused before sending the keystrokes. It can send to any unfocused or minimized window. This could turn into a malicious annoying keystroke bastard program. LOL.

For those who live on Windows
You can try AutoIt , so you don't have to write ruby code like this.
Just in case, you want to try writing Ruby code to sending keystrokes, I found two projects that can do this.

Au3 
This is a Ruby package acting as an interface to AutoIt library. That's why its requirement is "AutoItX3.dll" which you need to install AutoIt first, then copy the file from it. Au3 also created from the same team (Automations) who created Xdo.

FFI
This is totally different from the above packages. While it still acts as a proxy between Ruby code and native libraries, it does not specified the native library nor function to bind with. Therefore, you can bind it with LibC (on linux), user32.dll(on Windows), kernel32.dll(on Windows).
To send a keystroke on Windows, you need to call "kbd_event" function, on "user32.dll".

I have not yet tried these two out. So I don't know what to say on this. If anyone has tried it, please let me know on the comment below.

References:

8/02/2012

Install Conky on Ubuntu - Cool System Monitor

Conky is an application on Ubuntu for monitoring system status. It provides:

  • Date & Time
  • CPU utilization
  • Memory utilization
  • Hard disk & swap utilization
  • Network utilization
  • Be able to continuously execute commands
  • Display running processes (from "top" command)
  • Display IP addresses
  • Display kernel version and hostname
My screen is look like this now (minus the processes bar).

Individual Conky

Steps:
  1. install conky application: execute the line below
    sudo apt-get install conky conky-all
    
  2. choose theme: choices are:
    1. http://gnome-look.org/content/search.php and search for "conky"
    2. or download my version. It was "Conky Lua" theme, then I modified it a little bit to display Blackbuntu logo, IP addresses and processes .
  3. install theme: extract the downloaded theme file and copy them into directories
    1. other themes may have different way to put theme file in, so read their instruction for properly put the files
    2. but basicaly it would be like mine, which are
      1. put ".conkyrc" in your home directory (e.x. my home is at /home/anidear)
      2. put "blackbuntu_logo.png" into ".conky" directory in home directory (ex. /home/anidear/.conky) If the ".conky" directory does not exist, create it.
      3. put "clock_rings.lua" into ".lua/script" in home (so it'll be in /home/anidear/.lua/scripts) And again, if it does not exist, create it.
  4. test run: you can type "conky" into command line to see how it's running. 
    1. While it is running, if you edit ".conkyrc" file and save, Conky will update it using the new configuration automatically.
    2. Press "Ctrl+C" on the command line to stop the running process.
  5. set it to run at startup
    1. go to either "System -> Preferences -> Sessions" or "System -> Preferences -> Startup Applications"
    2. add the new startup running program (with delay 20 seconds before it starts to let system finished loading)
      • Name: Conky
      • Comment: Conky System Monitor
      • Command: sleep 20 && conky
    3. close

7/20/2012

Fixing Blackbuntu Command Line Wrapping Problem

I have been experiencing this problem for quite sometime now. It is when I type a long command (> 20 chars ), the whole line then disappears.  I have to re-type the whole line again, or pressing home then space to make it appears again.


I searched and found this article http://www.ibm.com/developerworks/linux/library/l-tip-prompt/ . And I came to the conclusion that this is a problem about coloring in bash and line wrapping.

Root cause is, coloring codes are not enclosed in bracket \[...\] which means Bash(or Xterm, I'm not sure) have to count the coloring code characters as characters on the line and does line-wrapping using the count. And this messes up the whole line.

Solution:
  1. find every file that setting the color for bash prompt. Possible files are:
    1. ~/.bashrc                <<< I found my prompt setting in here
    2. /etc/profile
    3. /etc/profile.d/*.sh
    4. /etc/bash.bashrc
  2. To check if your prompt setting in either of the files, look for the word "PS1"
  3. If there is any color code such as  
    ### Prompt 
    ((UID)) && User="\e[1;32m" || User="\e[1;31m"
    PS1="$User\u\e[0;34m@\h\e[0;37m\w$User\\$\e[m "
    

    Enclose every color codes with \[...\], so they become
    ### Prompt 
    ((UID)) && User="\[\e[1;32m\]" || User="\[\e[1;31m\]"
    PS1="$User\u\[\e[0;34m\]@\h\[\e[0;37m\]\w$User\\$\[\e[m\] "
    
  4. Save the file and you're done.
Next time you open your bash prompt again, it'll be problem-free  :)



7/13/2012

Basic Bash Shell Scripting for Network Programming

เขียน shell script บน Bash shell ให้ติดต่อกับ Network ได้


Unix, Linux เป็นระบบปฏิบัติการที่ทุกๆอย่างจะอยู่ในรูปไฟล์ทุกอย่าง
หมายความว่า นอกเหนือจากการเข้าไปถึงไฟล์ต่างๆธรรมดาแล้ว  เรายังสามารถเข้าถึงข้อมูลของระบบได้ในรูปแบบของไฟล์ได้ด้วย  เช่น
/proc/cpuinfo    #จะได้สถานะของ CPU ที่รันอยู่ตอนนี้ เช่น มีกี่ core, รันที่กี่ MHz
/proc/meminfo    #จะได้สถานะของ RAM ตอนนี้ว่า มีทั้งหมดเท่าไหร่ ใช้ไปแล้วเท่าไหร่ เหลือเท่าไหร่
/dev/sda  หรือ /dev/hda  #เป็นไฟล์ที่เป็นตัวแทนของ hard disk ตัวแรกของเครื่องขณะนั้น (sda=SATA hard disk, hda=PATA hard disk)

รวมถึงการติดต่อกับ network ก็สามารถทำการติดต่อได้ในรูปแบบนี้
/dev/tcp/host/port    #สำหรับการติดต่อด้วย TCP
/dev/udp/host/port    #สำหรับการติดต่อด้วย UDP
ตัวอย่าง เช่น
/dev/tcp/www.google.com/80
/dev/tcp/127.0.0.1/telnet      #ชื่อ port จะไปแม๊ปกับข้อมูลในไฟล์ /etc/services
/dev/udp/8.8.8.8/53

การติดต่อกับ network แบบนี้ Linux ก็จะมองว่ามันเป็นไฟล์หมดเลย ดังนั้นจึงต้องใช้วิธีการจัดการแบบไฟล์ด้วย

ดังนั้น... มารื้อฟื้นการจัดการไฟล์บน unix กันก่อนละกัน

File Descriptor
การจัดการไฟล์ของ linux จะต้องมีการเปิดไฟล์ และตั้งหมายเลขให้กับแต่ละไฟล์ที่เปิดขึ้นมาก่อน
ยกตัวอย่างเช่น
exec 9<>/etc/passwd
จะเป็นการเปิดไฟล์ /etc/passwd โดยให้ใช้หมายเลข 9 แทนไฟล์ๆนี้  และเป็นการเปิดไฟล์แบบสองทางคือ เปิดเพื่ออ่าน และ เปิดเพื่อเขียนลงไป โดยใช้ไฟล์หมายเลขเดียวกัน
(ปล. ถ้าจะเปิดไฟล์แค่ทางเดียว ให้เปลี่ยนเครื่องหมาย "<>" เป็น:  "<" สำหรับเปิดไฟล์เพื่ออ่านอย่างเดียว หรือ ">" สำหรับเปิดไฟล์เพื่อเขียนลงอย่างเดียว)

การตั้งหมายเลขไฟล์ใหม่ จะใช้หมายเลขอะไรก็ได้ที่เป็นเลขจำนวนเต็มบวก ยกเว้นสามตัวนี้ซึ่งเป็น file descriptor ที่ linux ตั้งมาให้เป็นมาตรฐานอยู่แล้วคือ
   0    หมายถึง standard input  เช่นถ้าเป็นบน console: จะหมายถึงการกด keyboard
   1    หมายถึง standard output  เช่นถ้าเป็นบน console:  จะหมายถึงการแสดงผลออกทางหน้าจอ
   2    หมายถึง standard error  เช่น ไว้แสดงผล error ที่เกิดจากคำสั่งต่างๆ โดยปกติบน console จะหมายถึงสิ่งเดียวกับ 1  (ซึ่งก็คือแสดงออกทางหน้าจอ)

การเล่นกับ File Descriptor

ก่อนอื่นต้องว่าด้วยการทำ redirection ก่อน
Redirection ก็คือ การถ่ายโอนข้อมูลจากคำสั่งนึงๆ หรือ file descriptor อันนึงให้กับตัวอื่นๆ
สิ่งที่น่าจะเคยทำกันแล้วก็คือ
เมื่อต้องการให้ แสดงผลของไฟล์(จากเดิมที่แสดงออกหน้าจอ) ให้แสดงออกลงไฟล์ คือ
ls > /tmp/abc.txt

การใช้ output redirection (เครื่องหมาย ">" )
คือหมายถึงการเอาข้อมูลที่จากเดิมจะแสดงออกทางหน้าจอ(file descriptor หมายเลข 1) ให้ออกลงไฟล์ที่ระบุไว้ด้านหลังเครื่องหมาย >

ดังนั้น คำสั่งข้างบนก็จะเหมือนกับการใช้
ls 1> /tmp/abc.txt

หมายถึงว่า เราสามารถระบุ file descriptor ให้ชัดเจนก่อนเครื่องหมาย ">" (ห้ามมีวรรคระหว่างหมายเลข file descriptor กับ > ) ได้ว่าเราต้องการจะเอาข้อมูลจาก file descriptor นี้ไป redirect ลงไฟล์

จะเห็นได้ว่า ถ้าเราจะใช้เครื่องหมาย > จะต้องตามด้วยไฟล์เสมอ แล้วการ redirection จะทำการเปิดไฟล์ เขียนไฟล์ และปิดไฟล์ให้เองโดยอัตโนมัติ

แต่ว่า ในบางกรณีเราก็ต้องการที่จะทำการเปิดไฟล์เอง ปิดไฟล์เอง และต้องการให้ redirection ทำการโอนข้อมูลเข้ามาในไฟล์ที่เปิดไว้แล้ว(มี file descriptor แล้ว) จะทำยังไง?

ตรงนี้จะเป็นการ redirect ข้อมูลลง file descriptor จะต้องใช้เครื่องหมาย >& แทน
คือถ้ามี & ตามหลังเครื่องหมาย redirection ใดๆ จะแปลว่า จะเป็นการกระทำกับ file descriptor
ยกตัวอย่างเช่น
ls 1>/tmp/abc.txt 2>&1

คำสั่งนี้จะหมายความว่า หลังจากคำสั่ง ls ได้ค่าออกมาแล้ว ให้ standard output(หมายเลขไฟล์=1)เขียนข้อมูลลงในไฟล์ /tmp/abc.txt  หลังจากนั้นจึงให้ standard error (หมายเลขไฟล์=2) ส่งข้อมูลลง standard output (หมายเลขไฟล์=1) ซึ่งตอนนี้ถูกตั้งไว้ให้ชี้ไปที่ไฟล์ /tmp/abc.txt  ดังนั้นจะได้ผลลัพธ์คือ ทั้ง error และผลลัพธ์ปกติ จะถูกเขียนอยู่ในไฟล์ /tmp/abc.txt ทั้งหมด

พอทำ redirection ของฝั่ง output ได้แล้ว(คือสัญลักษณ์ >, >&) มาดูด้านการ input ด้วย redirection บ้าง

การส่งข้อมูลเข้า มีอยู่ด้วยกันสองทางคือ การใช้เครื่องหมาย < และ การใช้ | (อ่านว่า ไปป์ pipe)
การใช้ input redirection (เครื่องหมาย "<")
ก็จะคล้ายกันกับข้างบน ก็คือ จะต้องตามหลังด้วยไฟล์ (ไม่ใช่หมายเลขไฟล์) แล้วระบบการ redirect นี้จะทำการเปิดไฟล์ให้เอง,อ่านไฟล์,ส่งข้อมูลให้กับโปรแกรม,และปิดไฟล์ให้โดยอัตโนมัติ ยกตัวอย่างเช่น
cat < /tmp/abc.txt

ก็จะเอาข้อมูลในไฟล์ /tmp/abc.txt ออกมา และโอนข้อมูลให้กับโปรแกรม cat ซึ่งก็จะแสดงผลออกมาทางหน้าจอ(หมายเลขไฟล์=1)

และเช่นเดียวกันกับเครื่องหมาย output redirection (>) ถ้าต้องการใช้ input แบบเป็นหมายเลขไฟล์ก็จะต้องใช้ & ตามหลังเครื่อง redirection เหมือนกัน เช่น
cat <& 9

ก็จะเป็นการแสดงข้อมูลทั้งหมดของไฟล์ที่ได้เปิดเอาไว้แล้วและตั้งหมายเลข file descriptor ให้ว่าเป็นหมายเลข 9

การใช้ pipe (เครื่องหมาย "|")
ก็เป็นอีกวิธีในการ redirect ข้อมูล output ของโปรแกรมหนึ่ง เพื่อให้กลายมาเป็นข้อมูล input ของอีกโปรแกรมหนึ่ง เช่น
ls | more
หมายความว่า สั่งให้ ls ทำงานไป เมื่อ ls มีข้อมูลที่จะแสดงออก standard output(หมายเลขไฟล์=1) ก็จะให้นำข้อมูลนั้นมาเป็น standard input(หมายเลขไฟล์=0) ของโปรแกรม more ต่อไป ทำให้โปรแกรม more ได้รับข้อมูลที่โปรแกรม ls แสดงผลออกมาได้โดยตรง โดยไม่ต้องมีการพักข้อมูลลงไฟล์ก่อนแล้วค่อยอ่านขึ้นมาอีกที

การปิดไฟล์
เนื่องจากเมื่อเปิดไฟล์ขึ้นมาแล้ว แต่ละ file descriptor นั้น จะสามารถรับส่งข้อมูลได้สองทางหรือทางเดียว ขึ้นอยู่กับตอนที่เปิดไฟล์ ดังนั้นการปิดไฟล์ก็จะเลือกปิดได้สองวิธี(หรือต้องปิดทั้งสองวิธี) คือ
exec 9<&-      # ปิดช่องทาง input เข้าไฟล์หมายเลข 9
exec 9>&-      # ปิดช่องทาง output ออกจากไฟล์หมายเลข 9

คราวนี้ พอได้พื้นฐานของการเล่นกับไฟล์บน linux แล้ว ก็จะเอาทุกอย่างมาประยุกต์เพื่อเล่นกับ network กันละ

อย่างที่บอกไว้ในตอนแรกว่า  การเชื่อมต่อผ่านทาง network ของ linux ก็สามารถมองให้เป็นการใช้ไฟล์ๆนึงได้
โดยการเขียนเข้าไฟล์ หมายถึง การส่งข้อมูลผ่าน network
และการอ่านข้อมูลจากไฟล์ ก็หมายถึง การรับข้อมูลจากทาง network มาประมวลผลต่อ

ยกตัวอย่างโค้ดอย่างง่ายๆ ก่อนละกัน  โค้ดต่อไปนี้เป็นการติดต่อไปที่ www.google.com ผ่านทาง port 80 เพื่อดึงหน้าเวปหน้าแรกออกมา โดยคำสั่งจะเป็น

exec 7 <> /dev/tcp/www.google.com/80
echo -e "GET / HTTP/1.0\n\n" >& 7
while read line <& 7
do
   echo -n $line
done
exec 7<&-
exec 7>&-


มาดูกันทีละคำสั่งละกัน
     exec 7 <> /dev/tcp/www.google.com/80   #เป็นการเปิดช่องทางแบบ TCP ติดต่อไปหา www.google.com ด้วย port 80 โดยจะให้ใช้ไฟล์หมายเลข 7 แทนช่องทางการติดต่อนี้

     echo -e "GET / HTTP/1.0\n\n" >& 7    # คำสั่งนี้เป็นคำสั่งที่ให้ส่งคำสั่ง HTTP ว่า ขอดึงข้อมูลจากหน้าแรก ("/") หน่อย  โดยใช้ echo ส่งคำสั่งนี้ไปใส่ในไฟล์หมายเลข 7 ซึ่งก็คือช่องทางติดต่อ www.google.com ที่เราเปิดเอาไว้แล้ว   และการใช้ "-e" หลังคำสั่ง echo คือการให้คำสั่ง echo แปลความหมายของ "\n\n" ว่าเป็นการขึ้นบรรทัดใหม่ด้วย ไม่ใช่ว่าต้องการจะแสดงออกมาเป็น "\"+"n"+"\"+"n"

    read line <& 7        # คำสั่ง read นี้เป็นการอ่านข้อมูลจากไฟล์หมายเลข 7 มาทีละบรรทัด และเก็บข้อมูลบรรทัดที่อ่านมาได้นี้ลงในตัวแปรที่ชื่อ line  (เวลานำไปใช้ต้องใส่เครื่องหมาย "$" นำหน้าด้วย เป็น "$line")

    while ... do ......... done    # เป็นคำสั่งวนรอบ โดยจะวนซ้ำ ทำคำสั่งที่อยู่ระหว่าง do....done จนกว่า คำสั่ง read นั้นจะให้ผลลัพธ์ออกมาเป็นเท็จ(แปลว่า ไม่มีข้อมูลเหลือในไฟล์หมายเลข 7 ให้อ่านแล้ว) แล้วจึงลงมาทำคำสั่งที่ถัดจาก done ต่อไป

    echo -n $line         # แสดงค่าที่เก็บอยู่ในตัวแปร line ออกมา โดยมี "-n" เพื่อบอกให้ echo ไม่ต้องเพิ่มการบรรทัดใหม่ต่อท้ายข้อมูลให้ (ปกติ echo จะมีการเพิ่มการขึ้นบรรทัดใหม่ให้หนึ่งครั้งหลังจากพิมพ์ข้อมูลเสร็จ)

   exec 7<&-  และ exec 7>&-    # ก็คือการปิดการใช้งานไฟล์หมายเลข 7  ซึ่งก็มีผลเท่ากับการจบการติดต่อกับ www.google.com ด้วย


จบเพียงแค่นี้ดีกว่า  ลองเล่นกันดูละกันนะคับ

---- อัพเดต ----
 - แก้ไขข้อผิดพลาด ls 2>&1 1>/tmp/abc.txt  เป็น ls 1>/tmp/abc.txt 2>&1 แทน

7/12/2012

Base64 encoding/decoding on Java

วันนี้ลองตั้งโจทย์ให้ตัวเอง ว่าลองทำ base64 encoding/decoding บน Java ซักหน่อย
คือเห็นว่าบน Ruby ทำได้ง่ายมากเลย เช่น
> base64=["abc"].pack("m*").chomp
 => "YWJj"
> base64.unpack("m*")[0]
 => "abc"

พอคิดว่าถ้าจะทำบน Java ก็คงทำได้ง่ายๆแหละ คิดว่าจะง่ายๆ แต่ทว่าจริงๆแล้วไม่ได้ง่ายเลย
ลอง search Google ดูปรากฎว่า Java ไม่มี built-in library สำหรับทำเรื่องนี้เลย
คือ  solution ที่จะทำได้มีดังนี้คือ

  1. เขียนเอง :  ไม่ทำแน่ๆ เพราะเยอะเกินไป และโค้ดจะรกมาก (ถ้าต้องทำก็ search หาโค้ดมาแปะเลยได้)
  2. ใช้ Built-in library ของ Sun : ใช้ class ชื่อ sun.misc.BASE64Decoder กับ sun.misc.BASE64Encoder สองตัวนี้เป็น Java's built-in library แต่ทว่าเป็น library ที่ใช้ภายในของ Sun ซึ่งจะไม่มีเอกสาร API ให้ดูว่าใช้ยังไง และที่สำคัญจะถูกยกเลิกในเร็วๆนี้  ดังนั้นตัวนี้ก็ไม่ใช่เป็นทางเลือกที่ดีนัก แต่ถ้าจะใช้ก็...
    import java.io.IOException;
    import sun.misc.BASE64Decoder;
    import sun.misc.BASE64Encoder;
    ....
    BASE64Decoder decoder = new BASE64Decoder();
    BASE64Encoder encoder = new BASE64Encoder();
    try{
        String base64 = encoder.encodeBuffer("abc".getBytes("UTF-8"));
        byte[] normal = decoder.decodeBuffer(base64);
    }catch (IOException e){
        e.printStackTrace();
    }
    ...
    
  3. ใช้ Library ของ JavaMail (http://www.oracle.com/technetwork/java/javamail/index.html): โดยใช้ class ชื่อ javax.mail.internet.MimeUtility JavaMail ตัวนี้ต้องมีการดาวน์โหลด jar ของ JavaMail มาแล้วใส่ไว้ใน classpath เพื่อใช้งาน
  4. ใช้ Library ของ Apache Common Codec (http://commons.apache.org/codec/): โดยใช้คลาสชื่อ org.apache.commons.codec.binary.Base64 ข้อดีคือเป็น library ของ Apache แต่ว่ายังไงก็ต้องโหลด jar มาเพิ่มอีกอยู่ดี
  5. ใช้ Library ของ MiGBase64 (http://migbase64.sourceforge.net/) : ตัวนี้ก็อีกเช่นกันที่ต้องโหลด library มาเพิ่มเพื่อรัน
  6. ใช้ Library ของ Jetty (http://download.eclipse.org/jetty/stable-7/apidocs/org/eclipse/jetty/util/B64Code.html)
  7. ใช้ Library ของ iHarder (http://iharder.sourceforge.net/current/java/base64/)
  8. ใช้ Built-in library ของ Java ชื่อ com.sun.org.apache.xml.internal.security.utils.Base64 แต่ว่ามันเป็นส่วนภายในซึ่งจะไม่มีเอกสารอ้างอิง
  9. ใช้ Built-in library ของ Java ชื่อ java.util.prefs.Base64 แต่ว่ามันเป็นส่วนภายในซึ่งจะไม่มีเอกสารอ้างอิง
  10. ใช้ Built-in library ของ Java ชื่อ javax.xml.bind.DatatypeConverter  ซึ่งเพิ่งเพิ่มมาใน Java6 และสามารถทำ Base64 encode/decode ได้  พร้อมเอกสาร API ที่ http://docs.oracle.com/javase/6/docs/api/javax/xml/bind/DatatypeConverter.html
คือสรุปแล้วว่า   Java แทบจะไม่มีคำสั่งนี้ให้มาตั้งแต่แรกเลย ทั้งๆที่มันควรจะเป็นคำสั่งพื้นฐานในการจัดการกับข้อมูลสำหรับส่ง binary data โดยเฉพาะผ่านทาง HTTP  
จากที่ลิสต์จะเห็นว่า 6/10 เป็น library ที่จะต้องไปโหลดไฟล์ .jar เพิ่มเพื่อจะใช้งานนั้น
ส่วน 4/10 เป็น library ที่ built-in ใน Java เอง  แต่ใช้งานได้อย่างมาตรฐาน มีเอกสารประกอบครบเพียงแค่ 1 วิธีเท่านั้น  อีก 3 วิธีที่เหลือเป็นคำสั่งที่ใช้งานกันภายในผู้พัฒนาJava ซึ่งจะไม่มีเอกสารประกอบว่าจะใช้งานยังไง แล้วก็อาจจะถูกตัดทิ้งได้ทุกเวลา ก็จะทำให้ไม่สมควรจะใช้ในงานจริงๆ

ข้างล่างต่อไปนี้เป็นโค้ดตัวอย่างของการใช้วิธีที่ 10 (javax.xml.bind.DatatypeConverter)
class TestBase64{
    public static void main(String[] args){
        /*** ascii art gun: from http://www.ascii-art.de/ascii/ghi/gun.txt ***/
        String original_word = 
        "  +-'~`---------------------------------/\\--\n"+
        " ||\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\" \\\\\\\\\\\\  \\/~)\n"+
        " ||                                  \\\\\\\\\\\\  \\/_\n"+
        "  |~~~~~~~~-________________-_________________\\ ~--_\n"+
        "  !---------|_________       ------~~~~~(--   )--~~\n"+
        "                      \\ /~~~~\\~~\\   )--- \\_ /(\n"+
        "                       ||     |  | \\   ()   \\\\\n"+
        "                       \\____/_ / ()\\        \\\\\n"+
        "                        `~~~~~~~~~-. \\        \\\\\n"+
        "                                    \\ \\  <($)> \\\\\n"+
        "                                     \\ \\        \\\\\n"+
        "                                      \\ \\        \\\\\n"+
        "                                       \\ \\        \\\\\n"+
        "                                        \\ \\  ()    \\|\n"+
        "                                        _\\_\\__====~~~\n";
        System.out.println(original_word);
        try{
            String base64 = DatatypeConverter.printBase64Binary(original_word.getBytes("UTF-8"));
            System.out.println(base64);
            String result = new String(DatatypeConverter.parseBase64Binary(base64),"UTF-8");
            System.out.println(result);
        }catch(Exception e){
            e.printStackTrace();
        }
    }
}

เอกสารอ้างอิง
[ - ] http://www.javatips.net/blog/2011/08/how-to-encode-and-decode-in-base64-using-java
[ - ] http://stackoverflow.com/questions/469695/decode-base64-data-in-java


6/25/2012

Labs for Practice Penetration Testing

I found this page, it has a pretty good mind-map listing all available labs to practice your skill in doing penetration testing.
The link is http://www.amanhardikar.com/mindmaps/PracticewithURLs.html

full image size please go to the original website

Vulnerable Web Applications [36 unique web applications]

  • OWASP BWA http://code.google.com/p/owaspbwa/
  • OWASP Hackademic http://hackademic1.teilar.gr/
  • Butterfly Security Project http://thebutterflytmp.sourceforge.net/
  • Foundstone Hackme Bank http://www.mcafee.com/us/downloads/free-tools/hacme-bank.aspx
  • Foundstone Hackme Books http://www.mcafee.com/us/downloads/free-tools/hacmebooks.aspx
  • Foundstone Hackme Casino http://www.mcafee.com/us/downloads/free-tools/hacme-casino.aspx
  • Foundstone Hackme Shipping http://www.mcafee.com/us/downloads/free-tools/hacmeshipping.aspx
  • Foundstone Hackme Travel http://www.mcafee.com/us/downloads/free-tools/hacmetravel.aspx
  • LAMPSecurity http://sourceforge.net/projects/lampsecurity/
  • Moth http://www.bonsai-sec.com/en/research/moth.php
  • WackoPicko https://github.com/adamdoupe/WackoPicko
  • BadStore http://www.badstore.net/
  • WebSecurity Dojo http://www.mavensecurity.com/web_security_dojo/
  • BodgeIt Store http://code.google.com/p/bodgeit/
  • hackxor http://hackxor.sourceforge.net/cgi-bin/index.pl
  • SecuriBench http://suif.stanford.edu/~livshits/securibench/
  • SQLol https://github.com/SpiderLabs/SQLol

Vulnerable Operating System Installations [16+ unique OS setups]

  • Damn Vulnerable Linux http://sourceforge.net/projects/virtualhacking/files/os/dvl/
  • Metasploitable v1 http://www.metasploit.com/learn-more/how-do-i-use-it/test-lab.jsp
  • Metasploitable v2 https://community.rapid7.com/docs/DOC-1875
  • LAMPSecurity http://sourceforge.net/projects/lampsecurity/
  • UltimateLAMP http://ronaldbradford.com/tmp/UltimateLAMP-0.2.zip
  • De-ICE, hackerdemia, pWnOS http://forums.heorot.net/
  • Holynix http://pynstrom.net/holynix.php
  • Kioptrix http://www.kioptrix.com/
  • CentOS http://www.centos.org/

Sites for Downloading Older Versions of Various Software [3 sources]

  • Old Apps http://www.oldapps.com/
  • Old Version http://www.oldversion.com/
  • Exploit-DB http://www.exploit-db.com/

Sites by Vendors of Security Testing Software [8 unique sites]

  • Acunetix acuforum http://testasp.vulnweb.com/
  • Acunetix acublog http://testaspnet.vulnweb.com/
  • Acunetix acuart http://testphp.vulnweb.com/
  • Cenzic crackmebank http://crackme.cenzic.com
  • HP freebank http://zero.webappsecurity.com
  • IBM altoromutual http://demo.testfire.net/
  • Mavituna testsparker http://aspnet.testsparker.com
  • Mavituna testsparker http://php.testsparker.com

Sites for Improving Your Hacking Skills [16 unique sites]

  • Google Gruyere http://google-gruyere.appspot.com/
  • Hack This Site http://www.hackthissite.org/
  • Hacker Challenge http://www.dareyourmind.net/
  • HackQuest http://www.hackquest.com/
  • Hax.Tor http://hax.tor.hu/
  • Hacker Test http://www.hackertest.net/
  • OverTheWire http://www.overthewire.org/wargames/
  • Root Me http://www.root-me.org/?lang=en
  • Smash The Stack http://www.smashthestack.org/
  • TheBlackSheep and Erik http://www.bright-shadows.net/
  • ThisIsLegal             http://thisislegal.com/
  • Try2Hack                http://www.try2hack.nl/
  • EnigmaGroup             http://www.enigmagroup.org/
  • hACME Game              http://www.hacmegame.org/
  • Exploit Exercises       http://exploit-exercises.com/
  • Hacking-Lab             https://www.hacking-lab.com


6/19/2012

Ruby Script For Downloading OS X's Boot Camp

My situation was to installing a Windows7 on to Mac Book Pro 13".
It should be doing fine because of the Boot Camp application that pre-installed in every OS X (in /Applications/Utilities) and it will need just few clicks to be done. Unfortunately, during the process, the BootCamp application needs to download Boot Camp drivers (they are Mac devices' driver for Windows) directly from Apple website and I can't download it because of bad internet access. Another way around is to skip the download, and use OS X installation DVD to install the drivers into Windows when it finishes installation. Somehow the MacBook I got doesn't come with any OS X Lion DVD. I can't do it this way.


Then I searched on the Internet for a way to download the drivers directly (trying to by-pass the application). I found nothing useful on Apple website. Apple's download page only has Boot Camp update files but not the actual installation file for the first-time users. No hope in doing it for me.


Further I searched, I found this blog. It has a pretty detail information on how to directly download the Boot Camp drivers. However, there is a step that would takes me a lot of time to do it if I have to do it manually. Therefore, I wrote this script for doing the heavy task for me.


Let me describe the basic process first:

  1. First, you need to see the model of your Mac, go to "Apple menu" -> "About this Mac" -> "More Info…" -> "Hardware". Remember the model and number (e.g. "MacBookAir3,1"), it will be use later. 
  2. you need to download this file http://swscan.apple.com/content/catalogs/others/index-lion-snowleopard-leopard.merged-1.sucatalog . It's a Mac update file list.
  3. Open the file in a good memory-management text-editor application, since the file is quite large for Notepad. (the file's size is 2.1 MB, and it could crash Notepad)
  4. search for a URL in the file that has word BootCampESD.pkg in it. And keep all matched lines.
  5. For each location of the matched line, in few lines below it, there is another URL that has the word "English.dist" (make sure the version (041-XXXX) from both URL are matched)
  6. Use a browser to go to .dist URL, it will contains javascript at the top of the HTML. On the top of the HTML page, there is a line contains information of the model that needs this download, such as:  var models = ['MacBookAir3,1','MacBookAir3,2',];
  7. Check to see if your model:
    1. Matches: download the .pkg file( it would be around ~600 MB), then proceed to next step (8)
    2. doesn't match what's in the Javascript, both these .dist and .pkg can be ignore. And repeat from step 4. again until there is a match
  8. From the file "BootCampESD.pkg", use 7zip(http://www.7-zip.org/) to browse into the file under Library/Application Support/BootCamp/ , then extract a file called WindowsSupport.dmg out
  9. Further use 7zip to browse inside "WindowsSupport.dmg" and there is another file 0.Apple_ISO and this is the file we are looking for. Extract it out and rename it to have suffix as ".iso" ; e.g. BootCamp.iso
  10. Mount BootCamp.iso on Windows or write it to a  or USB
  11. and now you can install drivers from the drivers resided in the ISO file.

As you can see, there are a hard-working process from steps 4 to 7, since I found that it has about 6 iterations I have to do it. So I thought I would write a ruby script for processing it for me, it would be pretty convenient. I was planing on writing every steps into my script at first, but I can't find a way to extract the "BootCampESD.pkg" yet. So, my script only does step 2-7 for now.

Here's the code
#!/usr/bin/env ruby

# Author: Anidear (anidear1 {at} gmail {dot} com)
# My Blog: http://blog.anidear.com
# Credit: http://blog.timvalenta.com/2011/11/19/boot-camp-driver-downloads/

require 'net/http'
require 'uri'

MAC_UPDATE_URL = 'http://swscan.apple.com/content/catalogs/others/index-lion-snowleopard-leopard.merged-1.sucatalog'

mac_update_uri = URI(MAC_UPDATE_URL)
mac_update_xml = Net::HTTP.get(mac_update_uri)
dist_links = Hash.new
pkg_links = Hash.new
mac_update_xml.split("\n").each{|line|
    #search for 041-XXXX.English.dist or BootCampESD.pkg
    line.chomp.scan(%r{(http://[^<]*041-[^<]*\English.dist)|(http://[^<]*BootCampESD\.pkg)})
        .each{|link|
            link_url = (link[0].nil?)? link[1] : link[0] ;
            version = link_url.match(/041-[0-9]+/)[0]
            if (link[0].nil?)
                pkg_links[version] = link_url
            else
                dist_links[version] = link_url
            end
        }
}

#filter only .pkg presents
dist_links.select!{|key,val|
    pkg_links.key?(key) #keep dist_link if pkg_link presents
}

#for each of dist_links
#find something like 
# "var models = ['MacBookAir3,1','MacBookAir3,2',];"
list = dist_links.keys.sort
list.each{|key|
    val = dist_links[key]
    uri = URI(val)
    data = Net::HTTP.get(uri)

    res = data.match(/var models = \[(.*)\];/)
    if !(res[1].nil?)
        puts "Code: #{key}"
        res[1].scan(/'[^']*'/).each{|model|
            puts "\t[+] #{model}"
        }
        puts "Download Link:\n\t[+] #{pkg_links[key]}"
        puts
    end
}

# instruction
puts 'INSTRUCTIONS: '
puts '1. Go to "Apple menu" -> "About this Mac" -> "More Info…" -> "Hardware"'
puts '   to see what model your mac is.'
puts '2. Match your model with the download link above and download it'
puts '3. Use 7zip application to extract the pkg file'
puts '4. Browse to "Library/Application Support/BootCamp/" in the archive'
puts '5. Inside there is a file "WindowsSupport.dmg", also use 7zip to browse inside it'
puts '6. There is "0.Apple_ISO" file, rename it to have a proper .iso extension e.g. "BootCamp.iso"'
puts '7. Mount BootCamp.iso in Windows or burn it to a CD in order to use it.'
puts 'Have fun!'



Credit:
http://blog.timvalenta.com/2011/11/19/boot-camp-driver-downloads/